Introduction
Patterns like Aggregate Root, Interface Marker, Entities, Value Objects, and Unit of Work enforce a discipline that gives your application power.
Starting at the Root and Working Forward
The application domain is where your idea of a business solution resides. And at the root of your domain, where it all comes together, at the
aggregate, should be your Aggregate Root. The Aggregate Root serves as a marker. It is a marker interface pattern that marks classes in the domain as Entities.
The Aggregate Root is an interface, a contract within your domain that your Entities sign. They sign it by implementing from the interface.
By creating an Entity base class, it can inherit the interface and all your other Entities can inherit from the base class. Using the interface in defining the
base class means that that each of those classes will be marked with the Aggregate Root as Entities.
public interface IAggregateRoot
{
}
public abstract class EntityBase<TId> : IAggregateRoot
{
}
public class Order : EntityBase<Guid>
{
public Guid Id;
}
Entities are the things your application works on. Your application works on them as individuals, they have identity. An order entry works on orders.
The order entry system has orders as entities. Each order is individually important to the system and needs to have an identifier. The order class should inherit
Aggregate Root to define it as an Entity.
The base class in the above example uses generics so you can create classes that use integers or GUIDs.
GUIDs are the better choice because they can be created within the domain. To make sure an integer identifier is unique it needs to be retrieved from
the data store making it dependent on something outside the domain.
Things that are not the focus or our solution, things we do not work on individually are Value Objects. Shipping mode would a Value Object in an order
entry system. Shipping mode serves to help describe the Entity, order. It is not worked with by itself. It is a setting, an attribute, a value.
public abstract class ValueObjectBase
{
}
public class ShippingMode : ValueObjectBase
{
}
Entities and Value Objects are defined by how they are used. Shipping mode will be a Value Object in an order entry system, but it might be an Entity
in an administration system. It depends on how the object is used, whether it is being worked on individually.
When working with Entities we frequently perform Create, Read, Update, and Delete (CRUD) operations. Reads don’t make changes to the data store, but CUD operations do.
Most often changes to the data store are simple. Updating an Entity, like an order, by a change to the value of a Value Object, like shipping mode,
involves just one command to the data store. But for more complex actions like placing an order, many changes and many commands may need to be given
to the data store. Money might change hands, inventory might be updated. All kinds of things might need to happen to make one thing happen.
And if they all do not happen correctly then they can leave your data in disarray, your customers un-served, and it might hang your program.
The solution is a concept or pattern called Unit of Work that uses a technology called a transaction. With transactions, changes to your data store can be made
as a group and if any fail then all changes are rolled back to the way things were before the changes were attempted. Your data provider knows how to do transactions.
With a reference to System.Transactions
, your domain will know how to provide a transaction to your provider. The Unit of Work pattern is the way to tell it.
A repository is a place to hold things. We want to create a place to hold on and store CUD actions so we can later bundle them and give them to the data store
to execute as a transaction. Since Create is different from Update and Delete is something different too, it is best to store them in 3 different parts
of a Unit of Work repository. Holding on to something is called persisting. Unit of Work methods often use the word persist, but this example uses names in keeping with this explanation.
public interface IUnitOfWorkRepository
{
void HoldOnToCreationOf(IAggregateRoot entity);
void HoldOnToUpdateOf(IAggregateRoot entity);
void HoldOnToDeleteOf(IAggregateRoot entity);
}
Each of these methods has a signature requiring the Aggregate Root. Each item that the application will create, update, or delete will be an Entity.
To enforce that rule, the repository that will hold items for CUD operations will only take those that are an instance of
IAggregateRoot
.
The interface serves as a marker to the rule. The Unit of Work repository has a rule, a contract, which says it only works on domain Entities,
classes that implement the Aggregate Root. After holding on to all these changed Entities, they need to be bundled and then have a way to commit to make the changes.
Our domain is an abstraction, just a model. We do not want to corrupt it with the real world. We are not concerned in the domain about how things
get done in the real world, just about how we need to tell to get it done.
public interface IUnitOfWork
{
void AddtoUpdatedBundle(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository);
void AddtoCreatedBundle(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository);
void AddtoDeletedBundle(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository);
void Commit();
}
Our domain says that if we want to prep the data store changes into one big bunch, into one transaction, we need to bundle all the changes being held for each type of change,
along with its corresponding Entity. And we are also specifying the command to commit the change. This implementation of the
UnitOfWork
class
uses dictionary items to be the bundles and creates a scope to hold the contents of the transaction.
public class UnitOfWork : IUnitOfWork
{
private Dictionary<IAggregateRoot, IUnitOfWorkRepository> addedEntities;
private Dictionary<IAggregateRoot, IUnitOfWorkRepository> changedEntities;
private Dictionary<IAggregateRoot, IUnitOfWorkRepository> deletedEntities;
private TransactionScope scope;
public UnitOfWork()
{
addedEntities = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();
changedEntities = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();
deletedEntities = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();
}
public void AddtoUpdatedBundle(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository)
{
if (!changedEntities.ContainsKey(entity)) {
changedEntities.Add(entity, unitofWorkRepository);
}
}
public void AddtoCreatedBundle(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository)
{
if (!addedEntities.ContainsKey(entity)) {
addedEntities.Add(entity, unitofWorkRepository);
};
}
public void AddtoDeletedBundle(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository)
{
if (!deletedEntities.ContainsKey(entity)) {
deletedEntities.Add(entity, unitofWorkRepository);
}
}
public void Commit()
{
using (scope = new TransactionScope()) {
foreach (IAggregateRoot entity in this.addedEntities.Keys) {
this.addedEntities[entity].HoldOnToCreationOf(entity);
}
foreach (IAggregateRoot entity in this.changedEntities.Keys) {
this.changedEntities[entity].HoldOnToUpdateOf(entity);
}
foreach (IAggregateRoot entity in this.deletedEntities.Keys) {
this.deletedEntities[entity].HoldOnToDeletionOf(entity);
}
scope.Complete();
}
}
}
Now the domain has a way to keep a hold of changes it wants to make, to bundle them, and then to commit them all at once, as one Unit of Work.
By making Unit of Work central to how our domain makes changes we incorporate the power to leverage data store transactions.
As you build other repositories to retrieve data and make changes to the data store you make them inherit from
IUnitOfWork
.
That way they will have bundling of changes and a way to roll them back built into them should any fail.
Your domain is an abstraction, an idea.
By incorporating the pattern of Unit of Work into your domain design you add the idea of transactions
by giving them a logical mechanism to work.