DDD: How to avoid breaking encapsulation and leaking technical concerns to the domain during model rehydration

aggregatedesign-patternsdomain-driven-designpersistenceruby

When applying DDD principles in Ruby, I feel that Active Record pattern ends up polluting the domain model, while I'm not sure how to implement JSON deserialization without breaking encapsulation.

The Data Mapper pattern could be an alternative, but I haven't found any satisfactory Ruby implementation that fulfills my Database requirements.

ActiveRecord usual implementation introduces persistence concerns on the domain entities, while separating domain entities and Active Record entities creates a duplicated class hierarchy, among other additional complexities.

With JSON deserialization, I either need to allow an empty constructor and public setters, or I need to give the Aggregate class the responsibility of deserializing JSONs, which doesn't seem as a legitimate domain concern, but more of an infrastructure concern. I understand this should be a Repository responsibility, but I don't see how to implement this without breaking the encapsulation of the aggregate class.

Perhaps the alternative would be to use Event Sourcing instead of persisting the aggregates states, or only persisting the aggregate state for read purposes. Is this correct?

Best Answer

I don't use Active Record in my projects as this pattern is forcing me to break my golden DDD rule: keep the aggregates pure, side-effect free.

If you do not split the Read from the Write, then you have a mixed aggregate. You need to be able to command it and query it. The problem is not querying a single aggregate, you can easily do that, after it is loaded from the repository, the problem is finding, filtering, browsing a list of aggregates because it forces you to break the aggregate encapsulation by depending on its internal structure. You need to know its structure in order to apply filters based on properties values. Every time you modify a property on the aggregate you need to adapt the filtering too.

If you split the Read from the Write (i.e. using CQRS and Event sourcing), then you can do all of the above without breaking aggregate encapsulation because you will not query the aggregate, you will query a special designated read-model, without breaking its encapsulation (because it applies its own filters on its own fields).

So, using Event sourcing helps a lot regarding your problem.

Of course, there are other problems that come with Event sourcing.