Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion skills/.gitkeep

This file was deleted.

50 changes: 50 additions & 0 deletions skills/dotnet/zerya-dotnet-ddd-architecture/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: zerya-dotnet-ddd-architecture
description: Discover, design, refactor, or review .NET domain models with strategic and tactical DDD. Use when clarifying ubiquitous language, subdomains, Bounded Contexts, context relationships, aggregate and entity boundaries, invariants, domain services, application processes, domain events, or misplaced business rules. Use it to diagnose domain-model problems before choosing persistence or modular-monolith boundaries; use a persistence- or framework-specific skill only after the domain decision is explicit.
---

# Zerya .NET DDD Architecture

Treat architecture as a set of evidence-backed model decisions, not a ladder from classes to projects. Keep semantic, consistency, ownership, and implementation boundaries distinct.

## Establish domain evidence

1. Read repository instructions and accepted architecture decisions.
2. Identify the business problem, actors, outcomes, policies, terminology, and disputed meanings.
3. Consult domain experts or existing domain artifacts when available. Mark boundaries inferred only from code as hypotheses.
4. Map the relevant subdomains, candidate Bounded Contexts, upstream/downstream relationships, and language translations.
5. Trace representative commands, queries, events, external imports, and failure cases through the current implementation.
6. Record true business invariants, acceptable inconsistency windows, ownership, and independent lifecycles.

Read [strategic-modeling.md](references/strategic-modeling.md) when discovering or changing Bounded Contexts. Do not infer a context solely from folders, tables, teams, or deployment units.

## Make separate decisions

Decide each axis on its own evidence:

- **Model meaning:** define where one ubiquitous language and internally consistent model apply.
- **Consistency:** cluster only state that must satisfy named invariants atomically.
- **Behavior:** place decisions with the domain concept that has the information and responsibility to make them.
- **Ownership:** assign authoritative writes and lifecycle responsibility explicitly.
- **Implementation:** choose namespaces, projects, DbContexts, schemas, or processes only to enforce already justified boundaries.

For aggregate design, read [aggregate-boundaries.md](references/aggregate-boundaries.md). For behavior and rule placement, read [ddd-and-rule-placement.md](references/ddd-and-rule-placement.md). For coordination across aggregates, read [application-processes.md](references/application-processes.md).

## Preserve domain integrity

- Keep invariants and state transitions in the domain model.
- Reference other aggregates by identity unless a repository-specific mechanism preserves the same boundary without exposing mutable cross-aggregate graphs.
- Modify one aggregate instance per transaction as a strong default, not an absolute law. Name the invariant before making a multi-aggregate write atomic.
- Let Application load authoritative facts, authorize the use case, invoke domain decisions, and coordinate I/O.
- Use domain events for meaningful completed domain facts. Choose synchronous or asynchronous handling from consistency and delivery requirements, not from event naming alone.
- Keep simple CRUD simple when no valuable domain model is present.

Read [authorization.md](references/authorization.md) only when permissions depend on domain state or resource ownership.

## Route implementation work

- Use `zerya-dotnet-persistence-evolution` after changing aggregate persistence, EF Core mappings, transactions, read models, schema, or data.
- Use `zerya-wolverine-vertical-slices` for Wolverine handlers, middleware, outbox, scheduling, or routing.
- Use `zerya-dotnet-modular-monolith` only after a business boundary and concrete isolation benefit are established.

For reviews, read [architecture-review.md](references/architecture-review.md), report findings by severity with evidence and consequences, and distinguish facts from modeling hypotheses. Do not edit during a review unless requested. Read [sources.md](references/sources.md) only for provenance or further study.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Zerya .NET DDD Architecture"
short_description: "Discover and improve .NET domain models"
default_prompt: "Use $zerya-dotnet-ddd-architecture to discover or review this .NET domain model and its boundaries."
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Aggregate Boundaries

## Start with invariants

Name the business rules that must remain true at the end of every successful transaction. Cluster only the state required to enforce those rules. Treat an aggregate as a consistency and change boundary, not an object graph, query shape, table group, or ownership label.

For each candidate boundary establish:

- the invariant protected by the root;
- the commands that may change it;
- the state required to decide those commands;
- the concurrency unit and expected contention;
- whether temporary inconsistency outside the boundary is acceptable;
- whether every external mutation can pass through the root.

## Use secondary signals carefully

Independent identity, lifecycle, import, correction, retention, query needs, large `Include` graphs, partial loading, and unrelated concurrency conflicts may expose a wrong boundary. None proves that an entity is a new aggregate root.

Promote an entity to an aggregate root only when it must be independently addressed as a consistency boundary, owns meaningful invariants and lifecycle transitions, and can protect them without routing changes through the former root.

Keep an entity inside an aggregate when the root must control its creation, mutation, or removal to preserve a named invariant. Keep a Value Object inside when identity is irrelevant and replacement preserves its semantics.

## Design the interaction

After splitting an aggregate:

- reference the other aggregate by identity;
- enforce each aggregate's local invariants internally;
- load authoritative external facts in Application and pass domain concepts to a named domain policy when the business decision spans aggregates;
- define stale-data tolerance when a decision uses a snapshot or projection;
- use optimistic concurrency on the boundary whose invariant is changing;
- create a read model when a use case needs a combined query shape.

Do not merge aggregates merely to make a screen, report, import, or orchestration step convenient. Do not split them merely because a collection is large; first determine whether the collection participates in an invariant and whether the model can represent that invariant without loading every member.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Application Processes

Use an application process when a use case coordinates aggregates, repositories, authorization, external ports, or durable steps. Keep coordination in Application and business decisions in the domain model.

## Coordinate a local use case

1. Resolve actor facts and reject coarse unauthorized access early.
2. Load the target resource and only the authoritative facts required for the decision.
3. Perform resource-based authorization without leaking existence or sensitive state.
4. Invoke aggregate behavior or a named domain policy.
5. Commit the chosen consistency boundary.
6. Persist outgoing facts durably when later reactions are required.

Reading another aggregate does not add it to the write boundary. Pass only the facts or domain concepts required for the decision, and define whether they must be current.

## Coordinate multiple writes

Modify one aggregate instance per transaction as the default. When a use case appears to require more:

1. Name the exact invariant and the state it protects.
2. Check whether the aggregates are wrongly split.
3. Check whether reservation, ordered steps, compensation, or temporary inconsistency satisfies the business.
4. Use a multi-aggregate local transaction only when a documented invariant truly requires atomicity and the operational platform supports it.
5. Use a durable process manager or saga when work spans time, retries, external systems, or compensation.

Do not invent a synthetic aggregate solely to hide orchestration. Do not use domain events to disguise a reaction that must be atomic with the originating invariant.

For long-running processes define state ownership, correlation, idempotency, timeout, retry, compensation, observability, and manual repair.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# DDD Architecture Review

Review domain evidence before code structure.

1. State the business capability and vocabulary in scope.
2. Trace representative commands, decisions, failures, events, and queries end to end.
3. Map Bounded Context hypotheses, context relationships, aggregate lifecycles, invariants, consistency needs, and ownership.
4. Compare declared rules with actual code, persistence, tests, and production constraints.
5. Distinguish model defects from persistence coupling, framework conventions, and missing technical enforcement.
6. Recommend the smallest model correction, then route implementation consequences to the relevant skill.

Classify findings as ambiguous language, context leakage, incorrect aggregate boundary, false invariant, misplaced domain rule, procedural domain logic, ownership ambiguity, consistency mismatch, or accidental implementation coupling.

For every material finding provide:

- concrete file and line evidence;
- the affected business behavior;
- the violated or unsupported model assumption;
- consequence and realistic failure mode;
- smallest viable correction;
- verification through examples, tests, or expert confirmation.

Label conclusions inferred only from code as hypotheses. Do not equate class size, folder structure, table count, or project layout with a Bounded Context.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Authorization

Keep authentication and authorization distinct. Authentication establishes actor facts; the use case decides whether that actor may act on a resource.

- Resolve stable actor identity and coarse permissions at the boundary.
- Load the target resource in Application before resource-based authorization.
- Place reusable business permission rules in explicit policies with clear ownership.
- Keep HTTP status mapping and claims parsing outside Domain.
- Do not pass a mutable current-user service into aggregates.
- Avoid stale authorization projections for decisions requiring current ownership, assignment, or status.
- Test denial paths, information disclosure, tenant boundaries, and state-dependent permissions.

If the rule is also a domain invariant, enforce the invariant in Domain even after an application-level authorization check.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# DDD and Business-Rule Placement

Place a rule where its business meaning can be expressed and protected without infrastructure knowledge. Distinguish a business decision from loading data, sequencing calls, authorization, persistence, and transport mapping.

## Entity or Aggregate

Use behavior on an Entity or Aggregate Root when it protects that object's state, invariants, and transitions. Expose intention-revealing operations rather than public mutation. Do not force behavior onto an aggregate merely because the use case starts there.

## Value Object

Use a Value Object when identity does not matter and validity can be established at creation. Make equality structural and prevent partially valid states. Use types that reflect the ubiquitous language rather than wrapping every primitive mechanically.

## Domain Policy or Domain Service

Use a named domain policy when a business decision spans concepts but does not naturally belong to one entity. Pass it domain values or authoritative facts loaded by Application. Keep EF queries, network calls, logging, authorization infrastructure, and message publication outside the policy.

Use a domain service sparingly. Give it a name from the ubiquitous language and keep it stateless unless state itself is a domain concept.

## Application Service or Process

Use Application to:

- load aggregates and authoritative facts;
- perform resource authorization;
- invoke domain behavior and policies;
- coordinate repositories and external ports;
- define transaction and retry boundaries;
- translate completed domain outcomes into external messages.

Application may enforce input, existence, sequencing, and operational rules. It must not become the only place where business meaning exists.

## Handler or Endpoint

Keep a handler thin enough that its flow reveals the use case. It may validate message shape, authorize, load state, invoke Application or domain behavior, map results, and arrange publication. Extract behavior when conditions, loops, status switches, fallback choices, or error interpretation encode domain meaning.

## Events

Raise a domain event for a meaningful completed fact in the domain model. Decide its handling mode separately:

- handle synchronously when another reaction must complete inside the same consistency boundary and transaction;
- handle after commit when temporary inconsistency is acceptable;
- publish an integration event when the fact becomes a stable contract across a system or ownership boundary.

Do not classify an event solely by namespace, process boundary, or transport. Document delivery, idempotency, ordering, and compatibility requirements for every asynchronous contract.

## Avoid ceremony

Use a rich domain model selectively. Straightforward CRUD and reference data may remain simple. Do not add empty repositories, forwarding services, factories without creation policy, or events for property changes that have no domain meaning.
17 changes: 17 additions & 0 deletions skills/dotnet/zerya-dotnet-ddd-architecture/references/sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Sources

Open only the sources relevant to the decision.

## Strategic DDD

- [Eric Evans, DDD Reference](https://domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf) — definitions and pattern summaries for ubiquitous language, Bounded Context, context mapping, entities, Value Objects, Services, and Aggregates.
- [Martin Fowler, Bounded Context](https://martinfowler.com/bliki/BoundedContext.html) — concise explanation of model consistency and language boundaries.
- [Martin Fowler, Domain-Driven Design](https://martinfowler.com/bliki/DomainDrivenDesign.html) — overview of domain modelling and strategic design.

## Aggregates and tactical modelling

- [Martin Fowler, DDD Aggregate](https://martinfowler.com/bliki/DDD_Aggregate.html) — aggregate root, integrity, persistence, and transaction boundary.
- [Vaughn Vernon, Implementing Domain-Driven Design sample](https://www.informit.com/content/images/9780321834577/samplepages/0321834577.pdf) — true invariants, small aggregates, identity references, and eventual consistency.
- [Microsoft, Design a microservice domain model](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/microservice-domain-model) — .NET-oriented aggregate and domain-model guidance; apply the tactical concepts without assuming a microservice deployment.

Treat all patterns as inputs to collaborative modelling. Prefer observed business language, examples, invariants, and consistency requirements over pattern matching.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Strategic Domain Modeling

## Discover the model

Work with domain experts, product language, process descriptions, examples, policies, events, and observed code. Capture:

- business outcomes and decisions;
- terms whose meaning changes by workflow, department, customer, or lifecycle stage;
- core, supporting, and generic subdomains;
- authoritative sources and ownership;
- upstream/downstream relationships and required translations;
- places where one model cannot remain internally consistent.

Treat event storming, example mapping, process modelling, and code archaeology as complementary discovery techniques. Do not let any workshop notation become the architecture automatically.

## Propose Bounded Contexts

Define a Bounded Context around one internally consistent model and ubiquitous language. For each candidate record:

```text
Purpose and business outcome:
Ubiquitous language and ambiguous terms:
Model and policy owner:
Authoritative data and writes:
Upstream and downstream contexts:
Translation or anti-corruption needs:
Evidence, assumptions, and unresolved questions:
```

Use organizational, lifecycle, regulatory, scaling, and change-cadence evidence to challenge a boundary, not to replace semantic modelling. A team, namespace, schema, service, or module may implement a Bounded Context but does not define one by itself.

## Map relationships

Name the relationship and direction explicitly. Decide whether the downstream conforms, translates through an anti-corruption layer, shares a published language, or requires another context-map pattern. Avoid a shared kernel unless the teams deliberately accept joint model ownership and coordinated change.

Validate proposed contexts against real scenarios and language. Keep uncertain boundaries reversible and revisit them as the model evolves.
Loading