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);
}
Related topics
- Domain model — template walkthrough using
DemoThing. - Exception handling —
DomainExceptionandResult. - Use cases — orchestrating aggregates and repositories.