How To Share Code In Modular Monolith? Supporting Module Integration Pattern

The Supporting Module Pattern reduces duplication by extracting shared functionality into a dedicated module. It promotes modularity, decoupling, and team independence, but introduces trade-offs like potential coupling and complexity. Apply only with a clear need and ownership model.

How To Share Code In Modular Monolith? Supporting Module Integration Pattern

The Supporting Module Pattern addresses the challenge of code duplication in modular monolith architectures by extracting common functionality into a dedicated supporting module. This pattern creates a shared module that provides cross-cutting concerns used by multiple feature modules, while maintaining clear boundaries and preventing tight coupling between core business modules.

🧱
This is one of the few overview articles about modular monolith architecture and integration patterns between modules. The rest can be found here.

A few rules are crucial to understand this article. You have to know that each pattern or approach is valid only in a specific context. You have to choose an approach suitable for your case, and it will never be ideal. It is always a trade-off.

1️⃣
The context is important.
2️⃣
No ideal solution, only trade-offs.
3️⃣
Architecture changes over time and evolves.

Shared operations, components (supporting module) Set of shareable operations or small functionalities that can be part of a larger feature.
💬
The Supporting Module Pattern is a term created by me. If you know DDD, you can see similarities to the supporting or subdomain domain concepts.

When to Apply

This pattern is appropriate when:

  • Multiple modules need common behavior that doesn't belong to any specific domain.
  • Feature teams need to remain independent without tight coupling between their modules. That is why the team is considering extracting atomic functionality to a separate module.
  • No single team wants ownership of the shared functionality within their module boundaries.
  • Synchronization and consistency are required across modules for specific operations (assuming working in the same memory space).
  • Cross-cutting concerns like logging, validation, or formatting are duplicated across modules.
  • Technical infrastructure needs to be shared without being module-specific.
💬
You need to have a good reason to use this pattern.
What Does Modularity Mean?
This article explores true modularity in software development, focusing on cohesion, managing dependencies, and using contracts for independence and scalability. It provides insights on organizing systems for improved flexibility, testability, and reusabi

Trade-offs

Advantages:

  • Reduces code duplication across feature modules.
  • Promotes consistency in cross-cutting concerns and shared utilities.
  • Enables independent team development while sharing common functionality.
  • Simplifies maintenance of shared components in a single location.
  • Improves system coherence by centralizing common patterns.

Disadvantages:

  • Creates coupling between modules and the supporting module (potential risk).
  • Can become a bottleneck if not properly designed or maintained.
  • May accumulate unrelated functionality over time without clear boundaries.
  • Requires careful change management to avoid breaking dependent modules.
  • Requires quality tests. The more consumers there are, the more important this requirement becomes.
  • Adds complexity in terms of versioning and deployment coordination.

When to Reconsider

Reconsider using this pattern when:

  • Only one module actually uses the shared functionality.
  • The shared functionality is evolving rapidly and changes frequently.
  • The supporting module is becoming too large or unfocused.
  • Team ownership of the supporting module is unclear or disputed.
  • Deployment dependencies are causing significant friction between teams.

Implementation Guidelines

Supporting module could contain:

  • Cross-cutting utilities (logging, validation, formatting).
  • Common data transformations used by multiple domains (without any other specific domain details).
  • Shared integration adapters (external API clients, messaging utilities).
  • Technical infrastructure that isn't domain-specific.
  • Instead of direct access to the Supporting Module functionalities, you could be use a separate public contract which implementation is implementation detail of Supporting Module (have a look at the diagram down below).

Extended implementation of access to Supporting Module by exposing functionalities by dedicated contract project.

Access to Supporting Module by public contract

A natural consequence of the pattern shown in the diagram is loose coupling between the Supporting Module and its client modules. This is achieved by introducing a public contract (interface or abstraction layer) that prevents direct dependencies on the concrete Supporting Module implementation.

Rather than modules directly referencing the Supporting Module, they depend on the contract interface. An Inversion of Control (IoC) container manages the dependency resolution, binding the contract to the specific Supporting Module implementation at runtime. This architectural approach ensures that client modules remain decoupled from the implementation details while still benefiting from shared functionality.

This approach enables easier testing through contract mocking and substitution.

What Should Stay Out

At all costs, avoid placing these in supporting modules:

  • Business logic specific to any domain.
  • Domain entities or value objects.
  • Complex workflows that might evolve independently.
  • Features with unclear ownership or governance.

Ownership and Maintaining

Establish clear ownership of the supporting module:

  • Designate which team maintains and evolves the supporting module.
  • Define how breaking changes are communicated and managed (consider versioning of public contract).
  • Team needs to be aware to not add specific module-bias changes to the supporting module.
What is Upstream and Downstream in Software Development?
Wondering about what upstream and downstream means in the context of software development? This articles discusses several usages of these words and defines two simple rules to identify what is upstream and what is downstream in every context.

The Upstream and Downstream concept is crucial to understand the proper way to decide where to put a potential shared logic.