Skip to content

Latest commit

 

History

History

README.md

CoreEx.DomainDriven

Provides the foundational Domain-Driven Design (DDD) building blocks for CoreEx: typed entities, aggregate roots with integration-event support, persistence-state tracking, and mutation-guard helpers.

Overview

CoreEx.DomainDriven implements the core DDD concepts referenced throughout the CoreEx framework. It supplies base classes and interfaces for entities and aggregate roots that are identity-based, change-tracked, and mutation-guarded, integrating cleanly with CoreEx contracts (IIdentifier, IChangeLog, IETag) and the broader Result<T> pipeline.

The package intentionally stays minimal: it does not dictate a persistence strategy, an ORM, or an event bus. Instead it establishes the behavioural invariants — read-only enforcement, persistence state transitions, event accumulation — that the application and infrastructure layers rely on. Higher-level packages such as CoreEx.EntityFrameworkCore and CoreEx.Database consume these contracts when mapping and persisting domain objects.

Key capabilities

  • 🆔 Typed entity base: Entity<TId, TSelf> enforces identity-based equality (Equals considers Id only) and carries a fluent self-typed API for state manipulation.
  • 🔄 Persistence state machine: PersistenceState (Unknown → New / NotModified → Modified → Removed) with extension helpers (IsNew, IsModified, IsNewOrModified, IsRemoved, …) and validated one-way transitions that prevent illegal state changes.
  • 🛡 Mutation guards: Modify(action), Remove(action), ModifyAndMakeReadOnly, and ModifyAndMakeReadOnly<TResult> wrappers enforce CheckReadOnly and OnCheckCanMutate before any state change, then advance PersistenceState automatically and fire the Mutated event.
  • 🔒 Read-only enforcement: MakeReadOnly() / IsReadOnly locks an entity after hydration or after terminal mutations; all Modify/Remove paths throw InvalidOperationException when violated.
  • 📢 Aggregate root with integration events: Aggregate<TId, TSelf> adds AddEvent(EventData) / ClearEvents() and the Events / HasEvents members for transient integration-event accumulation inside a unit-of-work scope.
  • 🔧 Override hooks: OnCheckCanMutate() returns a Result for pre-mutation business-rule validation; OnMutate() is a post-mutation extension point; both are called inside every Modify/Remove wrapper.
  • 💧 Hydration helpers: SetChangeLog, SetETag, and SetPersistenceState bypass read-only and state-change logic by design, allowing infrastructure layers to rehydrate an entity from persistence without triggering mutation semantics.
  • 🔑 CompositeKey support: EntityKey exposes the entity identity as a CompositeKey for infrastructure layers that need a key independent of the typed Id.

Key types

Type Description
IEntity Core DDD entity contract: PersistenceState, IsReadOnly, IReadOnlyIdentifier, IReadOnlyChangeLog, IReadOnlyETag.
IAggregateRoot Extends IEntity with Events (read-only collection of EventData) and HasEvents; integration events only — domain events are not supported by design.
EntityBase Abstract base implementing IEntity; provides the full mutation-guard (Modify, Remove), read-only, state-machine, SetChangeLog/SetETag hydration, Mutated event, and OnCheckCanMutate/OnMutate hooks.
Entity<TId, TSelf> Abstract typed, self-referential entity (TSelf pattern) with identity-based equality; re-exposes SetPersistenceState, AsNew, AsNotModified, MakeReadOnly, SetChangeLog, and SetETag as fluent TSelf-returning methods.
Aggregate<TId, TSelf> Extends Entity<TId, TSelf> with AddEvent(EventData) / ClearEvents() for integration-event accumulation within the aggregate lifetime.
PersistenceState Enum: Unknown, New, NotModified, Modified, Removed; governs the entity lifecycle from creation through persistence to deletion.
DomainDrivenExtensions Extension methods on PersistenceState: IsNew, IsNotModified, IsModified, IsRemoved, IsNotRemoved, IsNewOrModified.

Domain Events — Intentionally Not Supported

CoreEx deliberately does not provide a native domain-event mechanism (e.g. MediatR INotification dispatch or an in-process event bus). This is a conscious architectural decision:

  • Chatty emission — fine-grained domain events (PropertyChanged, ItemAdded, etc.) generate high volumes of events that produce implicit, hard-to-trace side-effects throughout the application layer.
  • Non-explicit side-effects — handler chains driven by in-process events obscure control flow, making it difficult to reason about what happens as a result of a single aggregate mutation.
  • Integration events are sufficient — coarse-grained integration events via IUnitOfWork.Events and the transactional outbox communicate meaningful state changes to other systems in an explicit, auditable, and transactional way.

A developer can extend CoreEx with a domain-event mechanism if a genuine use case exists — for example, by raising events from aggregate mutations and dispatching them via MediatR after the transaction commits. This is an opt-in extension, not a framework default.

Related namespaces

  • CoreEx - Provides IIdentifier, IChangeLog, IETag, CompositeKey, EventData, and Result<T> consumed by the DDD types.
  • CoreEx.EntityFrameworkCore - Persists Entity<TId, TSelf> and Aggregate<TId, TSelf> via EfDbModel<TModel>; uses PersistenceState to determine insert/update/delete operations.
  • CoreEx.Validation - Validates entity state; OnCheckCanMutate can delegate to a CoreEx validator for pre-mutation rule checks.

AI Usage Guide

An AGENTS.md file is included with this package. AI coding assistants (GitHub Copilot, Claude, Cursor, etc.) that support workspace-injected package documentation will automatically surface concise usage guidance, code examples, and Do Not rules for this package without requiring a local CoreEx checkout.