Вопрос

I've been developing applications according to the principles of DDD for a while now, and, as many, I often run into issues when it comes to persisting an aggregate.

One of the main advantages of DDD is that it allows me to use the full power of OO design in my domain -- as such I want to use polymorphism and conform to the open-closed principle. I.e. I can extend my logic by adding new subtypes without requiring changes to the supertypes.

enter image description here

The problem comes when persisting this. Somehow I need to flatten this domain to some persisted representation, and then later restore it. How do I achieve this, without littering the Repository implementation with instanceof (or equivalent in your language of preference) all over the place. This is generally considered bad form, plus it violated OCP, at least in the repository itself. When I add a new subtype, I'm forced to add a case in the repository.

Is there an elegant way to handle this?

Это было полезно?

Решение

One of the ways is to use reflection. During application startup, the repository would inspect structure of your domain entities and automagically determine relational model and mapping of entities to this relational model. This is basically what most modern ORMs can do. The disadvantage of this approach is that it is extremely complicated to implement for non-trivial cases and one small change in domain model might result in drastically different relational model, which complicates migrations between versions.

Other than that, no, there is no way. If your repository depends on your domain model, it means the repository has to change when domain model changes. That is very definition of dependency. And OCP can only really work if things are not dependent of each other.

But there is a way to make the change easier and safer. Why is usage of instanceof a problem? There is nothing wrong with using it. Its just that if you add new subtype, you don't know where you have to extend the code to support this new subtype. But there is solution for that : a Visitor pattern. With visitor, when you add a new subclass, you also add new method to an interface. This means every class that implements this interface will become non-compilable and you will immediatelly know where everywhere you have to extend the code to support the new class. Yes, it will make adding new subtype harder, but it will make that change safer.

Другие советы

Mark Seeman provides the simple answer to your immediate pain point: at the boundaries, applications are not object oriented.

You might also read Greg Young's essay The Generic Repository.

One of the main advantages of DDD is that it allows me to use the full power of OO design in my domain -- as such I want to use polymorphism and conform to the open-closed principle.

You might need to let that go - there's not a lot of prior art encouraging the use of inheritance in constructing the domain model.

Composition, rather than inheritance, is the more commonly discussed pattern of re-use.

My suggestion: if you really think that you need inheritance, step back for a bit and challenge your domain model; it may be that the ubiquitous language is trying to show you that there are isolated concepts that your current perspective conflates.

If reflection isn't a dirty word for you, that might be the simplest path. Store full class names (or full serializations) in the database, and use your language's reflection system to instantiate based on name -- or deserialize "directly" if you're storing full serializations.

In many languages, it's just a few extra lines of code in your repositories (or base class) and you're basically done.

All other solutions (Chains of Responsibility, Factories, etc.) require you to register or maintain a list of subclasses explicitly somewhere in code or configuration. And, that's OK for many applications. If you want to avoid reflection, be explicit, or gain some configurability, you just need to decide where you do and don't want to maintain those lists.

I prefer to use some Aggregate-oriented NoSQL database, such like MongoDB, to persist aggregates. Thus you can keep the structure of your model and you don't have to make any mapping and the concrete type of the object is also included as metadata.

Лицензировано под: CC-BY-SA с атрибуция
scroll top