DEV Community

Ujjwal Raj
Ujjwal Raj

Posted on

Design Don’ts in System Design with ‘The Method’

Welcome to another Sunday Blog on System Design.
This Sunday, we’ll explore the don’ts to keep in mind when designing a system using The Method.

Here are the rules we've already summarized. As I often say:
Learn the rules, follow the rules — and only once you're good at it, should you even think about bending them.

I suggest going through the last two articles to fully grasp the ideas discussed here: Part 1 | Part 2

  • 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? What are different use cases? (Remember: Almost always, 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.
  • layers given in the template should correspond to 4 question:

    • Client : Who interacts withe system
  • Managers : What is required of the system

  • Engines : How system performs the business logic

  • ResourceAccess : How system access the resources

  • Resource : Where is the system state

  • Validate if your design follows the golden ratio (Manager:Engine). Some valid ratios: 1:(0/1), 2:1, 3:2, 5:3.

    More than 5 Managers? You’re likely going wrong.

  • Volatility decreases from top to bottom, while reusability increases from top to bottom.

  • Slices are subsystems. No more than 3 Managers in a subsystem.

  • Design iteratively, build incrementally.

Now let's see how these don'ts are listed by opening a closed architecture to a semi-closed one.

Open and Closed Architecture

In an open architecture, any component can call any other component in any layer. The flexibility is there, but it's a bad design. There is too much coupling, and encapsulation is heavily sacrificed.

There are only two problems in Software Engineering:

  • Dependency management
  • Information hiding

Both problems are intensified when we use an open architecture. Even calling horizontally (intra-layer) creates some amount of coupling, so it should be avoided.

On the other hand, a closed architecture restricts access to only the immediate lower layer, allowing limited adjacent-layer interactions without skipping layers. Closed architecture promotes decoupling by trading flexibility for encapsulation. In general, that is a better trade than the other way around.

Semi-Closed/Semi-Open Architecture

A semi-closed/semi-open architecture allows calling more than one layer down. It still does not allow calling up or sideways.

E.g. A Manager can call a ResourceAccess component if there is nothing to encapsulate in an Engine layer.

This method relaxes the closed architecture with some don'ts and do's, making sure encapsulation is not compromised at the cost of flexibility.

Flexibilities Allowed in 'The Method'

Calling Utilities

In a closed architecture, placing Utilities like Logging, Security, or Diagnostics is problematic because every component may need them. If Utilities are assigned to a specific layer, access becomes limited. To solve this, The Method introduces a vertical utility bar that cuts across all layers, making Utilities accessible to every component.
E.g. Logging, Authentication, Security

Litmus Test for Utility

To avoid the misuse of utilities by developers, there is a simple litmus test:
Can the component plausibly be used in any other system, such as a smart cappuccino machine?

For example, a smart cappuccino machine could use a Security service to check if the user is authorized to drink coffee. Similarly, it may want to log how much coffee office workers drink, run diagnostics, and publish events (e.g., running low on coffee) using a Pub/Sub service.

Each of these needs justifies encapsulation in a Utility service. In contrast, you’d be hard-pressed to explain why a cappuccino machine needs a mortgage interest calculation service as a Utility.

Calling ResourceAccess by Business Logic

Managers and Engines can access ResourceAccess since they’re in the same layer. There might be cases where Managers need to reach Resources even without using Engines.

Managers Calling Engines

Managers can call Engines directly because Engines serve as strategies within Manager workflows. This separation is more about design detail than architectural structure. These calls aren't lateral, as Engines function in a different dimension from Managers.

Queued Manager-to-Manager

While Managers should not call other Managers directly (i.e., sideways), a Manager can queue a call to another Manager.

Technically, the queue itself is a Resource, and the publisher acts like a ResourceAccess component. The queue listener is effectively another Client in the system, calling downward to the receiving Manager. No true sideways call actually takes place.

Design Don'ts

Treat any violation of these rules as a red flag and investigate further to see what you might be missing.

  1. A Client should not call multiple Managers for a single use case.
  2. A Client must not call Engines.
  3. Managers must not queue calls to more than one Manager in the same use case. The need to have two (or more) Managers respond to a queued call is a strong indication that more Managers (and maybe all of them) would need to respond — so you should use a Pub/Sub Utility service instead.
  4. Engines and ResourceAccess services do not receive queued calls.
  5. Clients, Engines, ResourceAccess, or Resource components do not publish events.
  6. Engines, ResourceAccess, and Resources do not subscribe to events. This must be done in a Client or a Manager.
  7. Engines never call each other.
  8. ResourceAccess services never call each other.

Updated Rules

Let's update the rules based on what we have discussed today. On top of that, I am adding an additional observation:

There may not be internal symmetry inside a component like a Manager, but strive toward symmetry in the overall design.

  • 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? What are different use cases? (Remember: Almost always, 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.
  • layers given in the template should correspond to 4 question:

    • Client : Who interacts withe system
  • Managers : What is required of the system

  • Engines : How system performs the business logic

  • ResourceAccess : How system access the resources

  • Resource : Where is the system state

  • Validate if your design follows the golden ratio (Manager:Engine). Some valid ratios: 1:(0/1), 2:1, 3:2, 5:3.

    More than 5 Managers? You’re likely going wrong.

  • Volatility decreases from top to bottom, while reusability increases from top to bottom.

  • Slices are subsystems. No more than 3 Managers in a subsystem.

  • Design iteratively, build incrementally.

  • Design Don'ts

    1. A Client should not call multiple Managers for a single use case.
    2. A Client must not call Engines.
    3. Managers must not queue calls to more than one Manager in the same use case. The need to have two (or more) Managers respond to a queued call is a strong indication that more Managers (and maybe all of them) would need to respond — so you should use a Pub/Sub Utility service instead.
    4. Engines and ResourceAccess services do not receive queued calls.
    5. Clients, Engines, ResourceAccess, or Resource components do not publish events.
    6. Engines, ResourceAccess, and Resources do not subscribe to events. This must be done in a Client or a Manager.
    7. Engines never call each other.
    8. ResourceAccess services never call each other.

Conclusion

Starting next week, we’ll begin exploring real-world examples of software design using The Method.
See you next Sunday!

Here are links to previous articles in case you missed them:

  1. Why Functional Decomposition Leads to Bad System Design
  2. System Design Basics: Why The Right Method Matters
  3. The Anti-Design Thinking in System Design
  4. Volatility-Based Decomposition: A System Design Example
  5. Principles of Volatility-Based Decomposition in System Design
  6. Template for System Design Using ‘The Method’
  7. Template for System Design Using ‘The Method’: Part II

Top comments (0)