4

Please see the code below, which I took from Jimmy Bogards wicked domain models:

public Offer AssignOffer(OfferType offerType, IOfferValueCalculator valueCalculator)
        {
            DateTime dateExpiring = offerType.CalculateExpirationDate();
            int value = valueCalculator.CalculateValue(this, offerType);

            var offer = new Offer(this, offerType, dateExpiring, value);

            _assignedOffers.Add(offer);

            NumberOfActiveOffers++;

            return offer;
        }

Unfortunately he has not posted the code contained in the implementation of: IOfferValueCalculator. I have a feeling that the class will contain logic that determines what offers a member is entitled to. However, surely the domain object has to go to the database (via the repository) to get details of the offers e.g. date expiring etc. However, I understand that the domain layer in a rich domain model should not go to the database. Therefore how does the domain layer get the offers? Are all the offers injected into the domain layer by the application layer?

I believe there is a pattern here that I am missing? I already know of Command Responsibility Segregation. However, I believe in my case that the command object needs to access the database.

  • 1
    how does the domain layer get the offers? -- By whatever mechanism you choose to retrieve them from the database. If your IOfferValueCalculator implementation requires some decoupling from your data store, then you would pass something like an IOffer implementation into its constructor. You can even involve an IoC container if you like. How you choose to do this is entirely up to you. There's no "right" way to do this; there's only the way that best meets your specific requirements. Personally, I would favor a simple approach, unless this application is very large. – Robert Harvey Jul 4 '17 at 18:14
  • Thanks. In your scenario; would IOffer be a repository object? – w0051977 Jul 4 '17 at 18:24
  • 2
    Can you point to some specific examples on the Internet that say this? Accessing the database from your domain model is kind of the whole point. – Robert Harvey Jul 4 '17 at 19:08
  • 2
    Why would it be wrong? Because some stranger on the Internet says it is? Think about what you're trying to accomplish, and arrange your classes so that you accomplish that. – Robert Harvey Jul 4 '17 at 19:58
  • 1
    It's not sufficient for someone on the Internet to say that some way of doing things is wrong, or even that there is a consensus. You have to understand why. – Robert Harvey Jul 4 '17 at 20:00
12

If you need to access a database to solve your particular problem, then... Well, you need to access a database. Doesn't matter what Jimmy Bogards, Eric Evans or anyone else says. If their rules don't allow you to do that, then break their rules, follow someone else's rules, or make up your own.

When you read some rule on the internet that someone has devised, try to understand the motivations behind it first, before you apply it. That way, you have a reasonable assurance that the application of said rule aligns with your own needs and expectations.

Rules like the one you cited from Eric Evans generally have one of two motivations:

  1. Separation of Concerns, and
  2. Decoupling.

Separation of Concerns mostly means "write each class or method so that it has one area of expertise and does that well." Often, that means writing some module that specializes in database access only, allowing other modules/classes to focus on their own specific concerns without being concerned with the details of data retrieval. It also means that your classes can be "persistent-ignorant," meaning they don't have to know how to save or retrieve themselves from a database, especially some specific database technology.

Decoupling, within the context of database access, generally means one of two things:

  1. You want to isolate data retrieval from the rest of the system, just in case you might someday change out the database implementation for something else, or
  2. You want to mock the data retrieval mechanism for unit testing purposes.

There are two commonly-accepted ways to access data from a database. The first way is to use CRUD operations (create, read, update, delete). The second is to provide a Business Logic Layer. A business logic layer has methods on it like GetOffers() which provides more intelligent (and optimized) retrieval of data than create, read, update and delete.

Now then. Jimmy Bogard's Wicked Domain Models:

What is a domain model?

An object model of the domain that incorporates both behavior and data.

Why should I care?

A lot of times – you shouldn’t.

When you should – complex domain, or a long-lived project where behavior gets added piece by piece.

So even Jimmy Bogard says "use it only if you need it."

Many software developers today suffer from "Pattern-Matching disease." They think everything in software development is a pattern, and that writing a program is an exercise in stitching patterns together. Unfortunately, that's not quite the way it works, nor is slavishly following somebody's ideas about "best practices" without understanding why.

  • Thanks +1 for the reference to separate of concerns and decoupling. – w0051977 Jul 4 '17 at 21:19
  • I was reading this question: softwareengineering.stackexchange.com/questions/330428/…. I think this is a good point in the answer: "ubiquitious language". GetOffers is part of the ubiquitious language of the domain. Therefore I believe a repository should be injected into the domain service. – w0051977 Jul 4 '17 at 21:22
  • Remember that, at the end of the day, DDD is a design technique, not a coding technique. – Robert Harvey Jul 5 '17 at 13:43
0

I know this is an old question, but I'm going to offer a different perspective.

No.

You should not inject your domain model with an IRepository in any situation. If you have found yourself at a point where you think it has become necessary, what you need to do is take a careful look at the design of your system. It is far more likely that you have simply failed to crunch knowledge and taken a shortcut somewhere than reached the limitations of the fundamental principals of application architecture.

In the example above, I would expect that when the IOfferCalculator is created (in command handler), it is injected with the appropriate data so that it can make its decisions. In another way, instead of injecting the IRepository, one simply injects the data that repository is going to retrieve.

Application commands are deterministic. Somewhere, something knows, given an input, what the output should be. Think of every command handler as a 3-step process (in your application layer):

  1. Retrieve ALL data necessary to carry out command.
  2. Coordinate domain model (mutate state)
  3. Commit transaction

Simply put, it is nearly always possible to know what data is necessary (in step 1) in order to properly coordinate your domain (for step 2). Needing to retrieve data part-way through a transaction can only create problems. Push that data access "up and out" of your domain where it belongs. This facilitates a cleaner, more cohesive design.

It can also make testing easier. Now instead of testing that a mock IRepository will return the correct data (given inputs) and your IOfferCalculator implementation, you can directly test the IOfferCalculator against any data set you wish. This gets closer to the heart of your application.

I agree with many of sentiments @RobertHarvey expresses in their answer, but I would strongly caution the idea of making up your own rules. I get that many developers suffer from the idea that every time they reach a stumbling block, it must be that they are missing some "pattern" or "principal", but the reality is simply that many developers don't truly understand how to design systems.

I implore all of those who read this to, instead of looking for that missing technical detail in some blog (or wherever) about whether or not it's okay to inject repositories (or [insert problem here]), take a step back and try to understand your system. Figure why you have reached this impasse. Often, the problem was created by an upstream decision.

  • What should one do if the necessary data is dependent on some domain calculation? Especially one that involves the result of a mutation, possibly within in an external system? – Angus Goldsmith Sep 24 '18 at 16:35
  • @AngusGoldsmith Without an specific example it's difficult to elucidate a solution. Generally speaking though, one can hydrate as much state as necessary or use events to help atomize use-cases further. Say your domain is a lottery system, and you wish to email whomever won your lottery. Because you don't want to load all Tickets or Buyers into memory when a SelectWinningLotteryTicket command comes in, you could register an event listener to the WinningTicketSelected event to handle emailing the Buyer. Although domain logic selects the Ticket, we decouple the emailing. – king-side-slide Sep 24 '18 at 17:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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