Recently I have worked on removing a compile time dependency between two domains. What I have stumbled upon turned out to be a simple yet comprehensive example of a common problem that I believe many of us encounter every day. Below you can find what was the problem setting, why it important to get rid of such coupling and how we have accomplished it.
Each green rectangle is a compilation code package, each blue rectangle is a class or an interface, each arrow is a compile-time dependency between projects and the yellowish rectangle depicts a use case which spreads between domains.
The code corresponding with this diagram can be inspected in a github repository which should be explored together with this article. Bear in mind that there are two branches: before and after refactoring.
In order to fulfill use-case-related requirements, which is exposing aggregate public contract to outer world, one must wrap an Aggregate, which is a domain idea of Manufacturing using AggregatePublicContract class, because it does expose an interface of IAggregatePublicContract. (Let’s not dig deeper if there’s any other way, since it’s not a point of this example.)
Unfortunately for architecture, a tiny part of the use case, namely the conversion from aggregate to public contract, takes place in DataUpload part of the code. As a result, there’s tight compile-time coupling depicted with the bold red arrow on the diagram. Let’s take a look what it means from human point of view.
Using someone else’s code
Imagine two human teams, one working on Manufacturing and the other on DataUpload. Unless in an emergency, sharing private procedures between these teams:
- is not effective,
- requires both teams to communicate with each other often,
- exposes security issues,
- interrupt one teams work to help the other one,
- requires the team to learn said manual every time since it’s constantly changing.
Apart from that, I bet you can tell that such a procedure would be a nightmare in a day-to-day job. And that’s exactly why such problems, both in human teams and in software, are solved as follows. By the way, this is a message-based solution.
Steps to solve the issue
First of all, know your domains. There’s abundance of books on drawing boundaries in software architecture. It is not easy yet it is necessary to even find out that you can apply a refactoring like this. Keep in mind that dividing an application into domains should be based on business knowledge.
The core part is finding out why are two or more modules bound with compile-time reference. As far as I know the easiest way is to remove the dependency and let the compiler find out what’s missing. Once you know what does not compile, you know where to look for.
Afterwards, when the concern is already known, a wild question appears: to which part does a particular idea (or class or whatever) actually belong? Should the AggregatePublicContract class be placed in Manufacturing or DataUpload domain? Interestingly, sometimes it is reasonable to locate it in a wrong place just to break a dependency and come back to it later. In this case AggregatePublicContract has been moved to the domain of Manufacturing.
Finally, the technical part consists in shifting the code and thus the whole use case so it is confined within a single package (or domain), effectively removing the dependency. If you want to find out it has been done in this example, please make yourself familiar with the refactoring Pull Request, where I have left some clarifying comments.