6

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.

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
w0051977
  • 7,031
  • 6
  • 59
  • 87
  • 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 04 '17 at 18:14
  • Thanks. In your scenario; would IOffer be a repository object? – w0051977 Jul 04 '17 at 18:24
  • When the IOfferValueCalculator implementation is constructed you do not know what offers the member is entitled for. – w0051977 Jul 04 '17 at 18:24
  • Which is why you have to look them up from the database. `IOffer` is whatever mechanism you choose to lookup the necessary information. It's most likely a Repository object, yes. But remember, you don't need `IOffer` unless you decide to. Your `IOfferValueCalculator` could do the lookup from the database itself, if you so chose. Make the decision based on your specific situation, not on some random stranger on the Internet's notion of "best practice." – Robert Harvey Jul 04 '17 at 18:48
  • @Robert Harvey, I hear what you are saying - you have to compromise. However, I think there is a pattern I am missing here because everywhere I look says don't let the domain model access the database . – w0051977 Jul 04 '17 at 19:03
  • 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 04 '17 at 19:08
  • @Robert Harvey, here: https://stackoverflow.com/questions/5694241/ddd-the-rule-that-entities-cant-access-repositories-directly – w0051977 Jul 04 '17 at 19:10
  • It is widely accepted that domain objects do not *directly* go to the DB, i.e. are persistent igrorant, up to the point of your domain assembly not having a direct reference to an assembly that is your DAL. However, they can have interfaces injected in them that will access data, but I would not say that is very common. What I see more frequently is the domain model using an ORM with a lazy load - which, in essence goes to DB when you navigate the object model, but this is hidden under the covers so you have a feeling of navigating the object graph only – ironstone13 Jul 04 '17 at 19:21
  • @ironstone13, can you point me to an ORM with Lazy Load example? Perhaps you could write an answer? Thanks. – w0051977 Jul 04 '17 at 19:27
  • @w0051977: What specific trouble are you having with this? Domain objects, of the type ironstone13 describes above, are merely [DTO's](https://en.wikipedia.org/wiki/Data_transfer_object). That some other object or method accesses the database and populates those domain objects seems self-evident. There are many options for ORMs that offer lazy loading. Which do you prefer? The ideas being described in that Stack Overflow article amount to ordinary ***Separation of Concerns.*** – Robert Harvey Jul 04 '17 at 19:31
  • My use case is very similar to Jimmy Bogards. The domain model figures out what offers a member is entitled to. The details of the offers e.g. 'expiry date' are stored in the database. There are over 1,000 offers depending on a variety of factors. – w0051977 Jul 04 '17 at 19:34
  • You can do that in a dozen lines of code or so. You're not trying to build another Facebook, are you? Your difficulty lies, not in the interfaces, but in retrieving the correct offer. May I suggest that you're focusing on the wrong thing right now? Figure out how to retrieve the correct offer, and *then* decide which box you're going to stuff that functionality into. – Robert Harvey Jul 04 '17 at 19:35
  • Solving that use case you are describing by loading and iterating thousands of offers, materialized as domain objects is definitely not the way to go, you can surely use `IOfferValueCalculator` to implement (or delegate) to a query object that would run an optimized query against your db. – ironstone13 Jul 04 '17 at 19:38
  • Sounds like a decent plan to me. – Robert Harvey Jul 04 '17 at 19:39
  • Just make sure that no database abstractions leak from your `IOfferValueCalculator` (returning data reader is not a good idea), but instead you have a well defined contract in terms of your domain – ironstone13 Jul 04 '17 at 19:41
  • @ironstone13, could you provide a code example of this delegate? Perhaps in an answer. – w0051977 Jul 04 '17 at 19:43
  • He means that you're *delegating responsibility* for retrieving the proper Offer Value to a query object, not that you're writing a `delegate`. A query so optimized would contain the necessary `where` clauses and conditions such that it would only retrieve the data of interest. – Robert Harvey Jul 04 '17 at 19:53
  • Exactly, there is really not enough info to write a full answer, you just have your `IOfferValueCalculator` interface in your `DomainModel` assembly, and an implementation of that interface `OfferValueCalculator` outside your `DomainModel` assembly, perhaps in ApplicationLayer (if you're using layered architecture) and you just inject this implementation "down the call stack" from your ApplicationLayer, so your DomainModel stays agnostic of the implementation and does not care if your have a data reader there or an ORM – ironstone13 Jul 04 '17 at 19:55
  • @ironstone13 , but then the domain layer is accessing the database (via an injected dependency). Isn't that wrong? : https://stackoverflow.com/questions/5694241/ddd-the-rule-that-entities-cant-access-repositories-directly – w0051977 Jul 04 '17 at 19:58
  • 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 04 '17 at 19:58
  • @Robert Harvey, there appears to be a consensus that it is wrong. Plus Eric Evans wrote about it. – w0051977 Jul 04 '17 at 20:00
  • 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 04 '17 at 20:00
  • If you want to avoid this for some reason, you can sort of pre-populate the needed data in a "higher application layer" and just inject the results to your domain object method. It depends on how data - intense your application is, and other factors as well. So your `IOfferValueCalculator ` implementation could just contain the needed data, that was queried and injected by ApplicationLayer, instead of retrieving on the fly, when invoked from domain model – ironstone13 Jul 04 '17 at 20:01
  • Robert Harvey, the code is here: https://github.com/jbogard/presentations/tree/master/WickedDomainModels. There is no implementation of IOfferValueCalculator that I can find. – w0051977 Jul 04 '17 at 20:10

2 Answers2

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.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • Thanks +1 for the reference to separate of concerns and decoupling. – w0051977 Jul 04 '17 at 21:19
  • I was reading this question: https://softwareengineering.stackexchange.com/questions/330428/ddd-repositories-in-application-or-domain-service. 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 04 '17 at 21:22
  • Remember that, at the end of the day, DDD is a *design technique,* not a coding technique. – Robert Harvey Jul 05 '17 at 13:43
1

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.

user3347715
  • 3,084
  • 11
  • 16
  • 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. – user3347715 Sep 24 '18 at 17:59