Reputation: 43
I am experimenting with Domain Driven Design and Event Sourcing. I am planning to use (developing in C#) NServiceBus, JOliver's EventStore and NES to bind them. I already have the infrastructure working for a simple case (one aggregate root with value objects only).
I am reading Evans blue book and I am trying to develop a simple domain model, with examples taken from my work field (ERPs and CRMs for HVAC maintenance companies).
I am modelling a simple sub-domain, namely HVAC machines and relationships between them. Machines come in various types, e.g. furnaces, burners, air conditioners, compressors, generic components. Each machine can have multiple children machines. All machine types share some common data and some common behaviour. But each type has additional data and specific behaviours, for example you can add a Burner object only to a Furnace.
The first result of my analysis is that each machine should be an aggregate root (which inherit from AggregateBase in NES) because it must be possible to hold references to a specific machine (e.g. for inserting repair records which involve a single machine, faults recording, etc.), and also to reduce concurrency problems in large machine trees.
My hypothesis is thus the following:
public class Machine : AggregateBase
{
public DateTime InstallationDate { get; private set; }
public Guid ManufacturerId { get; private set; }
public Guid ModelId { get; private set; }
}
public class Furnace : Machine
{
public List<Burner> burners { get; private set; }
// other furnace properties
public void AddBurner(Burner burner)
{
// perform validation
this.Apply<BurnerAdded>(x=> x.burnerAdded = burner);
}
public void Handle(BurnerAdded @event)
{
this.burners.Add(@event.burnerAdded);
}
}
public class Burner : Machine
{
// burner specific properties/methods
}
but I have some doubts:
Is this a correct way to represent my domain? I read that class inheritance is discouraged, but this seems to me to be a perfect case to use it (a Burner IS A Machine, so is a Furnace). I will limit to one level of inheritance only.
Is it possible to implement class inheritance with Event Sourcing? With the proposed technological stack in particular (nServiceBus, EventStore, NES)?
How should I perform the Add of a child machine (e.g. a Burner to a Furnace)? This operation can be divided in two:
If I make the child machine reference the parent, the parent loses the list of children machines (which is needed for validation), I cannot query the event sourcing repository for other properties than the Guid.
Thanks in advance for any contribution to the discussion,
Upvotes: 2
Views: 1296
Reputation: 37719
I would avoid inheritance. Instead, create a single Machine aggregate and create a descriptor object that this machine will reference. The descriptor would be a value object that can discriminate between the different machine types. Use inheritance with this object if absolutely necessary, but don't use inheritance for the aggregate itself.
The ability to use class inheritance with ES is limited only by the serializer. ES does not dictate how the data is serialized, but if you use something like Protobuf, or Newtonsoft.Json, they support inheritance. However, in the JSON case, use of inheritance does place $type attributes in the JSON output.
This depends on whether a Burner needs to exist independently of the Furnace. If no, then it is a value object or entity part of Furnace aggregate and should be persisted in its entirety with the Furnace. If yes, then it must be an aggregate and should first be created and then added to the Furnace. This can be implemented with an NSerivceBus saga wherein the AddBurnerToFurnaceCommand is handled by first starting the corresponding saga, which sends a command to create a Burner. Once the Burner is created, create the association between a Burner and a Furnace. The Furnace would just reference the Burner by Guid. In ES, all queries are usually handled via projections and only behaviors invoke the event store for the canonical aggregate by ID.
Upvotes: 3
Reputation: 28016
For #1, I would say no. The aggregate root in your example should be Furnace (only). Burner should be modeled as a collection on Furnace, as you currently have it, but shouldn't be an aggregate root. I don't see the inheritance as a problem per se, except for the fact that your burner is now an aggregate root via inheritance (since Burner => Machine => AggregateBase).
If burners only exist in the context of Furnaces, you probably don't need a Burner repository--you will always add a burner to a furnace. I'm not clear if the creation of a new burner is itself interesting, and requires its own event. The answer to that question will drive whether you need both events or just the "Added" event.
Upvotes: 3