Domain-Driven Design – Saving Aggregates Without ORM: Best Practices

domain-driven-designobject-oriented

So for DDD folks there, Aggregate Roots are supposed to contain business logics only and exposed what is needed only.

In DDD Red Book by Vaugh Vernon, he used LevelDB and Hibernate as examples. LevelDB is key-value storage and Hibernate I think uses reflection.

However, if I don't want to use any of those, how am I going to save Aggregates?

3 of the easiest solutions I can think of are listed in the title:

  • Exposed public getters
  • Reflection
  • Inject repository to aggregate and have a method called save (Memento pattern?)

Let's imagine Payments:

  • CardPayment with cardNumber, expiration, cardHolderName
  • CashPayment with cashAmount
  • PaypalPayment with paypalAccountId

Each of those has their own unique properties but adheres to an abstract class/interface (won't go deeper for simplicity).

In my whole life, there are cases like this that can't be avoided especially when doing Repository where you really need to know what are you going to save.

Going with public getters, you might need to do an instanceOf checks in repository so you can cast and access the unique properties.

Going with reflection, it may not be a problem but feels like a hack…

Injecting repositoryObj to a save method seems to be the next best option, at least the Aggregate knows what properties to save but this violates DDD I think. It knows about persistence too much and save is not part of ubiquitous language.

I can be pragmatic and eat a cake but I want to know how it is done the pure OOP and/or DDD way.

EDIT:

Found an article from Vaughn Vernon on how to model Aggregates with Entity Framework. The article can be applied to anything else too, it's not really specific to EF. I'll just link to it to prevent longer O.P: https://kalele.io/blog-posts/modeling-aggregates-with-ddd-and-entity-framework/

Best Answer

Instead of updating the O.P, I will just post this as an answer so I can mark it as one.

I found the book Patterns, Principles, and Practices of Domain-Driven Design by Nick Tune, Scott Millett.

To quote from Chapter 21: Repositories:

If you are using a persistence framework that does not allow your domain model to be persistence ignorant, you need to take a different approach to the way you persist and retrieve your domain objects so they remain free of infrastructural concerns. There are a number of ways that you can achieve this, but all affect the domain model and the shape of your aggregates. This is, of course, the compromise you need to make your application work.

  • Public Getters and Setters

A simple method to enable the persistence of domain objects is to ensure that all properties have public getters and setters. The problem is that this leaves your domain objects open to being left in an invalid state because clients can bypass your state-altering methods. You can avoid this through code reviews and governance. However, exposing your properties does make it easy to persist your domain model because your repository has easy access to the domain object’s state.

  • Using the Memento Pattern

If you don’t want to expose your domain model’s properties and want them to be totally encapsulated, you can utilize the memento pattern. The Gang of Four pattern lets you restore an object to a previous state. The previous state in this instance can be stored in a database. The way in which it works is that an aggregate produces a snapshot of itself that can be persisted. The aggregate would know how to hydrate itself from the same snapshot. It’s important to understand that the snapshot is not the data model; it’s merely the state of the aggregates, which again is free from any persistence framework. The repository would still have to map the snapshot to the data model.

  • Event Streams

Another way to persist your domain model is to use an event stream. Event streams are similar to the memento pattern, but instead of taking a snapshot in time of your aggregate, your repository persists all the events that have occurred to the aggregate in the form of domain events. Listen for events from the domain, and map them to a data model. Again, you need a factory method to reconstruct and replay these events to rebuild the aggregate.

And in the end

BE PRAGMATIC

In all the strategies of domain-model persistence, it pays to be pragmatic. A pure domain model should be persistence ignorant in that it should be immune to changes required by the needs of any underlying persistence framework. Purity is good in theory, but in practice it can be difficult to achieve, and sometimes you must choose a pragmatic approach.