14

I'm a seasoned programmer but a novice with DDD. I have a project that I'm trying to implement with DDD and my understanding is that there is some ambiguity with regard to the use of domain models and application services. In the purest of DDD implementations, my understanding is that domain models are supposed to contain all business logic. However, this doesn't seem like a realistic goal to me. In simple validation scenarios and such, this is all well and good, but the goal of encapsulating business logic in domain models gets a little slippery when we think about logic requiring additional services such as database lookups/persistence and other external dependencies.

For example, I have a domain model "Order" that has a method "IsValid". To determine whether an order is valid, a database read must be performed. In order to keep the model clean, I don't want to inject any database work into it. OK fine - I create an OrderService to do the database hit and pass in a result to "IsValid" so that the dependency on the database resides with the service. However, what happens if there are multiple database hits that depend on business logic? Is it appropriate to add that logic to the application service? If so, it seems that business logic is leaking into the application services layer which is a violation of DDD. However, if we move the logic inside the domain model and do the database hit there, we're mixing concerns and also violating DDD.

Can anyone experienced with DDD provide any insight into how to deal with this sort of an issue? DDD seems like it provides a lot of nice value, but I'm worried about these kinds of issues leading to design "rot".

6
  • Domain Models should not know about persistence, but that doesn't mean that persistence won't play a role. A good ORM can abstract away the persistence logic without polluting your domain model. Mar 24, 2017 at 18:11
  • So one problem I've seen here is that some team members have injected an EF DbContext instance into a domain model, and then also used the constructor to do a database "Select" to populate an underlying entity. This has lead to N+1 situations from calling code that instantiates the domain model inside a loop. Seems dangerous.
    – Andy
    Mar 24, 2017 at 18:24
  • 1
    A Domain Model should not have a DbContext object as a class member. The ORM mappings should take care of loading related entities. You can tell Entity Framework to issue JOIN queries to perform an eager fetch of related entities, which should mitigate the N+1 query problems. Mar 24, 2017 at 20:04
  • In fact, adding to your question that some of your Domain Models have a DbContext object passed into them via constructor injection is an essential detail to your question. Could you add that to your question text? Mar 24, 2017 at 20:05
  • 1
    The more I think about it, the more I realize the DbContext being passed in the constructors is the true root of your problem, and, frankly, a violation of the fact a constructor should not do any work. Those constructors are (calling the database). They are bypassing the lazy loading feature for related entities. It sounds like your team needs to dig into the EF mappings more. Whatever select's they are doing could probably be accomplished by tweaking the mappings. Mar 24, 2017 at 20:09

3 Answers 3

6

This is a very broad question but I will try to give you an answer.

...there is some ambiguity with regard to the use of domain models and application services

There is no ambiguity if you design well your bounded contexts, the domain models and the relationships between them.

However, what happens if there are multiple database hits that depend on business logic?

In DDD, all the operations go through the Aggregate root (AR). The application services load the ARs from the persistence, send commands to them, then persist those ARs back. ARs don't need to hit the database at all. In fact a good designed AR does not even know that databases exists at all. All they touch/see/smell is their internal state and the immutable arguments that they receive in their command methods. If an AR needs something from the database then the Application service pass that thing as argument.

ARs should be pure, side effects free objects/functions. One reason is that commands applied on them must be retry-able, in case of concurrent modifications.

As an example: ARs don't send emails, they return a value object that holds the email data (from, to, subject and body) and the Application service takes that value object and passes it to a infrastructure service that does the actual sending of the email.

For example, I have a domain model "Order" that has a method "IsValid". To determine whether an order is valid, a database read must be performed.

You don't need an isValid method, as an Order cannot get into an invalid state anyway because any modifications are done through it's methods. If you are referring to the existence of an Order then this kind of validation is done by the Application service: if it does not find the Order in the persistence then it does not exist, as simple as that. Maybe you are referring to the ShoppingCart as being valid, not the Order. What about then? Well, you could try to create an Order from a ShoppingCart and if you succeed then a Cart is ready to be ordered. As the Order is side effect free, no order will actually be created. Just an example of how you might think in DDD.

DDD seems like it provides a lot of nice value, but I'm worried about these kinds of issues leading to design "rot".

If you follow the DDD approach well, your design will never rot. Never.

As a foot note: In the beginning I was a little miss-leaded by the Layered architecture. Don't make the same mistake. Take a look at some newer architectures like CQRS that fits very well with DDD.

6
  • Any tips for good CQRS reference material? I'm wary of CQRS as I've seen it lead to over-architecture and a very buggy system with 2 databases one optimized for queries and the other was the system of truth, but when they got out of sync (which was often), then bugs crippled our team for months.
    – Andy
    Mar 24, 2017 at 21:39
  • What have you seen? And how did the two models get into out of sync? Mar 25, 2017 at 2:25
  • Some CQRS people advocate 2 separate databases, one used for writes, and the other optimized for reads. We wrote to the write one and published events in order to try to keep the other DB in sync. However, those events were handled by custom code which, when buggy, caused the DBs to be out of sync. This is an example of CQRS gone wrong.
    – Andy
    Mar 26, 2017 at 17:46
  • "Some"? It should be "most of" as there are two models with two different purposes . This is not CQRS gone wrong, this is how CQRS cuts the bugs in half! Mar 26, 2017 at 18:05
  • CRQS at my former organization caused the number of bugs to increase by a LOT due to bugs in our event handlers meant to synchronize data between the 2 data sources. It's one of the reasons I left that company - we were spending entire sprints to address these bugs. Eventually the company backed away from CQRS. See Udi Dahan's post from 2011 that most people shouldn't be using it: udidahan.com/2011/04/22/when-to-avoid-cqrs
    – Andy
    Mar 27, 2017 at 12:39
2

For example, I have a domain model "Order" that has a method "IsValid". To determine whether an order is valid, a database read must be performed.

That's almost certainly not right; in that you are pulling in assumptions about where data is stored into your domain model.

A more reasonable spelling would be something like "an order is valid if the total order is less than $100 or it has a C level approval signature." In the domain model code, you would have access to this state, and would implement the boolean logic, checking against local copies of the state.

The flow would normally look like: the query arrives, the application reads the query to figure out which order is being asked about, the OrderRepository would be queried for the state of the specific order; the state of the order would be used to reconstitute the Order model, and then the query would be run against the model to get the result.

A design where the we end up hunting for the model data piecemeal has lost the plot. You load the whole model (in this case, the whole order), then query the model.

It may help to think about writing tests for the model; you ought to be able to do that without involving the persistence component at all, simply by working with in memory copies of model state.

So one problem I've seen here is that some team members have injected an EF DbContext instance into a domain model

Yeah, the literature is somewhat lacking in understanding the relations between the model, the repositories, and the underlying durable store. The domain code should be completely persistence ignorant, but there's some stitch work required to get the current state into the model -- and in uses cases where we change the model, getting the modified state back out of the model again.

1
  • 1
    Loading the whole model in this case is very expensive - the object graph for Order is huge. I am familiar with the concepts of bounded context and splitting aggregate roots into other roots. I don't know if I can get onboard with loading the whole thing. But perhaps that is why I'm struggling with this issue.
    – Andy
    Mar 24, 2017 at 21:34
2

I can't give you a concrete answer. But, I can give you two things to consider:

Firstly, a domain that "knows" about persistence, at least in a vague sense, isn't necessarily bad.

If domain entities are largely one-to-one with your database tables, you could be following an Active Record pattern. And that's OK. It's simple, and done right -- very few lines of code. (Both very good for maintenance!)

If you're using a repository pattern, which is typical of DDD, it's still OK for the domain to work with repositories. You just ideally want to inject those repositories into the domain. (I might argue locating repo's is also OK.)

An Order in your domain might be constructed with an ILineItemRepo and invoke repo.GetByOrder(this) - and that's OK. It might even need to know, as you suggested, about it's own repository to enforce certain constraints.

Perfectly fine.

This isn't at all incompatible with a configuration layer deciding how repositories are ultimately handled. Even the Active Record pattern permits this, if done right. Your domain is allowed to know it's pulling objects into and out of something -- it just shouldn't usually need to know exactly what that something is.1

Secondly, the domain only needs to know about "core" business rules.

In other words, the domain ideally contains all the rules, and only those rules, that would apply to any application using those entities in your business.

You can certainly stretch this either to fit your team's needs, because your little domain library probably isn't used across the organization. But, I've found it to be a good guiding principle -- especially as other groups in your organization ask for endpoints into your application.

Each client (and endpoint) can have certain rules or special steps that need to be done. But, only those rules that would apply to all possible "clients" or "applications" belong in your domain.

Everything that seems to apply to a single client's service or "application" belongs in that particular client's service or application2 ...


1. Unless your core software, and therefore your domain, is precisely about managing persistence! You'd have a hard time writing the domain layer for a file system that doesn't know about files!

2. Though he's not necessarily talking about DDD, Uncle Bob rants about this a lot. (And, I mostly agree with him on that point.)

6
  • Good answer, but note that in DDD, a Repository is part of the Domain Model, and as such it's implemented within the Domain Layer (Fowler's "Repository Pattern" is not quite the same as the DDD Repository).
    – Rogério
    Aug 6, 2019 at 18:58
  • sorry for being late, but @Rogério is true that Repositories are domain concepts, but is also true that repositories are ports, they don't implement access to database but are just an abstraction of a collection of aggregates, so it's still true that domain has no clue about data persistence (if any) Aug 7, 2020 at 18:03
  • @CarmineIngaldi Actually, in DDD layered architecture (the one described in the original book about DDD) while the interface of a Repository is certainly "an abstraction of a collection of aggregates", the implementation of said interface knows about data persistence, and it belongs within the Domain Layer. Note this does not polute the business logic implemented elsewhere (in Domain Services, Factories, and Entities), which only know about the Repository interfaces. It seems to me that people fail to understand the interface/implementation divide.
    – Rogério
    Aug 10, 2020 at 1:59
  • yes @Rogério this is exactly what i would point out: Domain Layer is still unaware of persistence/durability details of aggregate state because the logic is implemented elsewhere. Repositories just declare APIs to access a collection of aggregates and are consumed from Domain Services. Spring Data JPA is a good example of this abstraction: we declare *Repository interfaces in our codebase, but the implementation is owned by the framework at infra level Aug 10, 2020 at 11:12
  • @CarmineIngaldi No, the Domain Layer is not just a bunch of abstractions! As any layer within the application's codebase, it also contains the implementations for those interfaces/abstractions. A DDD Repository doesn't "just declare APIs". With Spring DATA JPA, we get runtime code generation from annotated Java interfaces, this is a peculiar technology, but we still have "implementation code" in such annotations, for example @Query("select c.firstname as firstname, c.lastname as lastname from Customer c") Collection<CustomerProjection> findsByProjectedColumns(); in CustomerRepository sample.
    – Rogério
    Aug 10, 2020 at 22:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.