Table of Contents

Domain building blocks

The DevKit CodeBlock.DevKit.Domain library provides small, reusable primitives for Domain-Driven Design so your template Core project can define aggregates without re-implementing identity, versioning, events, and repository contracts.

For how the SaaS template applies these ideas to a real feature (DemoThing), start with Domain model.

Below, each type lists its namespace and full source via DocFX.

Entities (CodeBlock.DevKit.Domain.Entities)

Entity.cs

  • Namespace: CodeBlock.DevKit.Domain.Entities
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

using CodeBlock.DevKit.Domain.ValueObjects;
using MongoDB.Bson;

namespace CodeBlock.DevKit.Domain.Entities;

/// <summary>
/// Base abstract class for all domain entities.
/// Provides common properties and behavior for entity identification and audit tracking.
/// </summary>
public abstract class Entity
{
    /// <summary>
    /// Initializes a new instance of Entity with a generated MongoDB ObjectId and current timestamps.
    /// </summary>
    public Entity()
    {
        SetId(ObjectId.GenerateNewId().ToString());

        CreationTime = Timestamp.CreateUtcNow();
        ModificationTime = CreationTime;
    }

    /// <summary>
    /// Gets the unique identifier of the entity.
    /// </summary>
    public string Id { get; private set; }

    /// <summary>
    /// Gets the timestamp when the entity was created.
    /// </summary>
    public Timestamp CreationTime { get; private set; }

    /// <summary>
    /// Gets the timestamp when the entity was last modified.
    /// </summary>
    public Timestamp ModificationTime { get; private set; }

    /// <summary>
    /// Sets the unique identifier for the entity.
    /// </summary>
    /// <param name="id">The identifier to set.</param>

    protected void SetId(string id)
    {
        Id = id;
    }

    /// <summary>
    /// Updates the modification timestamp to the current UTC time.
    /// </summary>

    protected void UpdateModificationTime()
    {
        ModificationTime = Timestamp.CreateUtcNow();
    }
}

AggregateRoot.cs

  • Namespace: CodeBlock.DevKit.Domain.Entities
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

using CodeBlock.DevKit.Domain.Events;

namespace CodeBlock.DevKit.Domain.Entities;

/// <summary>
/// Base abstract class for aggregate root entities in Domain-Driven Design.
/// Manages domain events, versioning for concurrency control, and aggregate invariants.
/// </summary>
public abstract class AggregateRoot : Entity
{
    private List<IDomainEvent> domainEvents;
    private bool versionIsIncremented;

    /// <summary>
    /// Initializes a new instance of AggregateRoot with empty domain events and version tracking.
    /// </summary>
    public AggregateRoot()
    {
        versionIsIncremented = false;
        domainEvents = new List<IDomainEvent>();
    }

    /// <summary>
    /// Gets the current version of the aggregate for concurrency control.
    /// Version changes after each aggregate update to handle optimistic concurrency.
    /// </summary>
    public int Version { get; private set; }

    /// <summary>
    /// Gets a read-only collection of all pending domain events.
    /// </summary>
    /// <returns>A read-only collection of domain events.</returns>

    public IReadOnlyCollection<IDomainEvent> GetDomainEvents()
    {
        return domainEvents == null ? new List<IDomainEvent>() : domainEvents.AsReadOnly();
    }

    /// <summary>
    /// Clears all pending domain events from the aggregate.
    /// </summary>

    public void ClearDomainEvents()
    {
        domainEvents?.Clear();
    }

    /// <summary>
    /// Adds a domain event to the aggregate and handles versioning.
    /// </summary>
    /// <param name="domainEvent">The domain event to add.</param>

    protected void AddDomainEvent(IDomainEvent domainEvent)
    {
        CheckInvariants();

        // Only increment the version if it hasn't already been incremented
        if (!versionIsIncremented)
            IncrementAggregateVersionByOne();

        domainEvents ??= new List<IDomainEvent>();

        domainEvents.Add(domainEvent);

        UpdateModificationTime();
    }

    /// <summary>
    /// Tracks a generic change in the aggregate by creating an EntityChangedEvent.
    /// </summary>
    /// <param name="changeTitle">Description of the change that occurred.</param>

    protected void TrackChange(string changeTitle)
    {
        AddDomainEvent(new EntityChangedEvent<AggregateRoot>(changeTitle, this));
    }

    /// <summary>
    /// Abstract method to check invariants for the aggregate.
    /// Invariants ensure the aggregate is in a valid state before allowing changes.
    /// </summary>

    protected abstract void CheckInvariants();

    /// <summary>
    /// Increments the aggregate version by one and marks it as incremented.
    /// </summary>
    private void IncrementAggregateVersionByOne()
    {
        Version += 1;
        versionIsIncremented = true;
    }
}

Events (CodeBlock.DevKit.Domain.Events)

IDomainEvent.cs

  • Namespace: CodeBlock.DevKit.Domain.Events
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

using MediatR;

namespace CodeBlock.DevKit.Domain.Events;

/// <summary>
/// Marker interface for domain events in Domain-Driven Design.
/// Extends MediatR's INotification to enable event publishing and handling.
/// </summary>
public interface IDomainEvent : INotification { }

EntityChangedEvent.cs

  • Namespace: CodeBlock.DevKit.Domain.Events
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

using CodeBlock.DevKit.Domain.Entities;

namespace CodeBlock.DevKit.Domain.Events;

/// <summary>
/// Generic domain event that represents a change in an aggregate root entity.
/// Captures the change description and entity snapshot for event handling.
/// </summary>
/// <typeparam name="T">The type of aggregate root entity that changed.</typeparam>
public class EntityChangedEvent<T> : IDomainEvent
    where T : AggregateRoot
{
    /// <summary>
    /// Initializes a new instance of EntityChangedEvent with change details.
    /// </summary>
    /// <param name="changeTitle">Description of the change that occurred.</param>
    /// <param name="entitySnapshot">Snapshot of the entity at the time of change.</param>

    public EntityChangedEvent(string changeTitle, T entitySnapshot)
    {
        ChangeTitle = changeTitle;
        EntitySnapshot = entitySnapshot;
    }

    /// <summary>
    /// Gets the description of the change that occurred.
    /// </summary>
    public string ChangeTitle { get; }

    /// <summary>
    /// Gets the snapshot of the entity at the time of change.
    /// </summary>
    public T EntitySnapshot { get; }
}

IIntegrationEvent.cs

  • Namespace: CodeBlock.DevKit.Domain.Events
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

namespace CodeBlock.DevKit.Domain.Events;

/// <summary>
/// Marker interface for integration events that cross bounded context boundaries.
/// Currently implemented as domain events to leverage existing event handling infrastructure.
/// Future implementations may use message brokers for more reliable cross-service communication.
/// </summary>
public interface IIntegrationEvent : IDomainEvent { }

Exceptions (CodeBlock.DevKit.Domain.Exceptions)

DomainException.cs

  • Namespace: CodeBlock.DevKit.Domain.Exceptions
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

using CodeBlock.DevKit.Core.Exceptions;

namespace CodeBlock.DevKit.Domain.Exceptions;

/// <summary>
/// Exception thrown when domain business rules or invariants are violated.
/// Extends ManagedException to provide localized error messages and structured error handling.
/// </summary>
public class DomainException : ManagedException
{
    /// <summary>
    /// Initializes a new instance of DomainException with localized message support.
    /// </summary>
    /// <param name="messageResourceKey">Resource key for the localized error message.</param>
    /// <param name="messageResourceType">Type containing the resource definitions.</param>
    /// <param name="messagePlaceholders">Optional placeholders for dynamic message content.</param>

    public DomainException(string messageResourceKey, Type messageResourceType, IEnumerable<MessagePlaceholder> messagePlaceholders = null)
        : base(messageResourceKey, messageResourceType, messagePlaceholders) { }

    /// <summary>
    /// Initializes a new instance of DomainException with a plain text message.
    /// </summary>
    /// <param name="message">Plain text error message.</param>

    public DomainException(string message)
        : base(message) { }
}

Value objects (CodeBlock.DevKit.Domain.ValueObjects)

BaseValueObject.cs

  • Namespace: CodeBlock.DevKit.Domain.ValueObjects
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

namespace CodeBlock.DevKit.Domain.ValueObjects;

/// <summary>
/// Base abstract record for value objects in Domain-Driven Design.
/// Ensures value objects are immutable and validate their business rules through policies.
/// </summary>
public abstract record BaseValueObject
{
    /// <summary>
    /// Abstract method to check business policies and validation rules for the value object.
    /// Must be implemented by derived classes to ensure value object integrity.
    /// </summary>

    protected abstract void CheckPolicies();
}

Timestamp.cs

  • Namespace: CodeBlock.DevKit.Domain.ValueObjects
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

namespace CodeBlock.DevKit.Domain.ValueObjects;

/// <summary>
/// Immutable value object representing a timestamp with date components.
/// Provides UTC-based timestamp creation and local time conversion capabilities.
/// </summary>
public record Timestamp : BaseValueObject
{
    /// <summary>
    /// Gets the full DateTime value of the timestamp.
    /// </summary>
    public DateTime DateTime { get; private set; }

    /// <summary>
    /// Gets the day component of the timestamp.
    /// </summary>
    public int Day { get; private set; }

    /// <summary>
    /// Gets the month component of the timestamp.
    /// </summary>
    public int Month { get; private set; }

    /// <summary>
    /// Gets the year component of the timestamp.
    /// </summary>
    public int Year { get; private set; }

    /// <summary>
    /// Private constructor to create a new Timestamp instance.
    /// </summary>
    /// <param name="utcDateTime">The UTC DateTime value.</param>
    private Timestamp(DateTime utcDateTime)
    {
        DateTime = utcDateTime;
        Day = DateTime.Day;
        Month = DateTime.Month;
        Year = DateTime.Year;
    }

    /// <summary>
    /// Creates a new Timestamp with the current UTC time.
    /// </summary>
    /// <returns>A new Timestamp representing the current UTC time.</returns>

    public static Timestamp CreateUtcNow()
    {
        return new Timestamp(DateTime.UtcNow);
    }

    /// <summary>
    /// Creates a new Timestamp from a specified UTC DateTime.
    /// </summary>
    /// <param name="utcDateTime">The UTC DateTime to create the timestamp from.</param>
    /// <returns>A new Timestamp representing the specified UTC time.</returns>

    public static Timestamp Create(DateTime utcDateTime)
    {
        return new Timestamp(utcDateTime);
    }

    /// <summary>
    /// Converts the timestamp to local time.
    /// </summary>
    /// <returns>A new Timestamp representing the local time equivalent.</returns>

    public Timestamp ToLocalTime()
    {
        var localDateTime = DateTime.ToLocalTime();
        return new Timestamp(localDateTime);
    }

    /// <summary>
    /// Checks business policies for the timestamp value object.
    /// Currently no policies are enforced.
    /// </summary>
    protected override void CheckPolicies() { }
}

Services (CodeBlock.DevKit.Domain.Services)

IBaseRepository.cs

  • Namespace: CodeBlock.DevKit.Domain.Services
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

using CodeBlock.DevKit.Domain.Entities;

namespace CodeBlock.DevKit.Domain.Services;

/// <summary>
/// Generic repository interface for basic CRUD operations on domain entities.
/// Provides both synchronous and asynchronous methods for entity management.
/// </summary>
/// <typeparam name="TEntity">The type of entity managed by this repository.</typeparam>
public interface IBaseRepository<TEntity> : IDisposable
    where TEntity : Entity
{
    /// <summary>
    /// Adds a new entity to the repository.
    /// </summary>
    /// <param name="entity">The entity to add.</param>

    void Add(TEntity entity);

    /// <summary>
    /// Adds a new entity to the repository asynchronously.
    /// </summary>
    /// <param name="entity">The entity to add.</param>
    /// <returns>A task representing the asynchronous operation.</returns>

    Task AddAsync(TEntity entity);

    /// <summary>
    /// Adds a new entity or updates an existing one based on entity ID.
    /// </summary>
    /// <param name="entity">The entity to add or update.</param>

    void AddOrUpdate(TEntity entity);

    /// <summary>
    /// Adds a new entity or updates an existing one asynchronously.
    /// </summary>
    /// <param name="entity">The entity to add or update.</param>
    /// <returns>A task representing the asynchronous operation.</returns>

    Task AddOrUpdateAsync(TEntity entity);

    /// <summary>
    /// Updates an existing entity in the repository.
    /// </summary>
    /// <param name="entity">The entity to update.</param>

    void Update(TEntity entity);

    /// <summary>
    /// Updates an existing entity in the repository asynchronously.
    /// </summary>
    /// <param name="entity">The entity to update.</param>
    /// <returns>A task representing the asynchronous operation.</returns>

    Task UpdateAsync(TEntity entity);

    /// <summary>
    /// Updates multiple entities in the repository asynchronously.
    /// </summary>
    /// <param name="entities">The collection of entities to update.</param>
    /// <returns>A task representing the asynchronous operation.</returns>

    Task UpdateManyAsync(IEnumerable<TEntity> entities);

    /// <summary>
    /// Deletes an entity from the repository by its ID asynchronously.
    /// </summary>
    /// <param name="id">The ID of the entity to delete.</param>
    /// <returns>A task representing the asynchronous operation.</returns>

    Task DeleteAsync(string id);

    /// <summary>
    /// Retrieves an entity by its ID.
    /// </summary>
    /// <param name="id">The ID of the entity to retrieve.</param>
    /// <returns>The entity with the specified ID, or null if not found.</returns>

    TEntity GetById(string id);

    /// <summary>
    /// Retrieves an entity by its ID asynchronously.
    /// </summary>
    /// <param name="id">The ID of the entity to retrieve.</param>
    /// <returns>A task containing the entity with the specified ID, or null if not found.</returns>

    Task<TEntity> GetByIdAsync(string id);

    /// <summary>
    /// Retrieves all entities from the repository asynchronously.
    /// </summary>
    /// <returns>A task containing a collection of all entities.</returns>

    Task<IEnumerable<TEntity>> GetListAsync();

    /// <summary>
    /// Gets the total count of entities in the repository asynchronously.
    /// </summary>
    /// <returns>A task containing the total count of entities.</returns>

    Task<long> CountAsync();
}

IBaseAggregateRepository.cs

  • Namespace: CodeBlock.DevKit.Domain.Services
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

using CodeBlock.DevKit.Domain.Entities;

namespace CodeBlock.DevKit.Domain.Services;

/// <summary>
/// Generic repository interface for aggregate root entities with concurrency control.
/// Extends the base repository to provide optimistic concurrency handling for aggregates.
/// </summary>
/// <typeparam name="TEntity">The type of aggregate root entity managed by this repository.</typeparam>
public interface IBaseAggregateRepository<TEntity> : IBaseRepository<TEntity>
    where TEntity : AggregateRoot
{
    /// <summary>
    /// Updates an aggregate root entity with concurrency safety using optimistic locking.
    /// </summary>
    /// <param name="entity">The aggregate root entity to update.</param>
    /// <param name="loadedVersion">The version of the entity when it was originally loaded.</param>
    /// <returns>A task representing the asynchronous operation.</returns>

    Task ConcurrencySafeUpdateAsync(TEntity entity, int loadedVersion);
}

ICulture.cs

  • Namespace: CodeBlock.DevKit.Domain.Services
namespace CodeBlock.DevKit.Domain.Services;

public interface ICulture
{
    string GetCurrentCultureName();
    string GetDefaultCultureName();
}

Specifications (CodeBlock.DevKit.Domain.Specifications)

ISpecification.cs

  • Namespace: CodeBlock.DevKit.Domain.Specifications
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev

namespace CodeBlock.DevKit.Domain.Specifications;

/// <summary>
/// Generic interface for domain specifications in Domain-Driven Design.
/// Defines the contract for business rule validation against entities.
/// </summary>
/// <typeparam name="TEntity">The type of entity to validate against the specification.</typeparam>
public interface ISpecification<TEntity>
{
    /// <summary>
    /// Determines whether the specified entity satisfies the business rule defined by this specification.
    /// </summary>
    /// <param name="entity">The entity to evaluate against the specification.</param>
    /// <returns>True if the entity satisfies the specification; otherwise, false.</returns>

    bool IsSatisfiedBy(TEntity entity);
}