View More

The first pattern of domain driven design (DDD) I would like to take a closer look at is the “Entity” tactical design pattern. We all know the term “entity” and may have a certain picture in our mind when we read it. I was first introduced to it while learning about entity relationship diagrams that are used to design databases. However, in DDD an entity is an element of the domain model, a kind of domain object.

The domain model entity element has distinct characteristics which separate it from value objects, another pattern that DDD uses to model domain objects. The main differences are that entities have an identity which stays the same during their lifecycle and that they are mutable whereas value objects are immutable.

The Pattern

An entity is a domain model element that represents some domain object. It has attributes and methods. For example, a ship that has a certain name and capacity.

One of the main characteristics of an entity is that it has an identity. The identity never changes through the life cycle of an entity. An example of this is a person who has a certain name. When the name changes if that person is married for example, the identity has to be preserved.

When comparing entities with each other, we cannot rely on their attributes. Two person objects with the same name value don’t necessarily have to represent the same Person. On the other hand, consider a contact detail object with an attribute phone number. This object doesn’t have an identity on its own, every contact detail object with the same phone number could replace it with no change in meaning.

Entities have a life cycle which starts when they are created. They can be loaded from a persistence store, changed due to an event or action and then stored to persistence again. At the end of their lifecycle, they are destroyed. For example, a car is built which starts its lifecycle. Then after some years in use, some parts are replaced. Finally, when we take the car to the scrap yard where they dismantle it, its lifecycle ends.

Commands and Queries

Methods of entities are either commands or queries. Commands are used to change the state of the entity, for example set an attribute to a new value. They never return state. In contrast, query methods should never change the state of an entity but return some value.

This distinction makes it easier to reason about our code. We know that a query method doesn’t change anything. When calling a command, we know that we may better carefully inspect what it does exactly.

Validation of invariants

The entity pattern advises us to model the invariants of an entity. Invariants are facts or rules that are always true after a command made a change to an entity. For example, a car needs to have always exactly four wheels. So after the command that changes the wheel of a car, we need to check this invariant.

Therefore after each command or series of commands that we execute on the entity we need to validate it. Even when factories create new entities, they should be in a valid state from the beginning of their life cycle.

Associations

Eric Evans also discusses associations in his DDD book [Eva04]. Many associations are bi-directional in nature, and therefore we model them as such initially. If we translate those directly into the software, the model becomes very entangled and hard to maintain. Therefore we should strive to minimize associations within the model and code.

Three ways to reduce associations are imposing a traversal direction, reducing multiplicity by introducing a qualifier and removing the association altogether. The following sections describe a sample of each possibility.

Impose direction

Imagine my favorite sample of the webshop. There are baskets to which users add products. The association between baskets and products is many-to-many. Does this imply that we need to maintain a list of baskets inside the product entity?

Most of the time it should suffice to be able to query the basket for its products. A product doesn’t have to know in which baskets it is stored. That’s a question the application may never need to answer. If it does, we can query the repository (database) for the answer.

If we needed to maintain both directions within the model, every addition of a product to the basket would need another operation that adds the basket to the list of baskets of the product. By imposing the direction, we can simplify the code.

Reducing Multiplicity

The following diagram shows the association between student and subject. It is a many-to-many association because students study multiple subjects and subjects are studied by multiple students.

To reduce the multiplicity we could, for example, add time slots to the model. At a given timeslot a student can only study one subject. Therefore the multiplicity is reduced to one-to-many.
This association is much easier to maintain in the code of the model.

Removing associations

The ultimate simplification is removing associations altogether. When the association is purely informative for example, or when it is used only in a given context we may better remove it. For example, the application for a publishing company may records reviewers of a book during the publishing process. However, once the book is printed and added to the store of the company, there is no need for the association to the reviewers anymore.

Wrap Up / Final Thoughts

The entity pattern defines domain entities which can be modeled and translated to code. It divides methods into commands and queries with clearly defined semantics. Entities maintain a lifecycle, and we can minimize their associations in multiple ways.

The next pattern in the series will be value objects. Generally, they should be preferred over entities when modeling. Let’s see why in the next post.

[Eva04] Eric Evans: Domain-Driven Design – Tackling Complexity in the Heart of Software (homepage)

View More

Welcome to this blog post series about concepts and patterns of domain driven design (DDD). Although the original book about DDD [Eva04] was written almost 15 years ago, I only recently stumbled upon an article about it on the internet [Sch17]. This read drove my interest in DDD, and I started to dig deeper, read the original book, watched some conference recordings and started to experiment.

In my opinion, DDD captures the common sense of software design and makes it explicit. I’ve seen many advanced software engineers implement the concepts and patterns of DDD which have never heard of Eric Evans and his book. That’s why I would like help to spread the word and write this series.

This posts shall give an overview of what DDD is all about. The further posts of the series will dig deeper into the concepts and patterns.

Introduction to DDD

Eric Evans does a great job defining DDD at his keynote speech at the explore DDD 2017 conference. I highly recommend you watch it. On the first slide he defines four fundamental concepts of DDD as follows:

  • Focus on the core complexity and opportunity in the domain
  • Explore models in a collaboration of domain experts and software experts
  • Write software that expresses those models explicitly
  • Speak a ubiquitous language within a bounded context

The first point is about putting the domain in the center of software and focus on it. Everything else should be built around the domain and may be replaced without affecting the core functionality. Different people have different names for an architecture like this for example “Clean Architecture”, “Onion Architecture” or “Hexagonal Architecture”. The core idea is always the same: Use dependency inversion to put the domain at the center.

The second point is about requirements and how we best gather them. It states that we should explore the models with domain experts. This advice aligns nicely with agile methodologies that recommend close interaction with the business (the domain experts).

Once we know the domain model what do we do with it? In DDD we implement it directly in code. This is possible because DDD defines patterns to build our domain model which can be implemented in code too. The model can then be used by higher-level controllers to execute use cases.

The last concept is: Speak a ubiquitous language within a bounded context. The same language should be used through conversations, the domain model and in code. The ubiquitous language allows us to communicate effectively and prevents misunderstandings. The strategic pattern “Bounded context” aims to divide software along the domain boundaries. Ubiquitous language is always valid within a given context.

The Patterns

Basic DDD patterns can be divided into two kinds of patterns: tactical and strategic. Tactical patterns are used while modeling the domain and in code. Strategic patterns are more high level and are used to structure the software on an architectural level. We will go into the details of each pattern through the series. Therefore I list them only very briefly here.

Tactical Patterns (or building blocks in original DDD terms):

  • Entity – Similar to objects in classic object-oriented design but with a focus on identity over time.
  • Value Object – Immutable entity identified by its attributes.
  • Factory – Super pattern for some of the GoF creational patterns.
  • Service – Stateless object which encapsulates domain logic that cannot reasonably put on an entity or value object.
  • Aggregate – Encapsulates multiple entities and value objects.
  • Repository – Library for obtaining access to aggregates.
  • Module – Structure objects on a higher level to reduce cognitive overload.

Strategic patterns and concepts:

  • Bounded context – Define boundaries around domain areas and reflect them in software.
  • Context Map – Model of the different bounded contexts of the system.
  • Model Integrity Patterns – Ways to model the connections between multiple bounded contexts.
  • Core Domain – Find the part of your model where the most business value lies and distill it into the highest priority context.

Supple Design

Supple design is the concept of designing the software in a way that feels natural when developing and maintaining it. The following patterns give some advice on how to achieve this property of software.

  • Intention-revealing interfaces – Give the elements of software meaningful names, use the ubiquitous language
  • Side-effect-free functions – Prefer functions that return results instead of manipulating some state
  • Assertions – State post-conditions of operations and invariants of classes explicitly
  • Conceptual Contours – Decompose elements of software into cohesive units
  • Standalone Classes – Minimize dependencies to reduce the mental load
  • Closure of operations – Where possible operations should return the same type as their argument(s)
  • Declarative design – If possible use a declarative design/program style

Don’t worry if these very short descriptions leave some question marks in your head. These are concepts that cannot simply be explained in one or two sentences. I will try to explain them in the further posts of these series. Meanwhile, if you are curious, see this article for a bit more information.

Distillation of the Core

Distillation of the core domain enables us to focus on the most valuable part of our domain model. Evans touches multiple patterns or strategies of how to approach the distillation.

  • Generic Subdomains – Extract subdomains that are not the main reason you build the system
  • Domain Vision Statement – High-level vision of the system that helps to define the core domain
  • Highlighted Core – Define which parts of model belong to the core but without extracting it (yet)
  • Cohesive Mechanisms – Identify parts of the model that form a general know mechanisms
  • Segregated Core – Remove supporting functionality from core objects
  • Abstract Core – Aim for abstract core concepts that specialized subdomains can use

Large-Scale Structure

Large-scale structure concepts organize the system on a component or layer level. It guides developers on where to find functionality and where to place a new one.

The large-scale structure is not the same as classic architecture. It is driven by the domain and should, therefore, be based on domain concepts rather than technical concerns.

  • Evolving order – Let the large-scale structure evolve over time
  • System metaphor – Look for an overarching metaphor of the system and make it explicit
  • Responsibility Layers – Organize the domain model across multiple layers
  • Knowledge level – Allow configuration of core operations from a knowledge level
  • Pluggable component framework – Abstract core with a plugin infrastructure

Wrap up / Final Thoughts

It seems that DDD consists of many concepts and patterns. However, don’t be scared, many of the overlap and together form a domain-centric way of thinking about software design.

When we take a look at our profession as a whole, DDD is concerned only with a small part of our responsibilities. Recently I read a blog post by Bob Martin where he reviews the XP process. He writes that metaphor in XP became DDD. The metaphor is only one of twelve practices in XP.

[Eva04] Eric Evans: Domain-Driven Design – Tackling Complexity in the Heart of Software (homepage)

[Sch17] M. Schimak: Domain-Driven Design? Na klar, das „blaue Buch“! – Mit „DDD“ (Domain-Driven- Design) zur gemeinsamen Sprache für Business und IT (http://www.sigs.de/public/os/2017/03/Schimak_OS_03_17_hdtz.pdf)

View More

Continuing the series about modularization patterns with Java 9 today I would like to introduce the patterns Default Implementation and Module Facade [Kno12]. As its name suggests, the Default Implementation pattern is about providing implementations for abstract modules but also default configuration. Facades are a well-known pattern from [GoF] which can be applied to modules too as we will see.

The Problem

When we try to make modules flexible, we create abstract modules that separate implementations from abstractions and configurations. These now flexible modules are harder to use because users need multiple modules (abstraction and implementation) or external configuration.

Even if there is an implementation provided for a given abstraction, users should not depend on it. We need some mechanism that wires the implementation to the user.

The Solution

The solution to the above problem is to provide default implementations of abstractions and default configuration settings. We deploy default configurations normally within the same module, default implementations can be deployed within the same module or in a separate module.

We should also provide means to obtain default implementations without requiring users to depend on them directly. With the java platform module system (JPMS) we can use services to obtain default implementations as can be seen in the following sample.

Default Implementation Sample

The sales module of our ongoing webshop sample provides an API to apply a discount to the sales. Different implementations are provided by other modules that calculate a discount based on various criteria. In many use cases, however, there won’t be a discount applied. The customer needs to pay the full amount.

As you can see, the sales module provides a default implementation of the discount calculator. It covers the case where no discount needs to be calculated. From the three services that use the sales module, two provide an implementation of the discount calculator and one doesn’t. Another case that I don’t show here and which doesn’t provide an implementation could be the test module.

Without further actions, users need to create this default implementation themselves and therefore have a dependency on it. To prevent this undesired dependency, we have at least two options: Add a factory [GoF] or provide the discount calculator as a service. Because the factory pattern is already well-known, I  choose the service option for this sample.

The following module descriptor of the sales module declares a uses dependency on the discount calculator. At the same time, it provides the default implementation as a service.

The default implementation will be used as long as no other module provides one with a higher priority. When a new requirement arises to provide a different implementation of the discount calculator, it can be implemented in a different module and plugged in the application without changing the sales module.

Default Configuration

In this post, we saw the external configuration pattern which enhances the flexibility of the module. The sample was about configuring the URL of a REST call for different environments. Every developer that wants to use that module needs to provide a configuration for it which makes it harder to use.

To improve the situation, we could add a default configuration inside the module that points at some localhost URL. Then the developers would not need to provide a new configuration each time they want to reuse the module.

Module Façade Pattern

The Module Façade pattern [Kno12] seeks to improve the usability of multiple smaller modules. When we focus on flexibility and reuse, we tend to create many small modules. Those small modules become harder to use because of their fine granularity.

A module façade provides a common API for multiple smaller modules. It may also contain configuration for them and even add functionality itself.

Sample Facade

Suppose we want to manage different product categories as independent modules. For example, we could have a home electronics module and a pc module. They both share common functionality of the product base module but add their own search and filter options.

To ease the use of those product category modules, we create a product module as a façade which includes the home electronics, pc and base modules. Users now only need to add a dependency on the façade without worrying about the different categories. When more categories are added, users automatically get access to them too without needing to modify their dependencies.

Wrap Up / Final Thoughts

When we want to use a module, it should work out of the box. If we need to find an implementation of an abstract module or need to add multiple dependencies to make it work, we are distracted from our actual task. If we design modules according to the two usability patterns default implementation and module façade, live for the users of our modules (which could be ourselves too) is greatly simplified.

[Kno12] K. Knoernschield: Java Application Architecture – Modularity Patterns with Examples Using OSGi (homepage)

[GoF] Design Patterns: Elements of Reusable Object-Oriented Software (wiki)

View More

In this post, we look at two usability patterns: Published Interface and External Configuration. Usability patterns help to integrate modules with each other and generally simplify development with modules.

When working with a module, the developer needs to know how he is supposed to use it. The published interface pattern acknowledges this and focuses on a clearly defined API for modules. Another problem that we may face when using modules and especially re-using them is that we may need to change some configuration values. To allow this without recompilation of the module configuration can be located outside of the module as the external configuration pattern proposes.

Published Interface

The published interface pattern [Kno12] states that one should make the API of a module well known. What is part of the public API of the module and equally (or even more) important what is not? It is important to make this obvious for anyone using your modules. If we don’t, users (like ourselves next month) will certainly access elements of the module that we did not intend to. Afterward, we cannot change such elements easily without breaking code that uses the module.

Before the java module system, it was impossible to enforce usage of the public API. With the module system, we can publish it and prevent usage of elements by only exporting the required packages and providing services.

The Sample

Continuing the webshop sample let’s have a look at the customer component before refactoring it to modules. It contains implementations for consumers and business customers. They both implement a shared interface and users should use the factory to create them. The files are organized as follows:

Without the module system, it was possible for users to create the implementation types directly without using the factory. They could access the implementation types directly. Therefore, any change to these classes could have a big impact on users of the component.

Now we refactor to a module by adding the following module descriptor:

It is now impossible for users of our module to use the implementation classes directly. We have not only published the interface by explicitly stating exported packages but also prevent access to the others.

External Configuration

Another usability pattern introduced by [Kno12] is external configuration. It states that we should make modules externally configurable. When we create modules that are configurable the chances that they can be reused are increased.

Sample

In many projects, there are multiple stages or environments involved when deploying an application. Maybe there should be at least two, one for testing and the production. To deploy a module to multiple environments it needs to be configurable. For example, the sales module from the webshop application posts an event to a REST endpoint when a sale was completed. It, therefore, needs to know the URI of the endpoint which is different for each environment. We configure the URI externally through a properties file which we deploy next to the module JAR file.

With this setup, we can provide a different settings file for each environment. It probably would be easier to store the properties files inside the module, but then we would need to rebuild it for each environment.

Wrap Up / Final Thoughts

In this post, we saw two usability patterns that simplify usage and maintainability of modules. With the published interface we can ensure that our modules are used as intended. The external configuration allows for reusable modules that can be configured for different environments or even use cases.

[Kno12] K. Knoernschield: Java Application Architecture – Modularity Patterns with Examples Using OSGi (homepage)

View More

The next pattern that I would like to discuss is called “Independent Deployment” [Kno12]. It states that modules should be independently deployable units. For a module to be independently deployable, it cannot have any outgoing dependencies on other modules. In most cases, this is unrealistic, but the dependencies can be minimized.

Why should modules be independently deployable? If we want to reuse a module, every outgoing dependency is needed too. Therefore if we minimize outgoing dependencies, the module is going to be simpler to reuse.

Some dependencies are “lighter” than others and are preferable to “heavier” ones. For example, abstract classes and interfaces are light because they have a lower rate of change than their concrete implementations. A dependency on implementation can be avoided by defining an abstraction and use a wiring technic to get access to the implementation. With the java platform module system (JPMS) we can use services to implement the wiring.

Sample code for this post can be found on GitHub.

The Sample

In the first part of this series, I introduced the web shop as a sample of an application that we would like to modularize. Now we have the following modules in place.

The inventory module depends on baskets to calculate availability. Now imagine if we would like to reuse the inventory module in an application for inventory management where there are no sales with baskets going on. With this dependency in place, we would need to bring along the baskets module too, but there would never be any baskets.

The Implementation

The solution is to define an abstraction for availability adjustments. We create a new interface called AvailabilityAdjuster in the inventory module which is implemented by the basket module. Then we need to wire the implementation from the basket module to the inventory using a JPMS service. The inventory module now only depends on the abstract service interface which could be deployed within the inventory module itself or in a separate module.

This design allows us to deploy the inventory module independently. It is a bit more complex than the previous that allowed direct usage of the baskets module through. That’s a typical tension that we encounter in module design: To get the benefits of reusability, the use of some modules maybe gets harder. Therefore, I would aim for the simpler solution first. When the need arises to reuse some module, there is always the possibility to refactor.

Another Option

The sample above uses a callback to break (or invert actually) the dependency from the inventory to the baskets module. There is another possibility that may be better in some situations. We could implement escalation to break the dependency. As you may remember from the first part of this series the availability is used by the sales module that displays it to the user. If we move the responsibility to adjust availability according to the contents of the baskets to the sales module the dependency from inventory to baskets can be removed.

With this design, we move functionality to another module. The internal inventory system could implement the same concept of availability through, may be based on what is going to be delivered through the day. Then it may be undesirable to implement two solutions.

Wrap Up / Final Thoughts

Modules should simplify reuse of code. Therefore it should be possible to take individual modules and use them independently. That’s the goal of the independent deployment pattern. It advises how to design or adjust modules to make them reusable.

The JPMS enables us to remove dependencies by converting them into services which can be made optional to use or provided by a simple implementation. In our example, if the inventory system defines no availability adjusters, we can ignore this functionality and still reuse the module.

[Kno12] K. Knoernschield: Java Application Architecture – Modularity Patterns with Examples Using OSGi (homepage)

View More

While developing software in Java and other languages, we often create abstractions and separate them from the implementation. This practice has many benefits that we would like to take advantage of while designing modules. The Abstract Module and Separate Abstraction patterns from [Kno12] and API Modules from [Sand&Bak17] deal with abstractions in the context of modules.

Sample code for this post can be found on GitHub.

Abstract Module

The Abstract Module pattern states that users of modules should depend on abstractions, not implementations. When we depend on interfaces or abstract classes the implementation can change without affecting us. It may even be replaced completely by the maintainer of the module. Systems composed of modules that depend on abstractions of each other are flexible and easier to extend.

Sample

You may have come across the sample of payment methods in one form or another already. Let’s see how it looks like with Java 9 modules. If we have a webshop when we check out and complete a sale, there are multiple ways to pay. A first version of the checkout module depends directly on a credit card module and PayPal module as shown below.

As you probably imagine if we add new payment methods the checkout module needs to depend on them too. These dependencies introduce high coupling which we can avoid.

The Refactoring

To prevent coupling the checkout module to every payment method implementation we introduce a payment method abstraction. Now the modules containing payment methods depend on the checkout module.

To make the payment methods usable from within the checkout module, we design the payment method abstraction as JPMS service with the interface PaymentMethod. The payment method modules provide this service with their implementation. Finally the opus.checkout module declares that it will use the payment method services as can be seen in the simplified module descriptors below.

If we need to add more payment options, they can now easily be used by the checkout system without affecting its dependencies. It is also possible for the different implementers of payment methods to change without affecting (rebuild, deploy) the checkout module.

API Modules

The API Modules pattern [Sand&Bak17] states that we should design the API of a module carefully and create dedicated API modules. The API of a module consists of all parts that are exported from it. We should make it as small as possible to gain resilient modules.

Another Refactoring

To apply the API Modules pattern we extend the previous sample with a separate API module called opus.paymentmethod. It contains only the interfaces for using the different payment methods and exports them.

By using the JPMS service infrastructure, we don’t even need to export anything from the implementers. They have zero API and therefore no incoming dependency which allows us to change them without affecting any other module.

The API Modules pattern also states that most parts of the API should be abstractions. This statement aligns well with the goal of the Abstract Module pattern (depend on abstractions) discussed earlier. The API Modules pattern, therefore, can be seen as the inversion of the Abstract Module pattern. If modules only export abstractions other modules can depend only on them and not the implementations.

Separate Abstractions

The third pattern I would like to introduce is Separate Abstractions [Kno12]. It states that we should place abstractions and implementations in separate modules. If they live in the same module, they cannot change independently. Also, an implementation may require additional modules to provide its functionality which are not relevant for the abstraction.

Sample

Suppose our first design introduced an abstraction but located implementations in the same module. This module needs to depend on everything the implementations need like the opus.visaconnector. By refactoring the credit card implementation into its own module, we could remove this dependency from the module with the abstraction (opus.checkout). If there are many different implementations with multiple dependencies, extracting those implementations, helps to keep the modules clean an cohesive.

Where should abstractions live then? If there is only one user of the abstraction, we could place it in the same module. Otherwise, we create a separate module on which multiple users can depend.

The implementations live in their own module. This module needs to depend on the abstraction which could be problematic when it is located in the same module as the user of it. To resolve this problem, we could create a separate abstract module even if there is only one user of it. We applied this already in the second refactoring of the payment method sample.

Wrap Up / Final Thoughts

Abstractions are an integral part of software development. With modules, multiple questions arise regarding when to create them and where they should live. The patterns described in this post should help you answer those questions.

There is always a tension between (re-)usability and flexibility when designing modules. Modules that export abstractions and provide implementations too are easier to use. API only modules that separate abstractions from implementation are more flexible and have fewer dependencies but can be harder to use.

[Kno12] K. Knoernschield: Java Application Architecture – Modularity Patterns with Examples Using OSGi (homepage)
[Sand&Bak17] Sander Mak & Paul Bakker: Java 9 Modularity – Patterns and practices for developing maintainable applications (homepage)

View More

Modularization is the process of organizing code into cohesive units. This process is sometimes tedious and hard to do consistently. Patterns can help to make modularization simpler by providing solutions for some common problems. [Kno12] and [Sand&Bak17] are two books which describe patterns that are concerned with modularity. In this blog post series, I am going to show you how they can be applied using the Java Platform Module System (JPMS).

Sample code for this post can be found on GitHub.

The Pattern: Acyclic Dependencies

Dependencies between modules should never form a cycle. Different kinds of cycles include direct dependencies as well as indirect dependencies over three or more modules. In the diagram below there are two cycles, one between module 1 and 2 and the other indirectly over module 3.


When refactoring existing code to modules, it is often the case that you discover cyclic dependencies between packages that you would like to separate in different modules. However, when you refactor towards acyclic dependencies usability and reuse of the code is improved as you will see in the example below.

Implementation

The simplest way to avoid cycles is to remove dependencies altogether. So before applying any of the methods to break cycles think through the involved dependencies. Are they absolutely required or is there a way to remove them?

If they are required, there are multiple ways to break a cycle in your module dependency graph. These are escalation, demotion, and call-back. The following sections show an example of each of these methods.

The sample

Imagine a web shop application that we want to modularize. It has three components: Product, Inventory, and Sale. When a product is displayed to the user during a session, the sales page checks the inventory for its availability. According to a business requirement, when there are multiple sales going on in parallel, we need to ensure that the availability is always guaranteed. Therefore, the inventory needs to take the baskets of these sales into account when calculating the availability. Baskets are located in the sale component. These dependencies form a cycle between the sale and inventory components as shown below.

The Refactoring

When we refactor the components to modules without change we end up with the following module descriptors:

This code fails to compile because of the cycle. We need to break it by using one of the following methods.

Escalation

The first method is to use escalation. We design an additional module called sale process. This module has a dependency on both the inventory and sale module. When the system shows a product to the user, the sale process is responsible for calculating the availability. It asks the inventory for the stock count and checks the baskets from the sale module. With this change, we can remove the dependencies between sale and inventory altogether.

Demotion

Another way to break the cycle is to create a new module called basket. Both the sale and the inventory module depend on the basket module. We ask the inventory for the availability like before. The inventory, in turn, checks the baskets for ongoing sales.

This is called demotion. We demote functionality to a module on a lower level.

Callback

The third method to break cycles is to use a callback. To implement it we need a new JPMS service called OngoingSalesCalculator with a method ongoingSalesCountFor(Product product). This interface is located inside the inventory module (or a separate module) and implemented within the sale module. The inventory uses the callback to calculate the availability now instead of checking the baskets directly.

With this callback in place, we can remove the dependency from the inventory module on the sales module. By using a JPMS service, we don’t need a main class or a framework to do the wiring. We obtain the callback service directly from the service loader inside the inventory module.

When looking at the above descriptors, it may seem that there is still a cycle between those modules. There is actually one but only at runtime. The inventory module does not depend on the sales module during development and compile time but uses the calculator service only at runtime.

Wrap Up / Final Thoughts

When an existing code base is refactored into modules, the resulting dependency graph may contain cycles. These not only prevent code reuse but make it harder to think about the code and change it. There are multiple ways to break cycles, three of which we have seen in this post. When we break the cycles, individual modules can be reused and should be simpler to maintain.

The next part of this series about modularity patterns will take a look at abstractions in the context of modules. We will discover three patterns which help us design resilient modules: Abstract Module, Separate Abstraction and API Modules.

[Kno12] K. Knoernschield: Java Application Architecture – Modularity Patterns with Examples Using OSGi (homepage)
[Sand&Bak17] Sander Mak & Paul Bakker: Java 9 Modularity – Patterns and practices for developing maintainable applications (homepage)