Eiffel
  • Overview
    • About
  • Features
    • Cross cutting concerns
      • API response consistency
      • Global exception handling
      • Idempotency
      • Audit logging
    • Caching
      • Configuration
        • In-Memory
        • Redis
      • Usage
      • API
        • CacheSource
        • InMemoryCacheOptions
        • RedisCacheOptions
        • ICacheService
        • ICacheServiceFactory
    • Localization
    • Job Processing
    • Multi-Tenancy
    • Metrics & monitoring
    • Transactional outbox
      • PostgreSQL
      • MongoDB (cluster only)
    • Messaging
      • Kafka
      • Rabbit MQ
      • Azure Service Bus
    • Awaitable socket client
    • Graceful shutdown
  • Fundamentals
    • Persistence
    • Modelling
  • Principles
    • Domain-Driven Design
      • Aggregates
      • Entities
      • Value Objects
      • Domain Events
      • Factories
      • Domain Services
      • Business Identifiers
      • Shared Kernel
    • Onion Architecture
      • Application Layer
      • Domain Layer
      • Infrastructure Layer
      • Anti-Corruption Layer
    • Modular Monolith Architecture
      • Modules
      • Shared Infrastructure
    • Microservice Architecture
      • API Gateway
      • View Model Composition
      • Contracts
  • Business Aligment
    • Domain Storytelling
    • User stories
  • Implementation
    • Modular Monolith
    • Microservices
  • Testing
    • Unit Testing
    • Integration Testing
    • Contract Testing
  • Cloud Infrastructure
    • CI/CD Pipelines
    • Docker
    • Kubernates
    • Infrastructure as Code
Powered by GitBook
On this page
  1. Principles
  2. Domain-Driven Design

Aggregates

An Aggregate is a cluster of domain objects that are treated as a single unit with regards to data changes.

The aggregate ensures that all the changes to the objects within the aggregate are performed in a consistent and transactional manner.

Key characteristics of an Aggregate are:

  1. Consistency Boundary: The aggregate defines a clear boundary for transactional consistency. All invariants and business rules within an aggregate must be satisfied as a whole, ensuring that the domain remains in a valid state at all times.

  2. Encapsulation: The internal structure and state of the aggregate's objects are hidden from the outside world. External entities can only interact with the aggregate through its root.

  3. Lifecycle: Aggregates are typically long-lived, persisting over multiple transactions. They should be designed with consideration for their lifecycle management.

  4. Isolation: Aggregates should not directly reference or hold references to other aggregates. Instead, they can reference other aggregates by their unique identifiers (IDs).

Eiffel.Modelling.Abstractions NuGet package contains base aggregate class. By using abstract Aggregate class you can define your aggregates.

/// <summary>
/// Aggregate implementation
/// </summary>
/// <typeparam name="TIdentity">The type of unique business identifier</typeparam>
/// <typeparam name="TKey">The type of database primary key</typeparam>
public abstract class Aggregate<TIdentity, TKey> : EntityBase<TKey>, IAggregate
   where TIdentity : ValueObject<TIdentity>
   where TKey : struct, IEquatable<TKey>
{
    /// <summary>
    /// Domain events collection
    /// </summary>
    public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.ToList();
    
    /// <summary>
    /// Business identifier
    /// </summary>
    public abstract TIdentity Identity { get; internal set; }
    
    private readonly List<IDomainEvent> _domainEvents;
    /// <summary>
    /// CTOR
    /// </summary>
    public Aggregate()
    {
        _domainEvents = new List<IDomainEvent>();
    }
    
    /// <summary>
    /// Raises/adds domain event
    /// </summary>
    /// <typeparam name="TEvent">The type of the class representing domain event</typeparam>
    /// <param name="domainEvent">Domain event</param>
    protected void Raise<TEvent>(TEvent domainEvent)
        where TEvent : IDomainEvent
    {
        _domainEvents.Add(domainEvent);
    }
    
    /// <summary>
    /// Clears all domain events
    /// </summary>
    public void ClearEvents()
    {
        _domainEvents.Clear();
    }
}
public class Booking : Aggregate<BookingId, Guid>
{
    // Business identifier
    public override BookingId Identifier { get; private set; }

    // Status enum
    public BookingStatus Status { get; private set; }

    // ...
    // ...
    void Cancel(string reason)
    {
        Status = BookingStatus.Cancelled;
        Raise(new BookingCancelled(BookingId, reason));
    }
}
PreviousDomain-Driven DesignNextEntities

Last updated 1 year ago

Each Aggregate is likely to contain more than one function and these functions are business needs demanded by domain experts/stakeholders. Each function usually requires notifying other members of the system that an event has occurred. These events are called . Each Aggregate is responsible for keeping the events occurring within itself. Each Aggregate must have a uniquely defined identity type and primary key type required for the database. You can find the identity details specific to the aggregates in the section. Example Booking Aggregate with BookingCancelled domain event.

Domain Events
Business Identifiers