1👍
Hypothesis
I’ll make an hypothesis for my answer that you are trying to implement some DDD boundary separation within a monolithic application, which implies you have a single monolithic persistence store (a single RDBMS or NoSQL database store) and you are not yet moving towards a microservice architecture (with separate API and persistence backend per bounded context).
The purpose of the domain layer
I’d like to express a few facts about the purpose of the domain layer in DDD.
The domain layer is where you implement code that validates that you do not violate some invariant business rules. The domain layer is useful when changing your application state, but if all state changing operations go through the domain layer, no read-only operation can violate a business rule. So calling the domain layer from read-only use cases is a waste of resources.
When changing the state of an entity, you don’t need to check for every invariants of every entities in your model. This is why the domain layer is split into small chucks of business models, called bounded context. When changing an entity state, you only check invariants that are susceptible to get violated by the change, within the bounded context. Sometimes you need to check some invariants accross multiple contexts but this has to be an exception as it will require some complex multi-phase commit patterns.
Now onto your questions.
Should you import GetResourceInterface
Your current implementation is to import an external class that reads the module A context for data. Now it is unclear to me as to whether this class returns domain layer objects or presentation layer objects. Though you specify that sometimes you are publishing your aggregates as DTO. You can read my answer here on why this can cause problems. Briefly:
The domain model of the resource is tailored to ensure no business rule is violated when changing the resource state. It is not tailored for read only operations, either within nor outside of the resource owning bounded context. This is why it is recommended that you separate your resource read methods in two different classes:
- A repository (interface domain layer, implementation persitence layer), reads the resource state for state-chaging operations, such as HTTP PATCH requests. It returns domain objects that you can mutate, check for business rules, then persist back to the store.
- A mediator (interface presentation layer, implementation persistence layer), reads the resource state for read-only operation, such as HTTP GET request. It directly maps the database to a DTO without projecting to the domain layer, as you don’t need to check for business invariants when querying your state.
Then instead of importing your generic GetResourceInterface
from other modules, you can at least import the mediator. The drawback of this approach is that you create a dependency from the domain layer towards the presentation layer, which might cause circular dependency or other problems. Also, changing the presentation interface of your resource could break your other modules domain layers, which is a risk you might not want to take.
This is why the most viable approach in your situation is to have a different class, in moduleB
, that reads the state of the resource directly from the database and returns a moduleB specific representation of the resource state. This model can differ both from the domain model and the DTO used in moduleA
. Having different representations, in the business layer, of the same concept for each bounded context is actually the recommended way to implement bounded contexts and is called polysemic domain model. Beware thouh, that you are creating a dependency between moduleB domain model and the persistence schema of the resource. This requires you to restrain to bounded contexts sharing the same persistence infrastructure and maintained by the same team.
Should you call another module API via HTTP
Short answer: never ever try to do that.
You are putting additional stress on your network infrastructure. Whenever you want to shift towards a more scalable microservice architecture you’ll be having a synchronous dependency between two services. Instead of multiplying the system’s availability you’ll divide it, as your service B will be unable to process request whenever service A falls. Your infrastructure will crumble like a game of dominoes, because service B will eventually have some dependents that will also stop working whenever service A crashes. And so on …
How to move towards a microservice
As stated, the solution presented above is only valid when the different modules share the same persistence backend and are maintained by a single team with good internal communication. However it will inevitably come a time when changing a persistence schema for the resource will not be correctly propagated to other bounded context or you’ll want to go towards a microservice architecture with each bounded context having their own dedicared persistence backend. moduleA
and moduleB
will then become serviceA
and serviceB
.
The solution to this is to extend the concept of polysemic to the whole application stack. Have a representation of the resource in serviceB
persistence store. This representation can be simpler than the one you have in serviceA
because some details are only useful when changing the resource state. For instance, you may flatten some relationships. Then you will need to synchronize the data from the source of truth in serviceA
to the copy in serviceB
asynchronously.