Put DDD in your business code,
not in architecture diagrams
soda is an enterprise-grade DDD / CQRS framework built for Spring Boot. Four-layer architecture, command-query separation, domain events, end-to-end context propagation — one starter dependency to begin.
What it is
soda is not a "layering specification document" — it is a set of code that already runs. Add the dependency, write an aggregate root and a CommandHandler, and the framework's aspects carry context into domain events, wire up the command chain, and cut the request off when recursion runs away.
Commands and queries apart
Writes go Command → CommandHandler → aggregate root → repository; reads go Query → QueryHandler → read store. The two chains evolve independently, which naturally avoids “one method that both reads and writes”.
The aggregate raises its own events
Domain events are produced by the aggregate root when state changes (addPendingEvent), not hand-built in a service layer. Events and state changes share one origin.
Context travels the whole chain
requestId, user identity, tenant, and authorities flow with the command into the aggregate root, into domain events, and across service delivery — async chains don't lose them.
Recursion protection
Synchronous calls have a depth limit of 10; async events have a hopCount limit of 20. When events trigger each other in a cycle, the request stops with a readable error instead of burning CPU.
Four layers. Strict boundaries
Generated for you on day one — and enforced ever after. Every file has exactly one home: touch one dependency, and only one layer is affected.
How the dependency direction is decided, and what each layer may depend on: Core Concepts.
The right amount of framework
The pieces DDD needs are all there, and nothing you don't — no event-sourcing storage tax unless you opt in.
Compared with a typical DDD framework:
| Aspect | soda | Typical DDD framework |
|---|---|---|
| Learning curve | ● simple | ●● moderate |
| Event sourcing | extensible | full support |
| Storage efficiency | ● efficient | ●● needs care |
| Query performance | ● fast | ●● needs care |
| Debugging | ● straightforward | ●● moderate |
| AI-assisted development | ● CLAUDE.md built in | ○ uncommon |
soda's trade-off is blunt: read models tuned for query speed, write models keep the business rules; persistence stays sensible and never forces event sourcing. How that looks in code: Write a Command.
One request, start to finish
This is the most worthwhile thing to understand about soda: what happens between an HTTP request arriving and the response going out. Panorama first, details after.
Controller receives the request
A plain Spring MVC controller — the one you wrote. ControllerAroundHandler (optional) reads Soda-Request-Id and Soda-User-Name from request headers and writes them into the MDC and the command object.
The command enters the bus
busFacade.sendCommand(cmd) hands the command to the CommandBus, which finds the matching CommandHandler bean by generic type.
The aspect moves context
CqrsAroundHandler's @Around writes the command's requestId / user / tenant into a ThreadLocal (DomainEventContext) for later use.
CommandHandler runs domain logic
Your CommandHandler calls aggregate root methods; the aggregate changes state and addPendingEvent(...) suspends a domain event — the event has context injected automatically at this point.
The repository saves
writeRepository.save(domain). On save, RepositoryEventAspect intercepts, takes the suspended events, and publishes them to the event bus.
Events drive downstream work
EventHandlers (event handlers) pick up the event for follow-up work (send messages, notify, write the read model). The Simple bus delivers in-process; the Redis bus delivers across services.
Unified response
The CommandHandler returns a DTO and the aspect maps exceptions to a uniform envelope: BaseException becomes Result.fail(code, message), everything else becomes "internal error" — no internals leak.
Full breakdown: Request Lifecycle.
Where to start
Quickstart
Generate a project on the website → unzip → run it. H2 in-memory database built in; no database install.
Project Structure
How the four layers are laid out, how dependencies point, and what goes in each package.
Version and coordinates
This documentation is rebuilt with each soda-core release; the version in the coordinates below is filled in at build time. The latest version is whatever the Maven repository shows — see Versions & Compatibility.
<dependency>
<groupId>com.hibuka.soda</groupId>
<artifactId>soda-core-base</artifactId>
<version>17.0.1</version>
</dependency>For cross-service event delivery, add one optional dependency:
<dependency>
<groupId>com.hibuka.soda</groupId>
<artifactId>soda-event-redis</artifactId>
<version>17.0.1</version>
</dependency>Repository: github.com/chenkangzeng1/soda-core. The sample project is meant to be copied — the Project Structure section has the full directory tree.