0

I am currently reading about domain driven design and n-tier architecture with a service layer and comparing between them trying to understand how each architecture is executed.

For n-tiers (specifically 3-tier) architectures, we have a service classes that bundle related logic together such as validation and doing operations in a system, these service classes then use a repository to deal with persistence related operations. This is straight forward

Now when it comes to Domain Driven Design, the goal is to have both data and behavior encapsulated in a single entity (the model class)

  1. I thought ok, I will use an ORM to define the model class as an entity and then place the methods that act on the data within the model class but I read somewhere that it's not a good idea because ORMs have cached state and other leakages in it's abstraction that can't allow us to just assume that the data is populated correctly and can be acted upon like a POJO (I come from the java world),

  2. I also thought about injecting the repository in the model class and then using it to read and write but wouldn't that be adding persistence logic to the model and that's something we don't want to do?

  3. I read somewhere that we could use a layer of Dumb DTOs that are used to retrieve data and then pass the data form the DTOs to a higher level layer and starting from that higher level we could use our objects while being blissfully oblivious to the persistence details but this still poses the question of how will we write back the data?, who will call these DTOs and fill the data? and wouldn't that cause a lot of boiler plate code of mapping between the results of the data retrieved form the db and filling domain objects?

  4. there is also the problem of how to deal with what Domain driven design call Aggregates and how should a model deal with logic that incorporates other models in it such as a validation rule for logical entity x that depends on the state of logical entity y

I am really confused on how to stay faithful to object orientation when one is building say a java backend which will of course will need to access some form of persistence. Thanks in advance.

edit: this question is not a duplicate of DDD repositories in application or domain service because that question is concerned with how to deal with persistence and repositories inside a domain service while I am discussing persistence inside a domain model entity and discussing how to deal with persistence while avoiding a service layer or keeping it to a minimum

TL;DR How is reading and writing (persistence) get handled in domain driven design such that the domain are as faithful to object orientation as possible and for the code to mainly use method that belong to classes that represent the domain instead of having a procedural style service layer centered architecture?

asked Sep 3 at 11:23
New contributor
Errors for life is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
12
  • DDD can model persistence, if persistense is a part of domain. For example, if there is a document that should be stored indefinitely, it can have archive() operation and access to an Archive repository. When persistence is not a part of domain, why would you need to persist an object? Commented Sep 3 at 12:29
  • 1
    This question is similar to: DDD repositories in application or domain service. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented Sep 3 at 12:32
  • Tree model in Domain Driven Design Commented Sep 3 at 12:33
  • 1
    How could I map a complex DTO to a Domain Aggregate in a Repository? Commented Sep 3 at 12:48
  • @Basilevs, I am currently thinking about the design of a jira like ticketing system, in such a system persistence is not a part of the domain but it is required to read information from a database to display and or do operations on and then write back Commented Sep 3 at 13:43

2 Answers 2

1

Now when it comes to Domain Driven Design, the goal is to have both data and behavior encapsulated in a single entity (the model class)

I would challenge this. If you have your Models as data structs and put the logic in "Domain Services" then all your problems (from this question) will be solved.

The structure will be something like

App
 Construct Repository
 Construct Business Logic Service
 Loop Over things to do
 Fetch Model from Repo
 Call Logic, passing in Model Model
 Save model back to Repo

If you have this, but then want a more object orientated approach, you can just move the code blocks around to get:

App
 Construct DTO->Model and back Mapper
 Construct Repository, passing in Mapper
 Loop Over things to do
 Fetch Model from Repo
 Call Model.Logic
 Save Model back to Repo

You'll find the code is pretty much identical, You've just moved it into different objects.

The problem with this style, happens when you have more than one logical operation. The you have to either put both on the same Model, or start having Model versions for each Bounded Context. Rather than just having a Logic Service class per operation.

Problem 1 and 3 are solved by having the DTO mapper hidden inside the repository. This separates the Model from the ORM, although this should be possible to do with the correct setup in most ORMS without an extra mapper.

Models should have all the data for the aggregate in them, so you can perform operations purely in memory without recourse to the database. So you should never have problem 4

This should also help you with problem 2. As you have all the data in the Model, there should be no need to inject a repo into your Model or Logic

answered Sep 3 at 13:11
6
  • so for the first approach, wouldn't this just be a 3 tier with a service layer, in DDD the domain service is supposed to be a one off thing for the edge cases where functionality cannot fit in a single logical entity. As for the second approach, what do you mean problems happen when there is more than one logical operation? a model entity can have a bunch of methods in it each representing a logical operation or am I misunderstanding sth here? finally, how would you deal with writing back the aggregate to the db? Commented Sep 3 at 13:56
  • I argue that, that kind of OOP isn't essential to DDD. If you drop that criteria, then your practical issues vanish. Commented Sep 3 at 14:12
  • when there are multiple unrelated methods on the same Model class it can become bloated Commented Sep 3 at 14:12
  • writing the agg back to the db isnt difficult so im not sure I understand where you are coming from on that one? Commented Sep 3 at 14:13
  • my question about writing back is where should I write it back and who is responsible for doing so Commented Sep 3 at 15:01
-1

Now when it comes to Domain Driven Design, the goal is to have both data and behavior encapsulated in a single entity (the model class)

Yeah, this is essentially correct; when you separate "data" from "behavior", then people begin to complain that you have an anemic domain model, aka "you're doing it wrong".

As far as I can tell, there aren't any good answers to the persistence question, just a number of kind of ok answers with different trade-offs.

The most "straight forward" approach would be to use something like the Memento pattern from the Design Patterns book (Gamma et al).

after = Factory.create( before.memento() )
assert after.isInSameStateAs(before)

Which is to say, you get information out of your entities via a query that produces an immutable VALUE OBJECT, and then you solve the problem of how to persist that (aka kick the can down the road). Now, in the design patterns book, the memento is typically opaque (ie bytes), but there's no particular reason that it couldn't instead be a "document" or a "message" or a "record", with affordances that allow you to get the information you need out of it.

A variation of this that allows you to shuffle some of the dependencies around is to use a builder interface

after = Factory.create( before.memento(builder) )
assert after.isInSameStateAs(before)

In effect, you define a protocol that allows the entity to pass information, and then choose the appropriate implementation .

If you look at dddsample-core (an open source DDD demonstration piece originally built in collaboration with Eric Evans's company), you can see that they "just" couple their models to their (current) persistence strategy.

public interface CargoRepositoryJPA extends org.springframework.data.repository.CrudRepository<Cargo, Long>, CargoRepository

and the Cargo entity has a bunch of jakarta.persistence annotations.

In some designs, you can combine these in a slightly different way; instead of passing around some opaque thing that only the model understands, you can store in the model an opaque thing that only the persistence strategy understands.

class Cargo<OpaqueHandle> {
 OpaqueHandle opaqueHandle
 <T> T storeIt( StorageProtocol<OpaqueHandle, T> protocol ) {
 return protocol.withHandle(this.opaqueHandle)....;
 }

The way you might use this is that your persistence uses an O/RM to fetch an anemic entity, and you read information out of it -- passing the reference to the anemic entity as a "memento" that the Cargo domain model carries around, and then at persistence time your storage protocol gets a handle to that entity back (along with the domain information we care about remembering) and now you "dirty" the O/RM entity by copying information into it, and then saving it.

Another variation (and I never see this one outside of my own experiments) comes from recognizing that your "domain logic" usually can be written independently of your internal data representation, so you separate them -- your domain logic goes into an abstract class that expresses the rules, and has place holders for the data affordances. What your persistence code actually gives you is a derived class, with a specific data model suitable for persistence and affordances that give the domain logic read/write access to the data model.

If you are feeling very fancy, you could implement your domain logic using traits, rather than an abstract class.

And then there are those who take the position that the entities rfc::2119::SHOULD be anemic, because their single responsibility is manipulating the data model (in the same way that a map or list is responsible for manipulating some internal data structure) and therefore shouldn't also be responsible for domain logic (which changes for completely unrelated reasons).

answered Sep 4 at 4:20
1
  • 1
    Bot memento and annotations indeed couple model to persistent storage. What is left unsaid, is that author will have to change their address after doing so. Commented Sep 4 at 14:21

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.