Christopher Sellers
Christopher Sellers

Reputation: 65

How loosely coupled can a Domain Model be?

Questions: Should I turn my dependencies around and have the DomainModel depend on Core (which holds the systems interfaces and high level policy)?

Do we ever consider a rich domain model as representing implementation details?

Background: So I have my DomainModel in a separate project with no outgoing dependencies. All of my subsystem services are in separate projects, and none of them know about each other. I also have a Core project which holds all of the system specific things such as interfaces, enums, validation classes, extensions and other things common to all the services which I wouldn't classify as low level implementation details. Core has no outgoing dependencies except to the DomainModel, all of the services depend on Core, and also DomainModel.

My current thoughts: So I've heard design statements such as

"the domain model should have no outgoing dependencies"

and potentially in contrast

"high level policy shouldn't depend on low level implementation details"

and

"modules should depend on each other in the direction of stability"

I find as I develop the system the implementation of the domain model changes quite a lot, all of the business rules and objects are there, I wouldn't consider most of the domain model stable at all, possibly some of it such as the ValueObject(s).

The advantage I see in having DomainModel depend on Core is that the high level policy then knows nothing of the implementation details of the domain model, which is also isolated from the rest of the system. Indeed, every other project would then just depend on Core and still know nothing of each other. When I make a change in DomainModel (which I often do as I develop) to build would recompile just that project, rather than the entire solution as is the case at present.

The disadvantage I see in achieving this extra amount of loose coupling is an increase in complexity? I would have to create interfaces for every single domain model object which would live in a special folder within Core. For those interfaces I'd also want to drop the 'I' prefix and have say MoneyType : Money [interface]. I imagine the only way I could then instantiate domain objects would be via abstract factories so I'd need a few more of those as well. Right now I do like being able to just instantiate concrete domain objects 'anywhere' in the system, almost like an extension to, or abstraction over, the BCL (which is part of the point of a domain model is it not?).

I can imagine the system working with either architecture, so is this just a classic case of competing design concerns - or am I completely missing something?

Edits: This highly voted answer seems to indicate that the domain model should be isolated with each object only exposing an interface to the rest of the system.

https://stackoverflow.com/a/821300/7417812

At the moment this feels better to me, as the rest of the system doesn't have unfettered access to the domain objects (thus the recompiling of them all every time there is a change). However, turning this dependency around for complete isolation will be no trivial task so I'm very open to more discussion and answers.

More edits: So I attempted the refactoring, at the point where I began trying to implement my own simple DI container I decided things were starting to become smelly and slightly ridiculous.

Any perceived benefit of complete DomainModel isolation was being offset with an exponential increase in complexity and lines of code (at least for me). So I refactored the architecture around so that DomainModel only depends on Core which now only holds my design by contract classes, custom collections, annotations and common extensions I use. I took all the system specific interfaces and infrastructure out into a new project Infrastructure which all the subsystem services depend on (no specific technologies in here so probably not a typical infrastructure layer?).

Everything feels better now however I'm back to depending on the concrete domain objects which I'm fine with. It wasn't at all a wasted exercise as I identified several improvements and simplifications of the DomainModel, and was able to clarify Core and identify the opportunity for Infrastructure.

Upvotes: 3

Views: 1276

Answers (2)

guillaume31
guillaume31

Reputation: 14064

I also have a Core project which holds all of the system specific things such as interfaces, enums, validation classes, extensions and other things common to all the services

You're describing agnostic code constructs and patterns which don't inherently belong in a particular layer.

  • Interfaces and enums, as long as they are about domain concepts, should be in the domain layer. Other interfaces and enums... well, wherever they fit best.

  • "Validation classes" is also very vague, but domain validation goes in the domain, command validation in Application layer, user input validation in the UI, etc.

  • Extension methods can be found in any layer.

I think you should get rid of that "Core" project that doesn't look really coherent and fan out all its content to the appropriate projects.

IMO, everything that is "high level policy" should go in Domain. Utility classes shared by all projects you could put aside in a library-like project or NuGet package, but there shouldn't be too many of these and I wouldn't call it Core anyway.

Upvotes: 1

Constantin Galbenu
Constantin Galbenu

Reputation: 17683

In the Onion architecture, one of the architectures that aligns well with DDD, the Domain layer can be further split in two sub-layers:

  1. Core: contains the building blocks not specific to any domain or technology, containing generic building blocks like lists, case classes and actors. It will never include technological concepts, e.g. REST or databases.
  2. Domain: the actual domain layer where all business logic resides with classes and methods named using the ubiquitous language for the domain. By controlling the domain through the API and putting all business logic into the domain the application becomes portable, all technical bits can be extracted without losing any business logic.

So, you could have a Core component that is shared between bounded contexts but I don't think that this should necessarily be in a separate project. It depends on how you want to propagate changes to this component to the other projects.

About the dependencies, the Domain sub-layer will depend on the Core sub-layer as it uses its low level classes but this does not break the Dependency inversion principle as both are in the same layer. However, the (entire) Domain layer should not depend on any other layers like Application, Presentation or Infrastructure, the Domain layer should remain pure, with no side effects and no dependencies. This rule should be respected in any architecture.

Upvotes: 4

Related Questions