Domain Model, Aggregate, Entity and Value Object


Domain Model

The domain model, which should be extensive and incorporate both data and behavior, is the foundation of Domain-driven Design (DDD). Crystal Sharp framework has all the components required for a domain model.

Aggregate

Domain-driven Design (DDD) principles define an aggregate as a collection of entities or value objects that have a strong association with a root aggregate. The root aggregate is responsible for maintaining the integrity of the entire aggregate. The following code snippet illustrates how an aggregate is implemented within the Crystal Sharp framework:

public class Currency : AggregateRoot<int>
{
    public string Name { get; private set; }
    public string Code { get; private set; }

    public static Currency Create(string name, string code)
    {
        Currency currency = new Currency { Name = name, Code = code };

        return currency;
    }

    public void Change(string name, string code)
    {
        Name = name;
        Code = code;
    }

    public override void Delete()
    {
        base.Delete();
    }
}

In the above code snippet, the Currency aggregate root is derived from the AggregateRoot<TKey> class. In the Crystal Sharp framework, it is mandatory for every aggregate root to inherit from the AggregateRoot<TKey> class. Here, TKey represents a value type that acts as the identifier and primary key for an aggregate root.

The base class AggregateRoot<TKey> provides predefined properties and methods that facilitate the implementation process. The base class AggregateRoot<TKey> provides the following properties and methods for an aggregate:

public virtual TKey Id { get; protected set; } Id of an aggregate.
public Guid GlobalUId { get; protected set; } = Guid.NewGuid(); This property is given an initial value by the Crystal Sharp framework when an instance is created.
public EntityStatus EntityStatus { get; protected set; } = EntityStatus.Active; Status of an aggregate.
public DateTime CreatedOn { get; protected set; } Creation date and time UTC based. The Crystal Sharp framework internally handles this property.
public DateTime? ModifiedOn { get; protected set; } Modification date and time UTC based. The Crystal Sharp framework internally handles this property. (This property is nullable and will be set only after the modifications).
public long Version { get; private set; } = -1; The initial value will be -1, and after saving the first version, it will be 0, and after every change, this value will be incremented by 1. The Crystal Sharp framework internally handles this property.
public virtual void Activate() Sets the EntityStatus property to EntityStatus.Active
public virtual void Delete() Sets the EntityStatus property to EntityStatus.Deleted
public bool Active() Returns true if an entity status is EntityStatus.Active; otherwise, false.
public void ThrowDomainException(string message) Raises a domain exception with the specified message.
public void ThrowDomainException(int errorCode, string message) Raises a domain exception with the specified error code and message.

Entity

An entity, primarily characterized by its identity, holds importance and has the ability to undergo transformations as time progresses. An aggregate root serves as the sole entry point to access an entity. Following is the code snippet that illustrates the entity class:

public class LineItem : Entity<int>
{
    public string Name { get; private set; }
    public int Quantity { get; private set; }
    public decimal Price { get; private set; }
    public Order Order { get; }

    public static LineItem Create(string name, int quantity, decimal price)
    {
        LineItem lineItem = new() { Name = name, Quantity = quantity, Price = price };

        return lineItem;
    }
}

In the above code snippet, the LineItem entity class is derived from the Entity<int> base class and maintains a reference to its aggregate root, Order. In the Crystal Sharp framework, it is mandatory for every entity class to inherit from the Entity<TKey> base class. Here, TKey signifies a value type that acts as the identifier and primary key for an entity.

Direct access to an entity is not permitted; instead, an aggregate root serves as the sole entry point for accessing the entity. The code snippet below exemplifies the proper method of accessing an entity through the aggregate root:

public class Order : AggregateRoot<int>
{
    public string Code { get; private set; }
    public ICollection<LineItem> LineItems { get; private set; }

    public static Order Create(string code)
    {
        Order order = new() { Code = code };

        return order;
    }

    public void AddLineItem(string name, int quantity, decimal price)
    {
        if (LineItems == null) LineItems = new List<LineItem>();

        LineItems.Add(LineItem.Create(name, quantity, price));
    }

    public decimal CalculatePrice()
    {
        if (LineItems == null || LineItems.Count == 0) return 0;

        return LineItems.Sum(x => x.Quantity * x.Price);
    }
}

In the above code snippet, the Order aggregate is depicted as having a collection of LineItems. It provides a set of methods that enable the execution of operations on the LineItem entity.

The base class Entity<TKey> provides the following properties and methods for an entity:

public virtual TKey Id { get; protected set; } Id of an entity.
public Guid GlobalUId { get; protected set; } = Guid.NewGuid(); This property is given an initial value by the Crystal Sharp framework when an instance is created.
public EntityStatus EntityStatus { get; protected set; } = EntityStatus.Active; Status of an entity.
public DateTime CreatedOn { get; protected set; } Creation date and time UTC based. The Crystal Sharp framework internally handles this property.
public DateTime? ModifiedOn { get; protected set; } Modification date and time UTC based. The Crystal Sharp framework internally handles this property. (This property is nullable and will be set only after the modifications).
public virtual void Activate() Sets the EntityStatus property to EntityStatus.Active
public virtual void Delete() Sets the EntityStatus property to EntityStatus.Deleted
public bool Active() Returns true if an entity status is EntityStatus.Active; otherwise, false.

Value Object

Value objects do not have an identity; value objects are defined based on their properties. These objects can be assigned to different aggregates or entities and should be implemented as immutable. The following code snippet illustrates an example of a value object:

public class CurrencyInfo : ValueObject
{
    public string Code { get; private set; }
    public int NumericCode { get; private set; }

    public CurrencyInfo(string code, int numericCode)
    {
        Code = code;
        NumericCode = numericCode;
    }

    protected override IEnumerable<IComparable> GetEqualityComponents()
    {
        yield return Code;
        yield return NumericCode;
    }
}

In the above code snippet, the CurrencyInfo value object is derived from the ValueObject base class and overrides the abstract method GetEqualityComponents(). In the Crystal Sharp framework, it is mandatory for every value object to inherit from the ValueObject base class.

Value objects can be assigned to different aggregates or entities. The following code snippet illustrates how to assign a value object to an aggregate:

public class Currency : AggregateRoot<int>
{
    public string Name { get; private set; }
    public CurrencyInfo CurrencyInfo { get; private set; }

    public static Currency Create(string name, CurrencyInfo currencyInfo)
    {
        Currency currency = new() { Name = name, CurrencyInfo = currencyInfo };

        return currency;
    }
}

In the above code snippet, the Currency aggregate contains the CurrencyInfo property, which represents a value object.