Overview
Guide domain modeling and framework-agnostic code against the current io.ddd4j.core public contracts. The core boundary is: ddd4j-core defines DDD / CQRS / SPI; Spring, Guice, Quarkus, Javalin, MyBatis, and friends only assemble in the adapter layer.
When to Use
- Modeling or reviewing a ddd4j domain aggregate (
AggregateRoot<ID>) in Active Record or Event Sourcing mode. - Implementing the write side —
Command,CommandExecutor,CommandBus,Result<R>. - Implementing the read side —
Query<M>,PersistenceQueryScope<M, P>,Repository<M, ID>. - Working with
DomainEvent<ID>metadata, publication, or replay. - Deciding whether a dependency belongs in the domain or in the adapter layer.
- Resolving
Contexts,ThreadContext,Subject, orCacheSPI contracts.
When NOT to Use
Do not use this skill when:
- Module boundaries or dependency direction are the question — use
ddd4j-architectureinstead. Install:npx skills add full-stack-skills/ddd4j-skills --skill ddd4j-architecture. - Annotation retention, targets, or consumers are the question — use
ddd4j-annotationinstead. Install:npx skills add full-stack-skills/ddd4j-skills --skill ddd4j-annotation. - Maven parent, BOM, or version ownership is the question — use
ddd4j-bominstead. Install:npx skills add full-stack-skills/ddd4j-skills --skill ddd4j-bom. - JSON, bean, string, collection, or ID utilities are the question — use
ddd4j-kitinstead. Install:npx skills add full-stack-skills/ddd4j-skills --skill ddd4j-kit. - A concrete ORM, broker, or cache product must be configured — use
ddd4j-data,ddd4j-mq, orddd4j-cache. - The project is not on ddd4j — generic DDD and Java skills apply; do not retrofit ddd4j types onto a non-ddd4j codebase.
- The code still uses
io.hiwepy.bootCRUD base classes — that is a legacy contract, not the currentddd4j-coreAPI.
Trigger Keywords
English: aggregate root, domain event, CQRS command, query, repository SPI, event sourcing, context cleanup, subject
Chinese: aggregate root, domain event, command bus, query, repository interface, event sourcing, context cleaning, topic
Quick Start
- "Build an event-sourced aggregate with ddd4j."
- "Implement Command, CommandExecutor, and the CommandBus dispatch."
- "Review whether the domain layer wrongly depends on Spring or MyBatis."
- "Explain the boundary between
ThreadContext,Contexts, andSubject."
Audience, Routing, and Customization
- Domain developers — provide the aggregate strategy, target maintenance line, and business invariants.
- Adapter developers — provide the runtime, repository / bus implementation, and transaction boundaries.
- Reviewers — require read-only inspection and specify whether the focus is DDD, CQRS, context, or SPI.
- Non-ddd4j projects — hand off to the generic DDD / Java skills; do not retrofit ddd4j types.
When input is insufficient, give a tentative verdict based on the current checkout first, then list missing: specific item; how to provide: path, branch, or contract.
Always confirm the current branch first; the 1.0.x, 2.0.x, and 3.0.x APIs are not interchangeable by name.
Capability Boundaries
✅ Strong At
AggregateRoot<ID>with both Active Record and Event Sourcing modes.Command,CommandExecutor,CommandBus, andResult<R>write-side contracts.Query<M>,PersistenceQueryScope<M, P>, andRepository<M, ID>.DomainEvent<ID>metadata, publication, replay, and event handlers.Contexts/ThreadContext,Subject,Cache, and other framework-agnostic SPIs.
⚠️ Needs Input
- Target maintenance line with source code and POM.
- Domain model, persistence strategy, and transaction boundary.
- Runtime adapter and real acceptance behavior.
❌ Out of Scope
io.hiwepy.boot.api.*—BaseEntity,PaginationEntity,ApiRestResponse.- Treating Spring annotation semantics as the universal runtime contract.
- Replacing specific MyBatis / JPA / Jackson / Sa-Token adapter skills.
Core Rules
| Topic | Current Contract |
|---|---|
| Aggregate root | Extend AggregateRoot<ID>; the identifier implements Serializable |
| Persistence mode | Choose Active Record OR Event Sourcing — never mix both on the same aggregate |
| Events | Subclasses keep a no-arg constructor; use registerEvent, replay via loadFromHistory |
| Commands | Command expresses intent; executed by CommandExecutor, dispatched via CommandBus.execute |
| Queries | Query<M> binds to the domain model; opt into PO fields with explicit persistence(P.class) |
| Repository | Domain only depends on Repository; the implementation is registered by the data / runtime adapter |
| Context | Request / thread scopes must be released on completion — Subject or tenant data must not leak |
| Cache | Domain depends only on the Cache SPI; cross-instance atomic semantics must be proven by the implementation |
Examples
public final class Order extends AggregateRoot<OrderId> {
private OrderStatus status;
public void pay() {
if (status == OrderStatus.PAID) {
throw new IllegalStateException("Order already paid");
}
registerEvent(new OrderPaid(id()));
}
@EventHandler
void apply(OrderPaid event) {
status = OrderStatus.PAID;
}
}
When using event sourcing, persist pullDomainEvents() and stop calling snapshot-style save() / update().
Workflow
Step 1: Confirm the maintenance line
Identify the current branch (1.0.x, 2.0.x, or 3.0.x) and read the active AggregateRoot, Command, Query, Repository, and DomainEvent source. The three lines are not interchangeable by name.
Step 2: Choose the persistence mode
Decide Active Record (snapshot save()) or Event Sourcing (pullDomainEvents() plus loadFromHistory). Decide once per aggregate and record the choice; never mix both on the same aggregate.
Step 3: Model the aggregate
Extend AggregateRoot<ID>, keep all mutations inside the aggregate body, register events with registerEvent(...), apply them in @EventHandler methods, and preserve a no-arg constructor on every event subclass.
Step 4: Wire the contracts
Define Command / CommandExecutor for the write path and Query<M> for the read path. The domain depends only on the Repository SPI; concrete data adapters register the implementations.
Step 5: Verify behavior
Run aggregate invariant tests, event replay tests, command result tests, and context cleanup tests. Confirm the domain layer compiles without framework imports.
Gotchas
- Continuing the legacy
BaseEntity / Model<T>inheritance chain — the current contract isAggregateRoot<ID>. - Importing Spring or MyBatis Wrapper types directly into the core domain layer.
- Persisting both aggregate snapshots and uncommitted events on the same aggregate.
- Treating
DomainEvent.source()as a completeEntityIdPath— it is a string-compatible view only. - Assuming
Repositorydefault methods work without testing the actual adapter; they may throwUnsupportedOperationException. - Binding
ThreadContextbut failing to clean it up in afinallyblock or scope close, leaking Subject or tenant data. - Copying
3.0.xJava, Jackson, or API semantics directly onto older lines without re-verification.
Output and Exceptions
Cite specific package names, source paths, and maintenance lines. When a symbol cannot be resolved, return missing: symbol or module in the current branch; how to provide: confirm branch, POM, and source path. Do not substitute legacy project types.
Capability Routing
- Module boundaries and dependency direction — hand off to
ddd4j-architecture. Install:npx skills add full-stack-skills/ddd4j-skills --skill ddd4j-architecture. - Annotation semantics and consumers — hand off to
ddd4j-annotation. Install:npx skills add full-stack-skills/ddd4j-skills --skill ddd4j-annotation. - Maven / BOM / version ownership — hand off to
ddd4j-bom. Install:npx skills add full-stack-skills/ddd4j-skills --skill ddd4j-bom. - JSON, Bean, string, collection, and ID utilities — hand off to
ddd4j-kit. Install:npx skills add full-stack-skills/ddd4j-skills --skill ddd4j-kit.
Privacy and Security
Examples use fictitious orders and identifiers. Never output real tokens, tenant data, user profiles, or private-repository credentials.
This skill does not access, collect, store, or transmit any user data, credentials, or keys; examples use only desensitized fictional data.
FAQ
- Can I still use
BaseEntity? Only when an external project explicitly depends on it; it is not a current ddd4j-core contract. - Must
AggregateRootuse event sourcing? No — but do not mix the two modes on the same aggregate. - Can
Queryreference a PO? Yes, via an explicit persistence scope. The domainQueryitself binds to the aggregate root. - Is
RepositoryMyBatis-specific? No — it is an ORM-agnostic SPI. - Must
DomainServicebe a Spring Bean? Cannot be assumed across runtimes. - What counts as proof of completion? Current branch source, related tests, and verified runtime adapter behavior.