I am in the process of migrating a "big ball of mud" (BBOM)-like system towards a system based on the ideas of domain driven design.
After various iterations of refactoring, domain aggregates/entities are currently modelled using inner state objects, as described by Vaughn Vernon in this article, for example: https://vaughnvernon.co/?p=879#comment-1896
So basically, 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;
[...]
}
As of today, the state object in this system is always a database table wrapper coming from the currently used proprietary data access framework of the application, which resembles an Active Record pattern. All the state objects therefore inherit from a base class part of the data access framework. At this time, it is not possible to use POCOs as state object, Entity Framework or any of that.
The application currently uses a classic layer architecture in which the infrastructure (including the mentioned table wrappers / state objects) is at the bottom, followed by the domain. The domain knows the infrastructure and the repositories are implemented in the domain, using the infrastructure. As you can see above, most entities contain a public constructor for conveniently creating new instances inside the domain, which internally just creates a new state object (because the domain knows it).
Now, we would like to further evolve this and gradually turn the architecture around, resulting more in an "onion" kind of architecture. In that architecture, the domain would only contain repository interfaces, and the actual implementations would be provided by the infrastructure layer sitting on top of it. In this case, the domain could no longer know the actual state objects / database table wrappers.
One idea to solve this would be to have the state objects implement interfaces defined by the domain, and this actually seems like a good solution for now. It is also technically possible because, even though the state objects must inherit from a special data access base 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) instantiates a new Customer, it can easily pass in the database wrapper object which implements the ICustomerState. So far so good
However, when creating new entities in the domain, it is no longer possible to also create the inner state object as we no longer know the actual implementation of it.
There are several possible solutions to this, but none of them seem really attractive:
- We could always use abstract factories for creating new entities, and those factories would then be implemented by the infrastructure. While there are certain cases where a domain factory is appropriate due to the complexity of the entity, I would not want to have to use one in every case as they lead to a lot of clutter in the domain and to yet another dependency being passed around.
- Instead of directly using the database table wrappers as state objects, we could use another class (POCO) which just holds the values and then gets translated from/to database wrappers by the infrastructure. This might work but it would end up in a lot of additional mapping code and result in 3 or more classes per database table (DB wrapper, state object, domain entity) complicating maintenance. We would like to avoid this, if possible.
- To avoid passing around factories, the constructor inside the entity could call some magic, singleton-like
StateFactory.Instance.Create<TState>()
method for creating the inner state object. It would then be the infrastructure's responsibility to register an appropriate implementation for it. A similar approach would be to somehow get the DI container and resolve the factory from there. I personally don't really like this sort of Service Locator approach but it might be acceptable in this special case.
Are there any better options that I'm missing?