Be Software Architecture Wise
Be Wise Apps

Leer en español · Lire en français · Auf Deutsch lesen

Chapter 3 of 12 · Intermediate

Design Principles and Fundamentals

Every architecture is built from the same few ideas about structure: how to divide a system into parts, how much those parts should know about each other, which way their dependencies should point and how to keep the whole thing as simple as the problem allows. This chapter teaches those ideas at every scale, from a Python class to a component to a fleet of services: modularity, coupling and cohesion, information hiding, dependency management, SOLID, connascence, Conway's law, simplicity and the metrics that measure structure. It suits developers who want to reason about design precisely, and architects who need a shared vocabulary for reviews.

Video introduction to Chapter 3

6 min · narrated, with captions
Listen only (audio)
Read the transcript

Welcome to Chapter 3: Design Principles and Fundamentals. Every architecture is built from the same few ideas about structure. How to divide a system into parts, how much those parts should know about each other, which way their dependencies should point, and how to keep the whole thing as simple as the problem allows. This chapter teaches those ideas.

Chapter 2 described the qualities an architecture has to deliver. Most of them, and modifiability above all, depend less on any particular technology than on structure. A team can choose the most fashionable framework and still end up with a system where every change touches ten places. These principles are old ideas. Information hiding dates from nineteen seventy-two, and Conway's law from nineteen sixty-eight. They have survived because they describe something that does not change: the cost of change depends on how far a change has to travel. What has changed is the scale. The principle that tells you to keep a class's fields private also tells you to keep a service's database private. So every section shows each idea twice: in a small piece of code, and at the scale of components and services.

This chapter suits developers who want to reason about design precisely. It also suits architects who need a shared vocabulary for design reviews. Being able to read short Python examples helps. It builds on Chapter 2's quality attributes, which these principles help to achieve. It is worth having read Section zero point three on organising code, and Section two point six on designing for change. And it leads into Chapter 4, which shows how to describe a structure so that others can understand it and challenge it.

The first four sections lay the foundations. Section three point one asks what a module is and where its boundaries should go. David Parnas showed that dividing a system by processing steps fails, and dividing it by likely change succeeds. Section three point two gives you the vocabulary for judging boundaries: the classic levels of coupling and cohesion, and the kinds of coupling that only appear between services. Section three point three looks at the interface itself: information hiding, contracts, abstractions that leak, and why every visible behaviour of a popular interface ends up being depended on. Section three point four turns to direction: which way dependencies should point, how to measure stability, how to break cycles, and how to check all of this automatically.

This diagram from Section three point two shows the vocabulary at work, in a real refactoring. On the left is the design before: an orders module and a billing module. Orders calls Billing directly, passing a flag that selects one of two behaviours. So Orders must know about that flag, and it cannot accept an order if Billing is slow or failing. Worse, both modules use the same orders table. Any change to its layout, its status codes or its VAT rules has to be made in both modules at once. On the right is the design after. Orders publishes an Order Placed event to a message broker, carrying exactly the facts Billing needs. Billing subscribes, and each service owns its own data. The coupling has not vanished: Billing still depends on the meaning of that event. But it is now explicit, narrow and versioned, which is what loose coupling means in practice.

The second four sections build on those foundations. Section three point five covers the five SOLID principles, each shown in code and between services, together with the criticism they attract when applied mechanically. Section three point six introduces connascence, a finer measure of coupling, with a clear set of rules for making it weaker and more local. Section three point seven widens the view to the organisation: Conway's law, the evidence for it, and the arithmetic of communication paths. Section three point eight closes with simplicity and measurement: the complexity that belongs to the problem and the complexity we add ourselves, the price of keeping options open, and the limits of all metrics.

Here is Conway's law in one picture. On the left is an organisation. A checkout team and a catalogue team must go through a shared team of database administrators for every schema change. On the right is the system they build: a checkout service and a catalogue service that both read and write a shared database. The structure of the system copies the structure of communication. So when you design a system, you are also designing who needs to talk to whom, and the reverse is just as true.

Read together, the eight sections make one argument. Change is the dominant cost of software, so a good structure is one in which likely changes stay local. Every principle in the chapter is a way of achieving that. That is also why the principles sometimes conflict, and why none is absolute. The professional skill is to ask, for a change that is likely in this system, how far it would have to travel, and which principle shortens that distance at the lowest cost.

If you are new to design, take the sections in order. Each opens with a short everyday explanation. The whole chapter takes about three hours to read. If you are an experienced engineer, you may prefer to start with the worked examples, such as the online shop in Section three point one, or the metrics calculation in Section three point four. They are designed to be argued with. Each section ends with a few self-check questions. Answer them in your head before you look. When you are ready, take the optional chapter test: twenty multiple-choice questions and two written answers, in about forty five minutes. The pass mark is eighty per cent.

That is the map of Chapter 3. Start with Section three point one, on modularity, where we see how to divide a system so that the changes you expect stay inside one part. See you there.

Download chapter (PDF)Read in the app

At a glance

You will learn to

  • Explain Parnas's criterion for decomposing a system into modules and apply it to compare two candidate decompositions.
  • Classify dependencies using the classic levels of coupling and cohesion, and identify temporal, deployment and semantic coupling between services.
  • Distinguish information hiding from encapsulation, specify an interface as a contract, and design stable APIs that account for leaky abstractions and Hyrum's law.
  • Calculate instability, abstractness and distance from the main sequence, and use them with the acyclic, stable-dependencies and stable-abstractions principles to fix dependency structures.
  • Apply the SOLID and component cohesion principles at class, component and service scale, and recognise when they are being applied dogmatically.
  • Use connascence to describe the strength, locality and degree of coupling and choose refactorings that weaken it.
  • Explain Conway's law, the evidence for it and the inverse Conway manoeuvre, and calculate communication paths for a team.
  • Separate essential from accidental complexity, compute cyclomatic complexity, and turn structural rules into fitness functions without falling into Goodhart's law.

Before you start

Chapters 0 to 2, especially Section 0.3 (organising code), Section 1.7 (technical debt) and Section 2.6 (modifiability). The ability to read short Python examples helps.

Time

About 3 hours to read; 45 minutes for the test.

Key terms

modularity · information hiding · coupling · cohesion · afferent and efferent coupling · abstraction · design by contract · Hyrum's law · Acyclic Dependencies Principle · instability and abstractness · SOLID · connascence · Conway's law · accidental complexity

Why principles, and why at every scale

Chapter 2 described the qualities an architecture has to deliver. Most of them, and modifiability above all, depend less on any particular technology than on structure: how a system is divided into parts, what each part knows about the others, and which way the dependencies between them run. A team can choose the most fashionable framework and the best cloud platform and still end up with a system where every change touches ten places, because the parts were cut in the wrong places. The principles in this chapter are the accumulated knowledge of more than fifty years about cutting them well.

They are old ideas, and that is a strength. Information hiding dates from 1972, coupling and cohesion from the structured design work of the late 1960s and 1970s, Conway's law from 1968. They have survived because they describe something that does not change: the cost of change depends on how far a change has to travel. What has changed is the scale at which architects apply them. The same principle that tells you to keep a class's fields private tells you to keep a service's database private; the same reasoning that forbids a dependency cycle between two packages forbids two services that can only be released together. Every section therefore shows each idea twice: in a small piece of code, and at the scale of components and services.

How the chapter is organised

The first four sections lay the foundations. Section 3.1 asks what a module is and where its boundaries should go, using David Parnas's KWIC example to show why decomposing by processing steps fails and decomposing by likely change succeeds, and separates logical modules from the physical units that are built and deployed. Section 3.2 gives the vocabulary for judging boundaries: the classic levels of coupling and cohesion, afferent and efferent coupling, and the kinds of coupling that only appear between services. Section 3.3 looks at the interface itself: information hiding versus encapsulation, contracts, leaky abstractions, deep modules and Hyrum's law. Section 3.4 turns to direction: Robert C. Martin's principles for dependencies between components, the instability and abstractness metrics, how to break cycles, layering, and how to check all of this automatically.

The second four sections build on them. Section 3.5 covers the five SOLID principles and the component cohesion principles, each shown both in code and between services, together with the criticism they attract when applied mechanically. Section 3.6 introduces connascence, a finer measure of coupling with a clear set of rules for making it weaker and more local. Section 3.7 widens the view to the organisation: Conway's law, the evidence for it, the arithmetic of communication paths and the inverse Conway manoeuvre. Section 3.8 closes with simplicity and measurement: essential and accidental complexity, KISS, YAGNI and DRY properly understood, the price of keeping options open, structural metrics such as cyclomatic complexity, and the limits of all metrics.

One idea underneath them all

Read together, the eight sections make one argument. Change is the dominant cost of software, so a good structure is one in which likely changes stay local. Every principle in the chapter is a way of achieving that. Parnas says to put each likely change inside one module. Coupling and cohesion say to keep things that change together close and things that change separately apart. Information hiding says to make sure that what may change is invisible from outside. The dependency principles say to point dependencies at the things that change least. Connascence says to keep any strong agreement between two pieces of code as close together as possible. Conway's law adds that people are part of the structure: a change that needs two teams to agree is not local, however the code is arranged.

That argument also explains why the principles sometimes conflict and why none is absolute. Removing duplication can increase coupling; adding interfaces to invert a dependency can make a module shallower; splitting a component to improve cohesion can create a network hop. The professional skill is not to apply every principle everywhere but to ask, for a specific change that is likely in a specific system, how far it would have to travel, and which principle shortens that distance at the lowest cost.

How to use this chapter

Readers new to design can take the sections in order; each opens with a short everyday explanation and links to Section 0.3 where the basics were first introduced. Experienced engineers may want to start with the worked examples: the online-shop decomposition in Section 3.1, the order and billing refactoring in Section 3.2, the payment interface in Section 3.3 and the metrics calculation in Section 3.4 are designed to be argued with. The principles here are used throughout the rest of the course: the architectural styles of Chapters 5 and 6 are largely different answers to the questions of where to put boundaries and which way dependencies point, and Chapter 11 turns the automated checks introduced here into a governance practice through fitness functions.

The 8 sections

3.1 · Intermediate · 22 minModularityWhat a module is at every scale, why we divide systems into modules, Parnas's rule of decomposing by likely change, logical versus deployment modules, granularity, and how to enforce boundaries in code.Read →3.2 · Intermediate · 24 minCoupling and cohesionThe two oldest measures of good structure: the classic levels of coupling and cohesion, afferent and efferent coupling, the extra forms of coupling in distributed systems, and tactics to loosen it, with a worked refactoring.Read →3.3 · Intermediate · 24 minInformation hidingWhy modules should hide decisions, how information hiding differs from encapsulation, interfaces as contracts and design by contract, leaky abstractions, deep modules, Hyrum's law and how to keep APIs between components stable.Read →3.4 · Intermediate · 25 minDependenciesWhich way dependencies should point and why: Robert C. Martin's acyclic, stable-dependencies and stable-abstractions principles, the instability, abstractness and distance metrics, breaking cycles, layering, and automated dependency checks.Read →3.5 · Intermediate · 26 minSOLID at scaleThe five SOLID principles explained precisely, each shown in Python and then at component and service scale, plus the REP, CCP and CRP cohesion principles, their tension triangle and the honest criticism of SOLID.Read →3.6 · Intermediate · 22 minConnascenceMeilir Page-Jones's connascence names the hidden agreements between parts of a system, ranks them by strength and judges them by locality and degree, from positional parameters to timing between services.Read →3.7 · Intermediate · 22 minConway's lawMelvin Conway's 1968 observation that systems copy the communication structure of the organisations that design them, the research evidence, the arithmetic of communication paths, the inverse Conway manoeuvre and team cognitive load.Read →3.8 · Intermediate · 25 minSimplicity and measurementEssential and accidental complexity, KISS, YAGNI, DRY as knowledge rather than text, reversibility as an option, and how to measure structure with cyclomatic complexity, LCOM, change coupling and fitness functions without falling foul of Goodhart's law.Read →

Chapter test

Test your understanding of Chapter 3

Optional: 20 multiple-choice questions and 2 short written answers, drawn afresh from a bank of 72 questions. Pass mark 80%. Passing every chapter test, then the final assessment, earns the Be Wise Certificate of Completion.

Take the Chapter 3 test in the app

Further reading