Add or modify a DDD domain object in a CoreEx Domain layer. USE FOR: new aggregate root (Aggregate<TId,TSelf>), new child entity (Entity<TId,TSelf>) owned by an aggregate, new value object (sealed record), adding mutation methods, PersistenceState-aware factory methods, mutation guards (OnCheckCanMutate/OnMutate). Interviews the developer to determine which of the three (aggregate root / entity / value object) applies, then follows the appropriate pattern. DO NOT USE FOR: CRUD-oriented domain...
Scanned 8/31/2026
Install to Claude Code
npx -y skills add Avanade/CoreEx --skill coreex-aggregate --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Coreex Aggregate?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/avanade-coreex-aggregate)More formats (shields.io, HTML) on the badges page.
---
name: coreex-aggregate
description: "Add or modify a DDD domain object in a CoreEx Domain layer. USE FOR: new aggregate root (Aggregate<TId,TSelf>), new child entity (Entity<TId,TSelf>) owned by an aggregate, new value object (sealed record), adding mutation methods, PersistenceState-aware factory methods, mutation guards (OnCheckCanMutate/OnMutate). Interviews the developer to determine which of the three (aggregate root / entity / value object) applies, then follows the appropriate pattern. DO NOT USE FOR: CRUD-oriented domains with no Domain layer (use coreex-app-service directly against repositories), Application-layer mappers between aggregate and contract (see coreex-application-services.instructions.md), Infrastructure persistence of aggregates (use coreex-repository)."
argument-hint: "Optional: domain object kind (aggregate root / entity / value object), identifier type, mutation operations needed, invariants to enforce"
tags: ["ddd", "domain-driven-design", "aggregate", "entity", "value-object", "domain-layer", "coreex"]
---
<!--
AI workflow asset — dual-audience notice:
- In the Avanade/CoreEx repository: this file is the authored source. Edit it here.
- In a consumer repository: this file was generated by `dotnet new coreex-ai` (or refreshed via
`dotnet new coreex-ai --force` / the `/coreex-docs-sync` skill). Do not hand-edit it directly —
propose the change upstream in Avanade/CoreEx instead, then refresh once it is released.
-->
# CoreEx: Aggregate (DDD Domain Object)
Guides you through adding or modifying a domain object in a CoreEx Domain layer — aggregate roots,
child entities, and value objects. This skill is scoped to solutions where the `*.Domain` project exists
(added via `dotnet new coreex-domain`). It interviews you to determine which kind of domain object you need,
then follows the corresponding pattern from `CoreEx.DomainDriven`.
## When to Use
- Add a new **aggregate root** — a consistency boundary with mutation guards and (optionally) integration events
- Add a new **child entity** — owned exclusively by an aggregate, with identity but no independent lifecycle
- Add a new **value object** — a concept with no identity, defined entirely by its values
- Add mutation methods, factory methods, or invariant guards to an existing domain object
- Decide whether a domain concept needs a Domain layer at all, or should stay CRUD-oriented
> **Mix and match within a domain.** Once a `*.Domain` project exists, adopting it is not all-or-nothing across
> every entity in that domain. Only give an entity the full aggregate/entity/value-object ceremony (mutation
> guards, `PersistenceState`, factory methods) when it genuinely benefits — meaningful invariants, state
> transitions, or integration events. A simple reference/lookup-style entity in the same domain can remain
> CRUD-oriented (Application service → repository directly), even while a sibling aggregate in the same
> `*.Domain` project uses the full pattern.
## When Not to Use
- The domain is CRUD-oriented with no meaningful invariants to protect at the model level — let the
Application service orchestrate directly against repository interfaces; skip the Domain layer entirely
- Application-layer mapping between aggregate and contract — see `.github/instructions/coreex-application-services.instructions.md`
- Infrastructure persistence of the aggregate (EF mapping, `PersistenceState`-driven insert/update/delete) — use `coreex-repository`
- Validating primitive request DTOs before they reach the domain — use `coreex-validator`
> **Confirm this skill applies before writing any domain object.** It is gated on the presence of the
> `*.Domain` project (`src/*.Domain/`). If it is absent, the domain is CRUD-oriented — stop and use
> `coreex-app-service` against repository interfaces instead. To add the Domain layer, run
> `dotnet new coreex-domain -n Company.Product.Books.Domain` and wire it into the solution first.
## Quick Reference
- **Aggregate root** → `Aggregate<TId, TSelf>` (extends `Entity<TId, TSelf>`) — adds `Events`/`AddEvent`/`ClearEvents` for integration-event accumulation
- **Child entity** → `Entity<TId, TSelf>` — identity-based equality (`Id` only), no independent construction path from outside the aggregate
- **Value object** → `sealed record` — structural equality, `init`-only properties, invariants enforced in the initialiser
- Two factory methods only: `CreateNew(...)` (`.AsNew()`) and `CreateFrom(...)` (`.AsNotModified()`) — constructor is `private`
- `Modify(...)` / `Remove(...)` are the **only** mutation paths — they invoke `OnCheckCanMutate()`, apply the change, transition `PersistenceState`, then call `OnMutate()`
- `OnCheckCanMutate()` returns `Result` but is invoked via `.ThrowOnError()` internally — a failing guard throws the exception carried by the `Result` (typically `BusinessException` for `Result.BusinessError(...)`) from `Modify`/`Remove`, regardless of the `Result` return type declared here
- Public mutation methods on the aggregate should return `Result`/`Result<T>` for consistency with Application-layer `Result<T>` pipelines — even though the internal guard throws
- Child entity mutation methods are `internal` — only the owning aggregate may invoke them
- No async I/O in domain classes — ever. Async belongs in Application services or Policies.
- No native domain-event dispatch (MediatR-style) — only integration events via `Aggregate<TId,TSelf>.Events`, forwarded through `IUnitOfWork.Events` in the Application layer
- Aggregates are the best unit-test target in the codebase (no injected dependencies) — cover every mutation method's happy path and rejection path with `WithGenericTester<EntryPoint>` + `Test.Scoped(...)`, calling the aggregate directly (no repository, no Application service); assert `OnCheckCanMutate()` guard failures as thrown exceptions, and failed-`Result` returns as `Result` assertions
- A value object backed by a JSON database column (e.g. `Address` stored as `basket.ShippingAddressJson`) is authored **exactly like any other value object** — a plain `sealed record`/`record class` with invariant-enforcing `init` setters. It sits as the middle type in a three-type chain (`Contracts.Xxx` ↔ `Domain.ValueObjects.Xxx` ↔ `Persistence.Xxx`) bridged by two separate mappers (Application: Domain↔Contract; Infrastructure: Domain↔Persistence) — see [`coreex-domain.instructions.md#value-objects-backed-by-a-json-column`](/.github/instructions/coreex-domain.instructions.md#value-objects-backed-by-a-json-column) and the [`coreex-db-migration`](/.github/skills/coreex-db-migration/SKILL.md#json-columns) skill for the column/mapper setup
For full workflow and code examples see [`references/workflow.md`](references/workflow.md).
## Key References
- [`/.github/instructions/coreex-domain.instructions.md`](/.github/instructions/coreex-domain.instructions.md) — full Domain layer conventions (aggregates, entities, value objects, `PersistenceState`)
- [`/.github/instructions/coreex-application-services.instructions.md`](/.github/instructions/coreex-application-services.instructions.md) — how Application services construct, mutate, and map aggregates (`Domain.Xxx.CreateNew(...)`, `Application/Mapping/` `Mapper<TSource,TDest,TSelf>`)
- [`/.github/instructions/coreex-tests.instructions.md`](/.github/instructions/coreex-tests.instructions.md) — `*.Test.Unit` conventions (`WithGenericTester<EntryPoint>`, `Test.Scoped(...)`) reused for aggregate unit tests
- Related skills: [`coreex-contract`](../coreex-contract/SKILL.md) (maps aggregate ↔ contract), [`coreex-repository`](../coreex-repository/SKILL.md) (persists aggregates via `PersistenceState`), [`coreex-app-service`](../coreex-app-service/SKILL.md) (constructs/mutates aggregates, forwards `Events`), [`coreex-db-migration`](../coreex-db-migration/SKILL.md#json-columns) (JSON column setup for JSON-backed value objects)
- Deep-dive (after `/coreex-docs-sync`) — the primary consumer-resolvable pointers into `CoreEx.DomainDriven`:
- [`/.github/docs/coreex/agents/CoreEx.DomainDriven.md`](/.github/docs/coreex/agents/CoreEx.DomainDriven.md) — package AI usage guide (key types, the "Domain Events — Intentionally Not Supported" rationale)
- [`/.github/docs/coreex/domain-layer.md`](/.github/docs/coreex/domain-layer.md) — Domain layer sample architecture doc
- Illustrative examples (CoreEx sample — not present in your project):
- [CoreEx.DomainDriven README](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.DomainDriven/README.md) — package overview and rationale
- [Aggregate.cs](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.DomainDriven/Aggregate.cs), [Entity.cs](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.DomainDriven/Entity.cs), [EntityBase.cs](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.DomainDriven/EntityBase.cs) — base-class implementation (`Modify`, `Remove`, `OnCheckCanMutate`, `OnMutate`, `PersistenceState` transitions)
- [PersistenceState.cs](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.DomainDriven/PersistenceState.cs), [DomainDrivenExtensions.cs](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.DomainDriven/DomainDrivenExtensions.cs) — state enum and filter helpers (`IsNew`, `IsModified`, `IsNewOrModified`, `IsNotRemoved`)
- [CoreEx.Template README](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.Template/README.md) — `coreex-domain` addon template and generated `*.Domain` project shape
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!