Welcome to another weekly article on System Design. In this article, I will give you a template for how to design systems using 'The Method'.
As I’ve already stated: we are going to learn the rules, follow the rules, and once you are an expert — bend the rules.
Let’s keep track of what we’ve learned so far, so everything stays at our fingertips even if you haven’t read the previous article:
- Avoid functional decomposition (what we were doing in universities), and remember: a good system design speaks — through how components interact.
- The client should not be the core business. Let the client be the client — not the system.
- Decompose based on volatility — list the areas of volatility.
- There is rarely a one-to-one mapping between a volatility area and a component.
- List the requirements, then identify the volatilities using both axes: — What can change for existing customers over time? — Keeping time constant, what differs across customers? (Remember: these axes are independent.)
- Verify whether a solution/component is masquerading as a requirement. Verify it is not variability. A volatility is not something that can be handled with if-else; that’s variability.
Let’s now start diving into the template.
The Use Case
A use case is an expression of required behavior — how the system is going to accomplish something to add business value. It describes the end-user interaction with the system or the system’s interaction with the user. It also illustrates system-to-system interactions and processing. It is always recommended to capture use cases graphically. Nothing communicates better and more simply than pictures.
An activity diagram showing all possibilities can also be drawn.
The two figures below show how a use case diagram (left) and an activity diagram (right) look:
It should be noted that it is not feasible to draw activity diagrams for all possible cases. So, only important ones should be covered, leaving the simple ones aside — they will be understood naturally through the layered approach we’re about to cover. Get ready for the ultimate framework.
Layered Approach
A layered approach is a better representation of systems and services. The encapsulations are layered:
a) Each layer encapsulates the volatility of itself and of the layers below it, from the layers above.
b) Within a layer, the services encapsulate volatility from each other. (Reminder: by “services,” I do not mean microservices.)
The following image is the same one I used to define a good design decision tree in the very first article. Don’t worry if you haven’t read it — but if you have, you’ll relate to it:
The image below is taken from the book Righting Software by Juval Löwy. I use the same color template while designing systems, and we’ll continue using it throughout the series:
It should be obvious that the number of layers in a practical system is limited. These layers terminate at a resource layer — such as a database, file storage, or third-party API.
The Services
As I’ve already emphasized in previous articles: system design is not just tech — it is engineering.
With service flow diagrams, the entire data flow can be understood very well. This is one of the key advantages of volatility-based decomposition. Designing in this layered manner makes service calls visualizable, giving us insights even before deployment — and certainly after some traffic is observed.
Things like security, scalability, traffic, throughput, responsiveness, consistency concerns, and synchronization can all be examined more clearly.
The General Template Framework for System Design
Here is the general framework of 'The Method'. We will discuss each section.
The Client
These are the entry points to the system. Make sure your client is only a client — not a system. The client should not encapsulate business logic. All clients should use the same entry point to the system. A phone app and a web app should not be calling different backends.
Volatility by client:
- Axis 1 (What can change for existing customers?): Different users may demand different accessibility options, dark mode, etc.
- Axis 2 (What differs across customers?): It can be a desktop app, Android app, or just an API interface. Technology may vary — React, .NET, etc.
The Business Logic Layer
This layer encapsulates the system’s use cases — i.e., what the system is supposed to do from a business perspective. The components here are Managers and Engines.
- Manager: Encapsulates volatility in the sequence.
- Engine: Encapsulates volatility in the activity.
A Manager can utilize zero or more Engines to complete a business task.
For example, a car starting system may look like:
MovementManager.Start();
And within the Manager:
PreparingEngine.ConfirmAdjustSeat();
PreparingEngine.AdjustSideGlass();
PreparingEngine.CloseWindow();
DrivingEngine.AdjustBrake();
DrivingEngine.AdjustGear();
DrivingEngine.AdjustAccelerator();
DrivingEngine.AdjustClutch();
DrivingEngine.StartICEngine();
Here, the Manager (MovementManager
) is utilizing multiple Engines — PreparingEngine
, DrivingEngine
.
Ensure that two managers do not use two different engines to do the same job. That’s a symptom of functional decomposition.
Engines can be reused between Managers, as the same activity might appear in different use cases. Design Engines with reuse in mind.
The Resource Access Layer
Components here are called ResourceAccess.
Volatility by ResourceAccess:
This layer defines how to access a resource — but it should not expose contracts like Read()
, Write()
, Open()
, Close()
.
Instead, it should use business verbs, such as:
CurrentFuelVolume()
GetMusicPlaylists()
Using database-style contracts creates tight coupling — any change in the resource's tech affects all upper layers. Avoid this.
ResourceAccess components may be reused across Engines or Managers needing access to the same resource.
The Resource Layer
This contains the actual physical resources — databases, file systems, caches, third-party APIs, etc. It encapsulates the tech being used. This can also include external systems like payment APIs.
Utility
The Utilities vertical bar (on the right of the diagram) includes common infrastructure services that most systems need:
- Authentication
- Logging
- Messaging Queue
- Pub/Sub, etc.
Rechecking the Layer Classification
Once you’ve classified your layers and components, ask these questions. If the answers are “yes,” you’ve done volatility-based decomposition — not functional decomposition.
- Are the names descriptive? E.g.,
SomeManager
,DoSomethingEngine
,SomethingResourceAccess
- For Managers, the prefix should be a noun tied to the encapsulated volatility (e.g.,
MovementManager
) - For Engines, the prefix should be a noun describing the activity (e.g.,
DrivingEngine
) - For ResourceAccess, the prefix should be a noun related to the resource (e.g.,
FuelResourceAccess
) - Gerunds (nouns ending in -ing) should only be used with Engines. Their use elsewhere usually signals functional decomposition.
- Atomic business verbs should not be used in service names — only as operation names in ResourceAccess contracts.
Learn these rules. They ensure we don’t fall into the trap of functional decomposition.
I’m Updating the Rules to Carry Forward
Let’s keep the rules on our fingertips for the next article:
Updated Rules
- Avoid functional decomposition (what we were doing in universities), and remember: a good system design speaks — through how components interact.
- The client should not be the core business. Let the client be the client — not the system.
- Decompose based on volatility — list the areas of volatility.
- There is rarely a one-to-one mapping between a volatility area and a component.
- List the requirements, then identify the volatilities using both axes: — What can change for existing customers over time? — Keeping time constant, what differs across customers? (Remember: these axes are independent.)
- Verify whether a solution/component is masquerading as a requirement. Verify it is not variability. A volatility is not something that can be handled with if-else; that’s variability.
-
Use the layered approach and proper naming convention:
- Names should be descriptive and avoid atomic business verbs.
- Use
<NounOfVolatility>Manager
,<Gerund>Engine
,<NounOfResource>Access
. - Atomic verbs should only be used for operation names, not service names.
Conclusion
See you next Sunday in another article where I will dive with examples. Stay Tuned!
Here are links to previous articles in case you missed them:
Top comments (0)