From skraft
Use when selecting architecture patterns for a new feature, performing Event Modeling, defining bounded contexts, choosing DDD tactical patterns, evaluating pattern fitness, or understanding how patterns compose. Covers Event Modeling methodology, DDD strategic design, DDD tactical patterns, Clean Architecture, CQRS, and Event Sourcing.
How this skill is triggered — by the user, by Claude, or both
Slash command
/skraft:architecture-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A catalogue of architectural patterns used in Clean Architecture + DDD + Event-Driven systems.
A catalogue of architectural patterns used in Clean Architecture + DDD + Event-Driven systems.
Core principle: Patterns are tools — select based on problem, not preference. Every pattern added to a design must be justified by a story or a quality attribute requirement. YAGNI applies to architecture.
The patterns in this skill compose in a specific order: Event Modeling → DDD Strategic → DDD Tactical → Clean Architecture → CQRS, with Event Sourcing applied at the Domain/Infrastructure boundary when history has value.
BDD-first methodology to model system behaviour as a timeline of events. It is the starting point for every DESIGN session.
| Concept | Definition | Example |
|---|---|---|
| Command | An expression of user or system intent. Imperative mood. May be rejected. | CheckEligibility, SubmitApplication |
| Event | An immutable fact that records a state change. Past tense. Cannot be rejected. | EligibilityChecked, ApplicationSubmitted |
| Read Model | A query output optimised for a specific view. No mutation. | EligibilityResult, ApplicationSummary |
Commands cause Events. Events update Read Models. The timeline reads left to right:
[Command] → [Event] → [Read Model]
Each vertical grouping of Command + Event + Read Model is a Slice — the minimum unit of deliverable business value.
timeline
title Eligibility Check — Event Timeline
section Command
CheckEligibility : Submitted by Driver
section Event
EligibilityChecked : Raised by EligibilityAggregate
section Read Model
EligibilityResult : Consumed by Driver UI
Colour coding convention (for tooling that supports it):
Each slice maps to one or more Gherkin scenarios:
Eligibility Check flow:
CheckEligibility (Driver, driverId, requestedAt)EligibilityChecked (driverId, result: Eligible|Ineligible, reason?)EligibilityResult (eligible: bool, validUntil?, rejectionReason?)Application Submission flow:
SubmitApplication (applicantId, policyType, vehicleInfo)ApplicationSubmitted (applicationId, submittedAt, status: Pending)ApplicationStatus (applicationId, status, submittedAt)| Mistake | Why it matters | Fix |
|---|---|---|
| Modelling implementation steps instead of business events | Creates a technical model, not a business model | Reframe in past tense business facts |
| Missing Read Models | Queries become undefined; DISTILL phase can't produce query scenarios | Identify every query the user needs before acting |
| Too many aggregates | Inflates complexity; over-splits consistency boundaries | Group events that must be consistent with each other |
| Commands that cannot fail | Suggests you are modelling state changes, not intent | All commands can be rejected — model the rejection event |
A Bounded Context is the explicit boundary within which a domain model applies. Within the boundary, every term in the Ubiquitous Language has exactly one meaning.
Identification heuristics:
| Pattern | Relationship | When to use | Risk |
|---|---|---|---|
| Upstream/Downstream | One context feeds another | When one context's model drives another's | Downstream is coupled to upstream changes |
| Conformist | Downstream adopts upstream's model | When upstream is powerful and the cost of translation is too high | Downstream polluted by upstream concepts |
| Anti-Corruption Layer (ACL) | Downstream translates upstream's model | When upstream uses a different or legacy model | Translation cost; must be maintained |
| Shared Kernel | Two contexts share a subset of the domain model | Small shared concepts between close teams | Changes to shared kernel must be agreed by both teams |
| Partnership | Two contexts co-evolve | Equal teams with strong alignment | High coordination overhead |
| Open Host Service | Upstream publishes a versioned API | When many downstreams consume the same upstream | API versioning discipline required |
| Published Language | Open Host Service uses a shared language (e.g., events schema) | Event-driven integration between contexts | Schema governance needed |
Apply these rules before labelling any arrow on the context map. They constrain which relationship is admissible, independently of how trivial the integration looks today.
Rule 1 — A Core subdomain is never Conformist. Conformist means the downstream submits to the upstream's model with no isolation layer, surrendering control of its own Ubiquitous Language. That is acceptable only for Supporting or Generic downstreams that cannot justify the translation cost. A Core subdomain is the competitive advantage; its language must be protected from upstream pollution. If the downstream is Core, the relationship is an ACL (ideally with the upstream exposing an Open Host Service / Published Language) — never Conformist.
Rule 2 — Consuming a published contract is not Conformist. Conformist requires consuming the upstream's internal domain model directly. If the upstream exposes a stable, simplified, public contract (a ViewModel, an event schema, a DTO) and hides its domain entities, the upstream is providing an Open Host Service + Published Language. The downstream reading that contract is not conformist to the upstream domain.
Rule 3 — A local copy or translation is an ACL. If the downstream keeps its own copy of the consumed data, or translates the upstream contract into its own types at the boundary — even a trivial translation such as reading a single boolean — that boundary is an Anti-Corruption Layer (Translation Layer). "The translation is trivial today" does not downgrade it to Conformist; the protection is structural, not proportional to current translation effort.
Decision table — Conformist vs ACL vs OHS/PL:
| Observation in the design | Correct label |
|---|---|
| Downstream imports upstream domain entities directly, no translation layer | Conformist (admissible only when downstream is Supporting/Generic) |
| Upstream exposes a stable public contract (ViewModel/event/DTO), hides its domain | Open Host Service + Published Language (upstream side) |
| Downstream translates or copies that contract into its own types at the boundary | Anti-Corruption Layer (downstream side) |
| Downstream is a Core subdomain | ACL — Conformist is forbidden regardless of translation effort |
| Subdomain | Definition | Investment | Examples in auto insurance |
|---|---|---|---|
| Core | The competitive advantage of the business | Build, invest, DDD deep dive | Eligibility engine, risk scoring |
| Supporting | Necessary but not differentiating | Build or buy, standard patterns | Document management, notifications |
| Generic | Commodity functionality | Buy or use off-the-shelf | Authentication, payment processing |
graph LR
EligibilityContext -->|ACL| PolicyContext
PolicyContext -->|Conformist| BillingContext
EligibilityContext -->|Published Language| NotificationContext
| Mistake | Symptom | Fix |
|---|---|---|
| Big Ball of Mud | One context for everything; all models coupled | Split by team ownership or language boundary |
| Anemic Domain | All logic in application services; domain objects are data bags | Move invariants back into aggregates |
| Over-splitting | 15 tiny contexts with no clear purpose | Merge contexts that share a language and team |
Definition: A cluster of entities and value objects with a consistency boundary. Changes are always atomic within an aggregate.
Rules:
Size guideline: Prefer small aggregates. Large aggregates cause contention and complexity. If an aggregate has more than 3–5 entities, consider splitting.
Auto-insurance example: EligibilityAggregate — root is Eligibility, contains DriverHistory, RiskScore. It enforces the invariant: "An eligible driver must have fewer than 3 accidents in 5 years."
Definition: An object with a distinct identity that persists over time. Two entities with the same data are still different entities if their IDs differ.
DriverId), surrogate key (UUID)Definition: An object that has no conceptual identity. Two value objects with the same data are equal.
RiskScore instead of intAuto-insurance example: RiskScore(value: 75), DriverId(value: "DRV-001"), DateRange(from, to)
Definition: An immutable record of something that happened in the domain. Past tense. Raised by an aggregate root.
EligibilityChecked, NOT EligibilityCheckEventDefinition: Abstracts the storage mechanism for an aggregate.
IEligibilityRepository, not IGenericRepository<T>GetEligibleDrivers, not FindAll(x => x.Status == "Eligible"))Definition: Stateless logic that doesn't naturally belong to an aggregate or value object.
When to extract:
When NOT to extract:
Definition: Encapsulates a business rule as a predicate. Composable with And, Or, Not.
Best use: Complex eligibility rules — compose HasCleanRecord.And(HasMinimumExperience).And(IsLicenceValid).
┌────────────────────────────────────┐
│ API / Presentation │ ← Controllers, DTOs, ViewModels
├────────────────────────────────────┤
│ Application │ ← Use Cases, Commands, Queries
│ │ Application Interfaces (I*Repository, I*Publisher)
├────────────────────────────────────┤
│ Domain │ ← Aggregates, Entities, Value Objects,
│ │ Domain Events, Domain Services
├────────────────────────────────────┤
│ Infrastructure │ ← Persistence, Messaging, External APIs
│ │ Implements Application Interfaces
└────────────────────────────────────┘
Source code dependencies point inward only.
The Application layer is the entry point for all external requests. Controllers never touch Domain objects directly — they go through use cases.
Any I/O abstraction (repository, message publisher, external gateway) is defined as an interface in the Application layer. Infrastructure provides the implementation. This keeps the Application layer testable without real infrastructure.
Command Query Responsibility Segregation separates the model used to handle commands (mutations) from the model used to handle queries (reads).
| Signal | Apply CQRS? |
|---|---|
| Read and write models have different shapes | Yes |
| High read/write ratio | Yes |
| Complex reporting or projection needs | Yes |
| Simple CRUD with no diverging read/write concerns | No — over-engineering |
| Requirement is to "use CQRS" without a concrete problem | No — justify with a problem |
| Variant | Description | When |
|---|---|---|
| Simple CQRS | Same database, separate command and query models | Most cases |
| Full CQRS | Separate stores (event store for commands, read DB for queries) | When Event Sourcing is also adopted |
Ask: "Does knowing the history of state changes provide business value?" If NO → do NOT use Event Sourcing.
CheckEligibility)EligibilityChecked)Store the aggregate's current state after every N events to avoid full replay on every load.
Coordinate cross-aggregate workflows. Sit in the Application layer.
Example: EligibilityRenewalSaga — listens for EligibilityExpired event, issues RenewEligibility command to EligibilityAggregate, then NotifyDriver command to NotificationAggregate.
Handle event schema evolution when the structure of an old event type changes.
Optimistic concurrency: when appending to the event store, pass the expected version. If the stored version differs, a conflict has occurred.
| Scenario | Strategy |
|---|---|
| Transient (e.g., two concurrent updates from different UI sessions) | Retry — reload and reapply the command |
| Domain-specific (e.g., two agents claim the same appointment slot) | Merge — apply domain logic to determine the correct outcome |
| Critical (e.g., financial transaction double-spend) | Reject — return a conflict error and require user intervention |
| Technique | Description |
|---|---|
| Read-your-own-writes | After a command, return a token; query endpoint waits for projection to catch up to that version before returning |
| Optimistic UI updates | UI applies the expected state change immediately without waiting for projection |
| Causal consistency tokens | Pass a token that represents "everything up to this point was processed" |
Solves the dual-write problem: atomically writing to the event store AND publishing to a message broker.
Avoids the risk of the event store committing but the broker publish failing (or vice versa).
Handles uniqueness constraints (e.g., unique driver licence number) in event-sourced systems.
ReservationAggregate for the unique keyReserveLicenceNumber → LicenceNumberReserved (or LicenceNumberAlreadyReserved)Patterns compose in a specific order. Start from the outside and work inward:
Event Modeling (what the system does)
→ DDD Strategic Design (where the boundaries are)
→ DDD Tactical Design (what the domain model looks like)
→ Clean Architecture (how layers are structured)
→ CQRS (how commands and queries are separated)
→ Event Sourcing (how state is persisted, when history matters)
→ Sagas (how cross-aggregate workflows are coordinated)
Key composition rules:
| Problem type | Recommended pattern | When NOT to use | Notes |
|---|---|---|---|
| Simple CRUD entity | Clean Architecture + Repository | Don't add Event Sourcing or CQRS | Keep it simple |
| Complex business rules with invariants | DDD Tactical (Aggregate + Specification) | Don't use anemic domain model | |
| Audit trail / regulatory compliance | Event Sourcing | Only if history has real business value | Requires infrastructure investment |
| Multiple diverging read models | CQRS with projections | Not needed for simple queries | Read models are disposable |
| Cross-aggregate coordination | Saga / Process Manager | Don't put saga logic in an aggregate | |
| High read/write ratio | CQRS with read-optimised projections | Don't use for simple apps | |
| Complex domain with multiple teams | DDD Strategic + Context Mapping | Overhead for small teams | |
| Event-driven integration | Domain Events + Outbox Pattern | Don't use for in-process only |
Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub sebastiendegodez/skraft-plugin --plugin skraft