Law of Demeter, Gateway Pattern, and Package Maturity

Some guidelines for dependency rules that make a system easier to understand and maintain.

Date

Dec 22, 2022

gateway pattern

How vs What?

Last week, we published a blog post on how you can create dependency rules between your packages. This post encouraged intriguing conversations, among others, in this HackerNews thread. These conversations mainly focused on what dependency rules make sense for many projects. In this post, we'll dive into some of those ideas and why they might make sense for your projects.

Many thanks to w_t_payne and hbrn for their input in the HackerNews discussion. 😃

We'll present 3 different guidelines:

  • Law of Demeter: Packages should talk only to their "direct neighbors".
  • Gateway pattern: Ensure that only a dedicated package of your software communicates with an external dependency.
  • Package maturity: A mature package shouldn't depend on a less mature package

Law of Demeter

The Law of Demeter was proposed by Ian Holland at Northeastern University in 1987. It states that an object should only communicate with its immediate neighbors. It shouldn't depend on the inner workings of other, "more distant" objects.

The Law of Demeter suggests how objects in an OOP system should communicate. But it makes sense on a higher level, between the packages of a system as well.

Law of Demeter

Some benefits of the Law of Demeter:

  • The system is less complex and more understandable because of the limited ways of communication and the lower number of internal dependencies.
  • Debugging is more straightforward in a system following clear communication rules. (See also the Principle of least astonishment)
  • It promotes the creation of modular, self-contained objects that are easier to understand, maintain, and extend.

Self-composed packages with few dependencies have a further advantage: They are more handy to move around. That becomes relevant if you want to reuse the package at other places of the system. Or perhaps even extract it into its own library or service. (In Building Microservices, Sam Newman provides some great insight into both whether and how to start splitting a monolith into smaller parts.)

Gateway Pattern

The Gateway Pattern was first described by Martin Fowler in the book Patterns of Enterprise Application Architecture in 2002. His definition: "An object that encapsulates access to an external system or resource".

gateway pattern

Using a gateway for external libraries provides several benefits:

  • Your code can use its own vocabulary when communicating with the external service.
  • Clear separation of the core domain and supporting services.
  • Replacing the external dependency becomes more straightforward.
  • More flexible testing possibilities. You can mock out the gateway's connection object or the whole gateway.

On his homepage, Fowler writes that "this pattern is widely used (but should be more prevalent)". He also gives a great guideline on when to use it:

I use a gateway whenever I access some external software and there is any awkwardness in that external element. Rather than let the awkwardness spread through my code, I contain to a single place in the gateway.

Package Maturity

This suggestion is from w_t_payne. They describe a cool system:

  • Each file has some metadata stored in a yaml comment.
  • Each file has its own maturity level.
  • The build system processes this metadata and takes some decisions based on it: Which checks are necessary, which dependencies are allowed etc.

Even if you don't have such a sophisticated system, considering the maturity level of both internal and external packages makes a lot of sense. Some questions to ask:

  • What are the crucial parts of this application?
  • Which packages and modules are involved in the most important user workflows?
  • What will happen if this code fails?

The answers may be more obvious if your system is split into several small parts, perhaps even repositories. If you have a big monorepo, you'll probably find some modules or packages which are surprisingly difficult to categorize.

Thinking in maturity levels is surprisingly helpful with the classical dilemma: Should I try out that cool & new library? Or should I be consistent and stick to libraries that pass the well-maintained test with flying colors?

For the code that processes thousands of transactions every hour, the shiny new toy might be too risky indeed. For the internal tool automating a part of your build, it might be an excellent choice.

w_t_payne points out how being explicit about maturity levels helps innovation and experiments:

It also means that there's more freedom in changing and adapting peripheral designs as you can have confidence that its stability is not something that is going to be relied upon.

Conclusion

Having clear boundaries and communication lines between your packages makes your system much easier to comprehend and maintain. Where these boundaries should be, depends highly on your project's domain.

However, there are some general guidelines on how parts of the system should communicate. The Law of Demeter and the Gateway Pattern are worth to follow in the majority of projects.

Check out our post Maintain A Clean Architecture With Dependency Rules and start creating dependency rules for your project in 3 steps:

  1. Visualize the optimal flow of communication between packages.
  2. Phrase some rules (in English) about the dependencies between those packages based on the visual.
  3. Translate the rules into code so that we can check how much our application follows the optimal architecture.

References

  • 📖 Evans, Eric: Domain-Driven Design: Tackling Complexity in the Heart of Software August 2003, Addison-Wesley Professional
  • Fowler, Martin: Gateway
  • 📖 Fowler, Martin: Patterns of Enterprise Application Architecture November 2002, Addison-Wesley Professional
  • Johnson, Adam: The Well-Maintained Test: 12 Questions for New Dependencies
  • 📖 Newman, Sam: Building Microservices, 2nd Edition August 2021, O'Reilly Media, Inc. Chapter 3: Splitting the Monolith
  • the-well-maintained-test GitHub PyPI