Let's say we sell cars, customizable cars.
A customer chooses a CarModel and then starts configuring the CarModel. In our shop, she can only choose the color of the Steeringwheel.
Some CarModels can have more types of SteeringWheels than others.
Therefore, we have a Catalog which contains CarModels and SteeringWheels.
A customer can create a CarConfiguration. She chooses the model and then from the available steering wheels for that model she chooses the color steering wheel she likes.
class Catalog
{
public IReadonlyCollection<int> CarModels { get; }
public IReadonlyCollection<int> SteeringWheels { get; }
public void RemoveSteeringWheel(int steeringWheelId)
{
...
}
}
class SteeringWheel : AggregateRoot
{
public int Id { get; }
public string Color { get; }
public decimal Price { get; set; }
}
class CarModel : AggregateRoot
{
public int Id { get; }
public decimal Price { get; set; }
public IReadonlyCollection<int> SteeringWheels { get; }
public void AddSteeringWheel(int steeringWheelId)
{
...
}
public CarOrder CreateCarOrder(int steeringWheelId)
{
return new CarOrder(...);
}
}
class CarOrder : AggregateRoot
{
public int Id { get; set; }
public CarConfiguration CarConfiguration { get; set; }
}
class CarConfiguration : ValueObject
{
public int CarModelId { get; set; }
public int SteeringWheelId { get; set; }
}
For this to work there is an invariant that the available steering wheels for a car model must always be present in the catalog. To enforce this invariant, we must guard (at least) two methods:
AddSteeringWheelonCarModel; we can only add aSteeringWheelif it is available in theCatalogRemoveSteeringWheelonCatalog; we can only remove aSteeringWheelif it is not configured on anyCarModel.
How to enforce this invariant? CarModel does not know about the SteeringWheel collection on Catalog and Catalog doesn't know anything about CarModel's steering wheels either.
We could introduce a domain service and inject repositories into that. That service would be able to access the data from both aggregates and be able to enforce the invariants.
Other options are to create navigation properties and configure the ORM (Entity Framework Core in my case) to explicitly load those relations.
And probably many more, which I can't think of right now…
What are the most elegant/pure-ddd/best practice options to achieve this?