1

Consider the domain entity, Order, which can be persisted by the OrderRepository. Orders maintain state that is stored in the database. However, it does not expose all of it directly. That is, parts of its private state is not accessible in the repository, when it should be mapped to database records. How does one go about allowing the repository access to this while restricting it for the rest of the domain?

AutoMapper and the like is not an option. Moving the repository to a separate project and declaring the members internal instead of private is not an option either.

1
  • You need to expose the privates somehow. The question is how much big of a problem it is and how much effort you want to put into making it.
    – Euphoric
    Oct 23, 2023 at 19:05

4 Answers 4

4

The problem here is that several guidelines which can be argued to be good advice (individually) simply contradict one another when combined. This isn't a failing of the individual guidelines, it's more that you can't always have your cake and eat it too.

If you subscribe to truly encapsulating everything about your domain objects that you can make private, then you inherently choose to make this information unavailable to other layers. When you've chosen to do so, it's an inevitable fact of life that your persistence layer cannot read this encapsulated state.

If you keep this data private, then by logical definition only that domain model can ever use the data to persist it. This would mean that your domain needs to occupy itself with persisting its state, which means that the domain would depend on the persistence mechanic, which starts violating the other guideline whereby the domain does not depend on anything.

Simply put, if the domain hides all of its private details and the domain does not depend on anything, then the privates are truly private and no one can do anything with them.

This is an impasse. How you resolve this impasse is up to you, based on which of these conflicting constraints you'd prefer to loosen.

Personally, I take one of two stances:

  • In general, I avoid true privacy and make these values at least publically readable (not writable). Not necessarily every value, but at least the ones that are reasonably needed for sensibly persisting the domain object.
  • In specific use cases, I'll design a properly encapsulated domain model and a mirrored "public DTO" model which essentially stores the same data but makes it all publicly gettable and settable. I allow the domain model to convert itself to this DTO. This way, you can work with your encapsulated domain model, but you can make a "snapshot" of this model in a way that it can easily be persisted.

The second bullet point I really only do for cases where the inner state is so complicated that making it public would significantly hamper a dev who's relying on IntelliSense to only use the "true public" features. It's not very common that I need to resort to this but it very much depends on how complex you allow your domain models to become.

1
  • This was exactly what I needed, I think. Thanks a lot!
    – STHA
    Oct 24, 2023 at 18:04
1

Besides a getter method, I believe having private properties usually means you're going to ask the object to do things for you, because it has knowledge of its internals (a principle called Tell, Don't Ask, although not everyone who uses private properties wants to apply the principle): so you'd have a method in your entity for returning a DTO for the repository (like a factory method); but beware this is not DDD-friendly, because it violates the idea of the domain knowing nothing about external layers.

6
  • I already considered that and arrived at the non-DDD-friendly conclusion as well. Is there really no way of having it both ways? I mean, I feel “Tell, don’t ask” is certainly DDD-friendly, so why does it require other parts of the design to be non DDD-friendly?
    – STHA
    Oct 23, 2023 at 19:27
  • I believe that feeling comes from knowing that the possible DTO to be returned from the entity is OOP-valid -- for OOP, "Tell, Don't Ask" makes a lot of sense, since implementation ends up hidden --, but the problem is that we're not talking about technicalities only, we're talking about layers, and, in this case, the domain needs to have a factory method for the DTO which will have knowledge about what the repository needs, because it'll have to return some properties and some not, so there is a logic for which properties the DTO will be composed of, but that logic shouldn't be on the domain. Oct 23, 2023 at 19:34
  • Now, back to the problem. When you build your entity (the one with the private property), how come the property is private but needs to be persisted on the DB? I mean, if its private, it won't change along the flux of your application, so can't you have a DTO started with this value (coming from the same data you use to build your entity) and then update the DTO as the rest of the data that will compose it change? Oct 23, 2023 at 19:40
  • It may change along the flux. As a side effect of “Tell”
    – STHA
    Oct 23, 2023 at 19:43
  • Oh boy... so, for me, it would be either a getter method or the DTO factory method. Hope anyone has a more elegant approach Oct 23, 2023 at 19:47
1

If you want to keep your actual state private, you can consider event sourcing the aggregate. That way you don’t persist the current state, but all events that happened in the lifetime of the aggregate. This means you don’t need any public properties, you only expose events. To load the current state, pass a list of all previous events to the aggregate.

There are various resources how to do this, but note this requires quite a different architecture than most people are used to. You will probably want to combine this with separate read-models, for example, to ensure read performance.

2
  • I worked on a project where we did exactly this. Now however, i am merely advocating the introduction of “proper” entities in a anaemic system that until now has achieved things by passing around the DTOs coming straight out of a Dynamics database. So I think event sourcing is out of scope.
    – STHA
    Oct 23, 2023 at 21:23
  • I feel your pain, when you’re struggling to convince colleagues about basic things like encapsulation, event sourcing is light years away.
    – Rik D
    Oct 23, 2023 at 21:44
1

Writing information to /dev/null is a waste of everyone's time.

If you are going to have entities whose lifecycles extend beyond a single executable session, then you need to have some mechanism for copying information from the transient data representation to a durable data representation, and vice versa.

The most straight forward approach is to use the Factory pattern (as described in Chapter 6 of the blue book). The obvious variation is described there - our persistence layer has some information, and we pass that information to a factory, and it returns to us a handle to a transient entity assembled from that information.

The same variation works in reverse - we pass a factory to the domain entity, the domain entity passes a copy of its transient data to the factory, and then returns to the caller whatever the factory produced.

In a language with generics, your design might look like

public class Order {
   public interface SomeFactory<T> {
       T build(OrderId id, Array<LineItems> items);
   }

   public <T>
   T verb(Order.SomeFactory someFactory) {
       return someFactory.build(this.id, copy(this.items));
   }
}

Then, at some location convenient to your OrderRepository, you create an implementation that extends Order.SomeFactory and uses the information to produce a representation of the information that you need to store the data correctly.


Note: you won't find anything like this in the blue book, as far as I can tell, because (a) Evans was assuming O/RM and (b) at the time it was written Java didn't have generics.

3
  • I like this. However, this requires the domain entity to know of some DTO, T, that is used for mapping back and forth between the persistence layer. Right?
    – STHA
    Oct 24, 2023 at 18:01
  • With generics? No. The definition of the DTO doesn't need to be visible to Order - you can put the DTO in a completely separate module, and that module doesn't need to be available when Order is compiled. So your dependencies all point the right direction. Oct 24, 2023 at 20:19
  • 1
    Gotta try this!
    – STHA
    Oct 24, 2023 at 21:31

Your Answer

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

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