soda docs v17.0.1

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.

CQRS

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”.

DDD

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

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.

Safety

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.

Adapterinbound / outbound
REST ControllersAdviceDTO / VO
Applicationuse-case orchestration
CommandsQueriesHandlers
Domainbusiness core
Aggregate RootsEntitiesDomain Events
Infrastructuretechnical details
RepositoriesACLAssemblers

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:

AspectsodaTypical DDD framework
Learning curve● simple●● moderate
Event sourcingextensiblefull 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.

1

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.

2

The command enters the bus

busFacade.sendCommand(cmd) hands the command to the CommandBus, which finds the matching CommandHandler bean by generic type.

3

The aspect moves context

CqrsAroundHandler's @Around writes the command's requestId / user / tenant into a ThreadLocal (DomainEventContext) for later use.

4

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.

5

The repository saves

writeRepository.save(domain). On save, RepositoryEventAspect intercepts, takes the suspended events, and publishes them to the event bus.

6

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.

7

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

5 minutes

Quickstart

Generate a project on the website → unzip → run it. H2 in-memory database built in; no database install.

30 minutes

Write a Command

Walk the full write chain: aggregate root → CommandHandler → repository.

Hands-on

Project Structure

How the four layers are laid out, how dependencies point, and what goes in each package.

Deep dive

Context Propagation

How requestId and user identity travel across threads and services.

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.

xml
<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:

xml
<dependency>
  <groupId>com.hibuka.soda</groupId>
  <artifactId>soda-event-redis</artifactId>
  <version>17.0.1</version>
</dependency>
Note

Repository: github.com/chenkangzeng1/soda-core. The sample project is meant to be copied — the Project Structure section has the full directory tree.