I am in the process of migrating a βbig ball of mudβ (BBOM) system towards a system based on domain-driven design ideas.
After various iterations of refactoring, domain aggregates / entities are currently modeled using internal state objects, as described by Vonn Vernon in this article, for example: https://vaughnvernon.co/?p=879#comment-1896
Thus, an entity might look like this:
public class Customer { private readonly CustomerState state; public Customer(CustomerState state) { this.state = state; } public Customer() { this.state = new CustomerState(); } public string CustomerName => this.state.CustomerName; [...] }
Today, the state object in this system is always a shell of the database table, based on the currently used proprietary data access structure of the application, which resembles the Active Record template. Thus, all state objects are inherited from the basic part of the data access structure. It is currently not possible to use POCOs as a state object, Entity Framework, or any of them.
Currently, the application uses the classic layer architecture in which the infrastructure (including the specified wrappers / state objects) is at the bottom, and then the domain. The domain knows that the infrastructure and repositories are implemented in the domain using the infrastructure. As you can see above, most objects contain a public constructor for conveniently creating new instances within the domain, which internally simply creates a new state object (because the domain knows it).
Now we would like to once again develop this and gradually turn the architecture around, resulting in a bow architecture. In this architecture, the domain will contain only the repository interfaces, and actual implementations will be provided by the infrastructure layer located on top of it. In this case, the domain can no longer know the actual wrappers of the state / database object table.
One idea for solving this problem would be for state objects to implement the interfaces defined by the domain, and at the moment this really looks like a good solution. It is also technically possible because, although state objects must be inherited from a special base data access class, they are free to implement interfaces. So the above example would change to something like:
public class Customer { private readonly ICustomerState state; public Customer(ICustomerState state) { this.state = state; } public Customer() { this.state= <<<-- what to do here??; } [...] }
So, when the repository (now implemented in the infrastructure) creates an instance of the new Client, it can easily go through the database shell object that implements ICustomerState. So far so good
However, when creating new objects in a domain, it is no longer possible to create an object of internal state, since we no longer know its actual implementation.
There are several possible solutions for this, but none of them seem really attractive:
- We can always use abstract factories to create new objects, and these plants will then be implemented by infrastructure. Although there are certain cases where the factory domain is suitable due to the complexity of the object, I would not want to use it in each case, since they lead to a lot of interference in the domain and to another dependency that has passed around.
- Instead of directly using the shells of the database table as state objects, we could use another class (POCO), which simply stores the values ββand then is transferred from / to the database wrappers by the infrastructure. This may work, but it will lead to a lot of additional matching code and lead to 3 or more classes of the database table (DB shell, state object, domain object), which complicates maintenance. We would like to avoid this if possible.
- To avoid going around factories, the constructor inside the object can call some magic single-element method
StateFactory.Instance.Create<TState>() to create an internal state object. Then the responsibility for the infrastructure will be to register the appropriate implementation. Similarly, one could somehow get the DI container and allow the factory from there. I personally don't like this Service Locator approach, but it might be acceptable in this special case.
Are there any better options that I am missing?