DDD - the rule that Entities can't access Repositories directly
Asked Answered
G

13

238

In Domain Driven Design, there seems to be lots of agreement that Entities should not access Repositories directly.

Did this come from Eric Evans Domain Driven Design book, or did it come from elsewhere?

Where are there some good explanations for the reasoning behind it?

edit: To clarify: I'm not talking about the classic OO practice of separating data access off into a separate layer from the business logic - I'm talking about the specific arrangement whereby in DDD, Entities are not supposed to talk to the data access layer at all (i.e. they are not supposed to hold references to Repository objects)

update: I gave the bounty to BacceSR because his answer seemed closest, but I'm still pretty in the dark about this. If its such an important principle, there should be some good articles about it online somewhere, surely?

update: March 2013, the upvotes on the question imply there's a lot of interest in this, and even though there's been lots of answers, I still think there's room for more if people have ideas about this.

Goree answered 17/4, 2011 at 15:2 Comment(2)
Take a look at my question https://mcmap.net/q/119465/-ddd-domain-driven-design-how-to-handle-entity-state-changes-and-encapsulate-business-rules-that-requires-large-amount-of-data-to-be-processed/235715, it's show a situation when it's hard to capture logic, without Entity having access to repository. Though I think entities should not have access to repositories, and there is a solution to my situation when code can be rewritten without repository reference, but currently I can't think of any.Petrillo
Don't know where it came from. My thoughts: I think that this misunderstanding comes from people how don't understand what DDD is all about. This approach is not for implementing software but for designing it (domain .. design). Back in the days, we had architects and implementers, but now there are just software developers. DDD is meant for architects. And when an architect is designing software he needs some tool or pattern to represent a memory or database for devs who will implement the prepared design. But the design itself (from a business perspective) doesn't have or need a repository.Haggi
N
61

There's a bit of a confusion here. Repositories access aggregate roots. Aggregate roots are entities. The reason for this is separation of concerns and good layering. This doesn't make sense on small projects, but if you're on a large team you want to say, "You access a product through the Product Repository. Product is an aggregate root for a collection of entities, including the ProductCatalog object. If you want to update the ProductCatalog you must go through the ProductRepository."

In this way you have very, very clear separation on the business logic and where things get updated. You don't have some kid who is off by himself and writes this entire program that does all these complicated things to the product catalog and when it comes to integrate it to the upstream project, you're sitting there looking at it and realize it all has to be ditched. It also means when people join the team, add new features, they know where to go and how to structure the program.

But wait! Repository also refers to the persistence layer, as in the Repository Pattern. In a better world an Eric Evans' Repository and the Repository Pattern would have separate names, because they tend to overlap quite a bit. To get the repository pattern you have contrast with other ways in which data is accessed, with a service bus or an event model system. Usually when you get to this level, the Eric Evans' Repository definition goes by the way side and you start talking about a bounded context. Each bounded context is essentially its own application. You might have a sophisticated approval system for getting things into the product catalog. In your original design the product was the center piece but in this bounded context the product catalog is. You still might access product information and update product via a service bus, but you must realize that a product catalog outside the bounded context might mean something completely different.

Back to your original question. If you're accessing a repository from within an entity it means the entity is really not a business entity but probably something that should exist in a service layer. This is because entities are business object and should concern themselves with being as much like a DSL (domain specific language) as possible. Only have business information in this layer. If you're troubleshooting a performance issue, you'll know to look elsewhere since only business information should be here. If suddenly, you have application issues here, you're making it very hard to extend and maintain an application, which is really the heart of DDD: making maintainable software.

Response to Comment 1: Right, good question. So not all validation occurs in the domain layer. Sharp has an attribute "DomainSignature" that does what you want. It is persistence aware, but being an attribute keeps the domain layer clean. It ensures that you don't have a duplicate entity with, in your example the same name.

But let's talk about more complicated validation rules. Let's say you're Amazon.com. Have you ever ordered something with an expired credit card? I have, where I haven't updated the card and bought something. It accepts the order and the UI informs me that everything is peachy. About 15 minutes later, I'll get an e-mail saying there's a problem with my order, my credit card is invalid. What's happening here is that, ideally, there's some regex validation in the domain layer. Is this a correct credit card number? If yes, persist the order. However, there's additional validation at the application tasks layer, where an external service is queried to see if payment can be made on the credit card. If not, don't actually ship anything, suspend the order and wait for the customer. This should all take place in a service layer.

Don't be afraid to create validation objects at the service layer that can access repositories. Just keep it out of the domain layer.

Nummular answered 22/4, 2011 at 18:47 Comment(9)
Thanks. But I should be striving to get as much business logic as possible into the entities (and their associated factories and specifications and so on), right? But if none of them are allowed to fetch data via Repositories, how am I supposed to write any (reasonably complicated) business logic? For example: Chatroom user is not allowed to change their name to a name thats already been used by someone else. I'd like that rule to be built into by ChatUser entity, but its not very easy to do if you can't hit the repository from there. So what should I do?Goree
My response was larger than the comment box would allow, see the edit.Nummular
You entity should know how to protect itself from harm. This includes making sure it cannot get into an invalid state. What you are describing with the Chat room user is business logic that resides IN ADDITION to the logic the entity has to keep itself valid. Business logic like what your wanting really belongs in a Chatroom service, not the ChatUser entity.Malvern
Thanks Alec. Thats a clear way of expressing it. But to me it seems that Evans' domain-focused golden rule of 'all business logic should go in the domain layer' is in conflict with the rule of 'entities should not access repositories'. I can live with that if I understand why that is, but I can't find any good explanation online of why entities should not access repositories. Evans does not seem to mention it explicitly. Where did it come from? If you can post an answer pointing to some good literature, you might be able to bag yourself a 50pt bounty : )Goree
First, I like this discussion. A good one. kertosis, your right in that validation must take place where it makes sense. Working a lot with web apps you have a lot of duplicate validations, both in web form (jquery), server side (Application service layer) and by the domain itself.Kozak
Clarification. Of course your repositories should work against your aggregate roots like the answer mentions. Sorry for that.Kozak
"his doesn't make sense on small" This is a big mistake teams do... it is a small project, as such I can do this and that... stop thinking like that. Many of the small projects we work with, end up becoming big, due to business requirements. If you do something wither small or big, do it right.February
@codeulike, that's not a conflict - domain services are part of the domain layer.Protohistory
If I should conclude my entity isn't allowed to have access to any service that refers to the persistence layer (like repositories), does it mean my entity may not fire any (domain) event (send messages, etc.) too? The last signify the entity have access to some IEventDispatcher. It in turn refers to the infrastracture concern (under the hood), isn't?Christinchristina
C
48

At first, I was of the persuasion to allow some of my entities access to repositories (i.e. lazy loading without an ORM). Later I came to the conclusion that I shouldn't and that I could find alternate ways:

  1. We should know our intentions in a request and what we want from the domain, therefore we can make repository calls before constructing or invoking Aggregate behavior. This also helps avoid the problem of inconsistent in-memory state and the need for lazy loading (see this article). The smell is that you cannot create an in memory instance of your entity anymore without worrying about data access.
  2. CQS can help reduce the need for wanting to call the repository for things in our entities.
  3. We can use a specification to encapsulate and communicate domain logic needs and pass that to the repository instead (a service can orchestrate these things for us). The specification can come from the entity that is in charge of maintaining that invariant. The repository will interpret parts of the specification into it's own query implementation and apply rules from the specification on query results. This aims to keep domain logic in the domain layer. It also serves the Ubiquitous Language and communication better. Imagine saying "overdue order specification" versus saying "filter order from tbl_order where placed_at is less than 30 minutes before sysdate" (see this answer).
  4. It makes reasoning about the behavior of entities more difficult since the Single-Responsibility Principle is violated. If you need to work out storage/persistence issues you know where to go and where not to go.
  5. It avoids the danger of giving an entity bi-directional access to global state (via the repository and domain services). You also don't want to break your transaction boundary.

Vernon Vaughn in the red book Implementing Domain-Driven Design refers to this issue in two places that I know of (note: this book is fully endorsed by Evans as you can read in the foreword). In Chapter 7 on Services, he uses a domain service and a specification to work around the need for an aggregate to use a repository and another aggregate to determine if a user is authenticated. He's quoted as saying:

As a rule of thumb, we should try to avoid the use of Repositories (12) from inside Aggregates, if at all possible.

Vernon, Vaughn (2013-02-06). Implementing Domain-Driven Design (Kindle Location 6089). Pearson Education. Kindle Edition.

And in Chapter 10 on Aggregates, in the section titled "Model Navigation" he says (just after he recommends the use of global unique IDs for referencing other aggregate roots):

Reference by identity doesn’t completely prevent navigation through the model. Some will use a Repository (12) from inside an Aggregate for lookup. This technique is called Disconnected Domain Model, and it’s actually a form of lazy loading. There’s a different recommended approach, however: Use a Repository or Domain Service (7) to look up dependent objects ahead of invoking the Aggregate behavior. A client Application Service may control this, then dispatch to the Aggregate:

He goes onto show an example of this in code:

public class ProductBacklogItemService ... { 
    ...
    @Transactional 
    public void assignTeamMemberToTask( 
        String aTenantId, 
        String aBacklogItemId, 
        String aTaskId, 
        String aTeamMemberId) { 

        BacklogItem backlogItem = backlogItemRepository.backlogItemOfId( 
            new TenantId(aTenantId), 
            new BacklogItemId(aBacklogItemId)); 

        Team ofTeam = teamRepository.teamOfId( 
            backlogItem.tenantId(), 
            backlogItem.teamId());

        backlogItem.assignTeamMemberToTask( 
            new TeamMemberId( aTeamMemberId), 
            ofTeam,
            new TaskId( aTaskId));
   } 
   ...
}

He goes on to also mention yet another solution of how a domain service can be used in an Aggregate command method along with double-dispatch. (I can't recommend enough how beneficial it is to read his book. After you have tired from endlessly rummaging through the internet, fork over the well deserved money and read the book.)

I then had some discussion with the always gracious Marco Pivetta @Ocramius who showed me a bit of code on pulling out a specification from the domain and using that:

  1. This is not recommended:
$user->mountFriends(); // <-- has a repository call inside that loads friends? 
  1. In a domain service, this is good:
public function mountYourFriends(MountFriendsCommand $mount) {
    $user = $this->users->get($mount->userId()); 
    $friends = $this->users->findBySpecification($user->getFriendsSpecification()); 
    array_map([$user, 'mount'], $friends); 
}
Calcar answered 25/8, 2015 at 14:27 Comment(3)
Question: We're always taught not to create an object in an invalid or inconsistent state. When you load users from the repository, and then you call getFriends() before doing anything else, it will be empty or lazy loaded. If empty, then this object lying and in an invalid state. Any thoughts on this?Capacitor
Repository calls the Domain to new up an instance. You don't get an instance of User without going through the Domain. The problem this answer addresses is the reverse. Where the Domain is referencing the Repository, and this should be avoided.Calcar
"After you have tired from endlessly rummaging through the internet, fork over the well deserved money and read the book" - oof point taken. I'll go get the bookGlobular
K
33

Its a very good question. I will look forward to some discussion about this. But I think it's mentioned in several DDD books and Jimmy nilssons and Eric Evans. I guess it's also visible through examples how to use the reposistory pattern.

BUT lets discuss. I think a very valid thought is why should an entity know about how to persist another entity? Important with DDD is that each entity has a responsibility to manage its own "knowledge-sphere" and shouldn't know anything about how to read or write other entities. Sure you can probably just add a repository interface to Entity A for reading Entities B. But the risk is that you expose knowledge for how to persist B. Will entity A also do validation on B before persisting B into db?

As you can see entity A can get more involved into entity B's lifecycle and that can add more complexity to the model.

I guess (without any example) that unit-testing will be more complex.

But I'm sure there will always be scenarios where you're tempted to use repositories via entities. You have to look at each scenario to make a valid judgement. Pros and Cons. But the repository-entity solution in my opinion starts with a lot of Cons. It must be a very special scenario with Pros that balance up the Cons....

Kozak answered 18/4, 2011 at 9:30 Comment(5)
Good point. The old school domain-model would probably have Entity B responsible for validating itself before it lets itself get persisted, I guess. Are you sure Evans mentions Entities not using Repositories? I'm halfway through the book and it hasn't mentioned it yet ...Goree
Well I read the book several years ago (well 3...) and my memory fails me. I cannot recall if he exactly phrase it BUT however I believe he illustrated this through examples. You can also find a community interpretation of his Cargo example (from his book) at dddsamplenet.codeplex.com. Download the code project (look at Vanilla project - its the example from the book). You'll find that repositories are only used in Application layer for accessing domain entities.Kozak
Downloading the DDD SmartCA example from the book p2p.wrox.com/… you'll see another approach (though this is a RIA windows client) where repositories are used in services (nothing strange here) but services are uses inside entites. This is something I wouldn't do BUT I'm a webb app guy. Given the scenario for SmartCA app where you must be able to work offline, maybe ddd design will look differently.Kozak
The SmartCA example sounds interesting, which chapter is it in? (the code downloads are arranged by chapter)Goree
Well I have the C# solution at my hardrive but I guess I have the total final solution since I downloaded it from dddpds.codeplex.com where he keeps the whole solution. Download and check out SmartCA.Model.Projects.ProjectService and do a FindUsage in VStudio you see an example of using service in entities. BUT I haven't done it this way :)Kozak
E
18

What an excellent question. I am on the same path of discovery, and most answers throughout the internet seem to bring as many problems as they bring solutions.

So (at the risk of writing something that I disagree with a year from now) here are my discoveries so far.

First of all, we like a rich domain model, which gives us high discoverability (of what we can do with an aggregate) and readability (expressive method calls).

// Entity
public class Invoice
{
    ...
    public void SetStatus(StatusCode statusCode, DateTime dateTime) { ... }
    public void CreateCreditNote(decimal amount) { ... }
    ...
}

We want to achieve this without injecting any services into an entity's constructor, because:

  • Introduction of a new behavior (that uses a new service) could lead to a constructor change, meaning the change affects every line that instantiates the entity!
  • These services are not part of the model, but constructor-injection would suggest that they were.
  • Often a service (even its interface) is an implementation detail rather than part of the domain. The domain model would have an outward-facing dependency.
  • It can be confusing why the entity cannot exist without these dependencies. (A credit note service, you say? I am not even going to do anything with credit notes...)
  • It would make it hard instantiate, thus hard to test.
  • The problem spreads easily, because other entities containing this one would get the same dependencies - which on them may look like very unnatural dependencies.

How, then, can we do this? My conclusion so far is that method dependencies and double dispatch provide a decent solution.

public class Invoice
{
    ...

    // Simple method injection
    public void SetStatus(IInvoiceLogger logger, StatusCode statusCode, DateTime dateTime)
    { ... }

    // Double dispatch
    public void CreateCreditNote(ICreditNoteService creditNoteService, decimal amount)
    {
        creditNoteService.CreateCreditNote(this, amount);
    }

    ...
}

CreateCreditNote() now requires a service that is responsible for creating credit notes. It uses double dispatch, fully offloading the work to the responsible service, while maintaining discoverability from the Invoice entity.

SetStatus() now has a simple dependency on a logger, which obviously will perform part of the work.

For the latter, to make things easier on the client code, we might instead log through an IInvoiceService. After all, invoice logging seems pretty intrinsic to an invoice. Such a single IInvoiceService helps avoid the need for all sorts of mini-services for various operations. The downside is that it becomes obscure what exactly that service will do. It might even start to look like double dispatch, while most of the work is really still done in SetStatus() itself.

We could still name the parameter 'logger', in hopes of revealing our intent. Seems a bit weak, though.

Instead, I would opt to ask for an IInvoiceLogger (as we already do in the code sample) and have IInvoiceService implement that interface. The client code can simply use its single IInvoiceService for all Invoice methods that ask for any such a very particular, invoice-intrinsic 'mini-service', while the method signatures still make abundantly clear what they are asking for.

I notice that I have not addressed repositories exlicitly. Well, the logger is or uses a repository, but let me also provide a more explicit example. We can use the same approach, if the repository is needed in just a method or two.

public class Invoice
{
    public IEnumerable<CreditNote> GetCreditNotes(ICreditNoteRepository repository)
    { ... }
}

In fact, this provides an alternative to the ever-troublesome lazy loads.

Update: I have left the text below for historical purposes, but I suggest steering clear of lazy loads 100%.

For true, property-based lazy loads, I do currently use constructor injection, but in a persistence-ignorant way.

public class Invoice
{
    // Lazy could use an interface (for contravariance if nothing else), but I digress
    public Lazy<IEnumerable<CreditNote>> CreditNotes { get; }

    // Give me something that will provide my credit notes
    public Invoice(Func<Invoice, IEnumerable<CreditNote>> lazyCreditNotes)
    {
        this.CreditNotes = new Lazy<IEnumerable<CreditNotes>>() => lazyCreditNotes(this));
    }
}

On the one hand, a repository that loads an Invoice from the database can have free access to a function that will load the corresponding credit notes, and inject that function into the Invoice.

On the other hand, code that creates an actual new Invoice will merely pass a function that returns an empty list:

new Invoice(inv => new List<CreditNote>() as IEnumerable<CreditNote>)

(A custom ILazy<out T> could rid us of the ugly cast to IEnumerable, but that would complicate the discussion.)

// Or just an empty IEnumerable
new Invoice(inv => IEnumerable.Empty<CreditNote>())

I'd be happy to hear your opinions, preferences, and improvements!

Elbring answered 16/11, 2016 at 12:31 Comment(0)
W
14

Why separate out data access?

From the book, I think the first two pages of the chapter Model Driven Design gives some justification for why you want to abstract out technical implementation details from the implementation of the domain model.

  • You want to keep a tight connection between the domain model and the code
  • Separating technical concerns helps prove the model is practical for implementation
  • You want the ubiquitous language to permeate through to the design of the system

This seems to be all for the purpose of avoiding a separate "analysis model" that becomes divorced from the actual implementation of the system.

From what I understand of the book, it says this "analysis model" can end up being designed without considering software implementation. Once developers try to implement the model understood by the business side they form their own abstractions due to necessity, causing a wall in communication and understanding.

In the other direction, developers introducing too many technical concerns into the domain model can cause this divide as well.

So you could consider that practicing separation of concerns such as persistence can help safeguard against these design an analysis models diverging. If it feels necessary to introduce things like persistence into the model then it is a red flag. Maybe the model is not practical for implementation.

Quoting:

"The single model reduces the chances of error, because the design is now a direct outgrowth of the carefully considered model. The design, and even the code itself, has the communicativeness of a model."

The way I'm interpreting this, if you ended up with more lines of code dealing with things like database access, you lose that communicativeness.

If the need for accessing a database is for things like checking uniqueness, have a look at:

Udi Dahan: the biggest mistakes teams make when applying DDD

http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/

under "All rules aren't created equal"

and

Employing the Domain Model Pattern

http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119

under "Scenarios for Not Using the Domain Model", which touches on the same subject.

How to separate out data access

Loading data through an interface

The "data access layer" has been abstracted through an interface, which you call in order to retrieve required data:

var orderLines = OrderRepository.GetOrderLines(orderId);

foreach (var line in orderLines)
{
     total += line.Price;
}

Pros: The interface separates out the "data access" plumbing code, allowing you to still write tests. Data access can be handled on a case by case basis allowing better performance than a generic strategy.

Cons: The calling code must assume what has been loaded and what hasn't.

Say GetOrderLines returns OrderLine objects with a null ProductInfo property for performance reasons. The developer must have intimate knowledge of the code behind the interface.

I've tried this method on real systems. You end up changing the scope of what is loaded all the time in an attempt to fix performance problems. You end up peeking behind the interface to look at the data access code to see what is and isn't being loaded.

Now, separation of concerns should allow the developer to focus on one aspect of the code at one time, as much as is possible. The interface technique removes the HOW is this data loaded, but not HOW MUCH data is loaded, WHEN it is loaded, and WHERE it is loaded.

Conclusion: Fairly low separation!

Lazy Loading

Data is loaded on demand. Calls to load data is hidden within the object graph itself, where accessing a property can cause a sql query to execute before returning the result.

foreach (var line in order.OrderLines)
{
    total += line.Price;
}

Pros: The 'WHEN, WHERE, and HOW' of data access is hidden from the developer focusing on domain logic. There is no code in the aggregate that deals with loading data. The amount of data loaded can be the exact amount required by the code.

Cons: When you are hit with a performance problem, it is hard to fix when you have a generic "one size fits all" solution. Lazy loading can cause worse performance overall, and implementing lazy loading may be tricky.

Role Interface/Eager Fetching

Each use case is made explicit via a Role Interface implemented by the aggregate class, allowing for data loading strategies to be handled per use case.

Fetching strategy may look like this:

public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
    Order Load(string aggregateId)
    {
        var order = new Order();

        order.Data = GetOrderLinesWithPrice(aggregateId);
    
        return order;
    }

}
   

Then your aggregate can look like:

public class Order : IBillOrder
{
    void BillOrder(BillOrderCommand command)
    {
        foreach (var line in this.Data.OrderLines)
        {
            total += line.Price;
        }

        etc...
    }
}

The BillOrderFetchingStrategy is use to build the aggregate, and then the aggregate does its work.

Pros: Allows for custom code per use case, allowing for optimal performance. Is inline with the Interface Segregation Principle. No complex code requirements. Aggregates unit tests do not have to mimic loading strategy. Generic loading strategy can be used for majority of cases (e.g. a "load all" strategy) and special loading strategies can be implemented when necessary.

Cons: Developer still has to adjust/review fetching strategy after changing domain code.

With the fetching strategy approach you might still find yourself changing custom fetching code for a change in business rules. It's not a perfect separation of concerns but will end up more maintainable and is better than the first option. The fetching strategy does encapsulate the HOW, WHEN and WHERE data is loaded. It has a better separation of concerns, without losing flexibility like the one size fits all lazy loading approach.

Whitsunday answered 27/4, 2011 at 12:0 Comment(4)
Thanks, I'll check out the links. But in your answer are you confusing 'separation of concerns' with 'no access to it at all'? Certainly most people would agree that the persistence layer should be kept separate from the layer that the Entities are in. But that is different to saying 'the entities should not be able to even see the persistence layer, even through a very general implementation-agnostic interface'.Goree
Loading data through an interface or not, you are still concerned with loading data while implementing business rules. I agree that a lot of people still call this separation of concerns though, maybe single responsibility principle would have been a better term to use.Whitsunday
Not quite sure how to parse your last comment, but I think you are suggesting that data should not be loaded while processing business rules? I see that would make the rules 'purer'. But many types of business rule are going to need to refer to other data - are you suggesting that it should be loaded in advance by a separate object?Goree
@codeulike: I've updated my answer. You can still load data during business rules if you feel you absolutely have to, but that doesn't require adding lines of data access code into your domain model (e.g. lazy load). In the domain models that I have designed, data is generally just loaded in advance like you said. I've found that running business rules usually doesn't require an excessive amount of data.Whitsunday
A
3

To me this appears to be general good OOD related practice rather than being specific to DDD.

Reasons that I can think of are:

  • Separation of concerns (Entities should be separated from the way they are persisted. as there could be multiple strategies in which the same entity would be persisted depending on usage scenario)
  • Logically, entities could be seen in a level below the level in which repositories operate. Lower level components should not have knowledge on the higher level components. Therefore entries should not have knowledge on Repositories.
Aldose answered 5/7, 2012 at 23:3 Comment(0)
A
2

Entities only capture the rules related to their valid state. Is the data in them valid? Can the data in them change in this way?

An aggregate root does the same for a group of entities. Is the data in the aggregate valid? Can the data in the aggregate change in this way?

Domain services capture rules about changes across entities or aggregates. Can we change X and Y this way?

None of this ever requires access to a repository or to infrastructure. What you do is that an application service will offer up a domain use case, for that use case, the application service will gather all the needed data from the repositories, that will return it your domain entities and/or aggregate roots and their value objects. The entities/aggregate roots and value objects would have validated that they are in a good state when created by the repository. Then the application service will use a combination of those entities (some of them could be aggregate roots), to perform the domain use case. If the domain use case requires changing X, Y and Z, the application service will ask X, Y and Z entities/aggregate roots if the current use case request of changes can be made to X, Y and Z, and if so, how should it be made. Finally, the application service will commit those changes back to the repository.

If some change spans across entities or aggregates, the application service will use a domain service to ask if the change can be made and if so how, and once again will use the repository to commit those changes.

If a domain use case spans multiple bounded contexts, that means it requires information or changes across bounded contexts, this is called a process, and you can have a process service manage the full process life-cycle, it will make use of application services of multiple bounded contexts to coordinate the full process across all bounded contexts.

Finally, the application service can also use other application services, could be other micro-services in a shared bounded context, that would imply they share the same domain model, or it could do so across to application services in other bounded contexts, in which case you'd want to model those within your own bounded context's domain model as well, you'd treat those other bounded contexts much like a repository in a way. The application service communicates with another bounded context to get info about that other context, it then creates a representation of that info within its own domain model, using its own entities and VOs, and aggregates, which will again validate that state within their context. Similarly, you can commit changes to your domain model to other bounded contexts by asking them to change accordingly. All this can be implemented with direct method calls, remote API calls, async events, shared kernel, etc.

And to answer why it is like so, that's because the whole point is building software that can evolve over time without it becoming slower to make changes to it and add/modify its behavior while retaining its current correctness with regards to its current functionality. A good way to do this is by making it a change in one place doesn't break things elsewhere. This is why bounded contexts exist, already changes are restricted to each context, so a change in one is less likely to break another. This is also why the domain model validates all changes to the domain state, so you can't change part of the state in ways that breaks other usage of it. This is why aggregates are used, to maintain a change boundary between the things that need one, and clearly not have one where it doesn't need one. Finally, by having the whole domain layer, with domain model and domain services, not depend on any infrastructure, like the repository (and thus the DB), a change to the DB or repository will also not be able to break your domain model or services.

P.S.: Also note I use the term "state" loosely. It doesn't have to be a static value; state could be the application of some dynamic computation or rules that generates state when requested. You can have something like totalItemsCount on some entity which computes it when asked about what is the current totalItemsCount for the entity. Again, the entity will make sure to return you valid state, that means it will know how to correctly count the total and make sure that what is returned is the correct application of the domain rules for totalItemsCount.

Airscrew answered 4/4, 2022 at 4:15 Comment(0)
P
1

Did this come from Eric Evans Domain Driven Design book, or did it come from elsewhere?

It's old stuff. Eric`s book just made it buzz a bit more.

Where are there some good explanations for the reasoning behind it?

Reason is simple - human mind gets weak when it faces vaguely related multiple contexts. They lead to ambiguousness (America in South/North America means South/North America), ambiguousness leads to constant mapping of information whenever mind "touches it" and that sums up as bad productivity and errors.

Business logic should be reflected as clearly as possible. Foreign keys, normalization, object relational mapping are from completely different domain - those things are technical, computer related.

In analogy: if you are learning how to handwrite, you shouldn't be burdened with understanding where pen was made, why ink holds on paper, when paper was invented and what are other famous Chinese inventions.

edit: To clarify: I'm not talking about the classic OO practice of separating data access off into a separate layer from the business logic - I'm talking about the specific arrangement whereby in DDD, Entities are not supposed to talk to the data access layer at all (i.e. they are not supposed to hold references to Repository objects)

Reason is still the same I mentioned above. Here it's just one step further. Why entities should be partially persistence ignorant if they can be (at least close to) totally? Less domain-unrelated concerns our model holds - more breathing room our mind gets when it has to re-interpret it.

Prandial answered 9/7, 2012 at 14:5 Comment(2)
Right. So, how does a totally persistence ignorant Entity implement Business Logic if its not even allowed to talk to the persistence layer? What does it do when it needs to look at values in arbitrary other entities?Goree
If your entity needs to look at values in arbitrary other entities you probably have some design issues. Perhaps consider breaking classes up so they are more cohesive.Loveliesbleeding
S
1

simply Vernon Vaughn gives a solution:

Use a repository or domain service to look up dependent objects ahead of invoking the aggregate behavior. A client application service may control this.

Sandra answered 21/8, 2017 at 7:28 Comment(3)
But not from an Entity.Twelfthtide
From Vernon Vaughn IDDD source: public class Calendar extends EventSourcedRootEntity { ... public CalendarEntry scheduleCalendarEntry( CalendarIdentityService aCalendarIdentityService,Jorge
check his paper @JorgeSandra
H
0

I learnt to code object oriented programming before all this separate layer buzz appear, and my first objects / classes DID map directly to the database.

Eventually, I added an intermediate layer because I had to migrate to another database server. I have seen / heard about the same scenario several times.

I think separating the data access (a.k.a. "Repository") from your business logic, is one of those things, that have been reinvented several times, altought the Domain Driven Design book, make it a lot of "noise".

I currently use 3 layers (GUI, Logic, Data Access), like many developer does, because its a good technique.

Separating the data, into a Repository layer (a.k.a. Data Access layer), may be seen like a good programming technique, not just a rule, to follow.

Like many methodologies, you may want to start, by NOT implemented, and eventually, update your program, once you understand them.

Quote: The Iliad wasn't totally invented by Homer, Carmina Burana wasn't totally invented by Carl Orff, and in both cases, the person who put others work, all togheter, got the credit ;-)

Hoffman answered 27/4, 2011 at 17:53 Comment(1)
Thanks, but I'm not asking about separating data access from business logic - thats a very clear thing that there is very wide agreement on. I'm asking about why in DDD architectures such as S#arp, the Entities are not allowed to even 'talk' to the data access layer. Its an interesting arrangement that I havent been able to find much discussion about.Goree
B
0

To cite Carolina Lilientahl, "Patterns should prevent cycles" https://www.youtube.com/watch?v=eJjadzMRQAk, where she refers to cyclic dependencies between classes. In case of repositories inside aggregates, there is a temptation to create cyclic dependencies out of conveniance of object navigation as the only reason. The pattern mentioned above by prograhammer, that was recommended by Vernon Vaughn, where other aggregates are referenced by ids instead of root instances, (is there a name for this pattern?) suggests an alternative that might guide into other solutions.

Example of cyclic dependency between classes (confession):

(Time0): Two classes, Sample and Well, refer to each other (cyclic dependency). Well refers to Sample, and Sample refers back to Well, out of convenience (sometimes looping samples, sometimes looping all wells in a plate). I couldn't imagine cases where Sample would not reference back to the Well where it's placed.

(Time1): A year later, many use cases are implemented .... and there are now cases where Sample should not reference back to the Well it's placed in. There are temporary plates within a working step. Here a well refers to a sample, which in turn refers to a well on another plate. Because of this, weird behaviour sometimes occurs when somebody tries to implement new features. Takes time to penetrate.

I also was helped by this article mentioned above about negative aspects of lazy loading.

Bailment answered 7/5, 2019 at 17:24 Comment(0)
V
0

Very late to the party but I'll give my 2 cents.

Repositories and Domain services abstracting REST API operations in the domain model can be a major disaster from performance standpoint. I would argue that nor domain service (despite said otherwise in Red Book!), nor aggregate should try working with them and that those two concepts should remain only in realm of Application Service which has a sole responsibility to communicate with outside world, no matter you use Layers or Hexagon (Ports & Adapters).

In this way all expensive I/O communication is allocated and completely controlled by one Application Service. It will:

  1. Prevent any kind of performance bottleneck.
  2. Prevent any kind of global access (Repositories are GLOBAL) within domain model.

Build proper object graph, use correct fetching strategies in the application service and just pass pure in-memory objects to rich domain model. Lazy loading will sneak into your code and hit you where it hurts most.

Vern answered 2/5, 2021 at 6:58 Comment(0)
M
-1

In the ideal world , DDD proposes that Entities should not have reference to data layers. but we do not live in ideal world. Domains may need to refer to other domain objects for business logic with whom they might not have a dependency. It is logical for entities to refer to repository layer for read only purpose, to fetch the values.

Mckinzie answered 7/11, 2013 at 14:4 Comment(1)
No, this introduces unnecessary coupling to the entities, violates SRP and Separation of Concerns, and makes it difficult to deserialize the entity from persistence (since the deserialization process must now also inject services/repositories the entity frequires).Twelfthtide

© 2022 - 2024 — McMap. All rights reserved.