# ddd-by-examples/library: A Java DDD Reference Project with a Library Domain

> ddd-by-examples/library is a teaching codebase that models a public library using Domain-Driven Design, Event Storming, and Behavior-Driven Development, with deliberate architecture choices such as hexagonal layout for the lending context, CQRS for read models, and no ORM anywhere. It is aimed at Java developers who want to see how DDD tactical patterns look in production-grade code rather than toy examples.

**ddd-by-examples/library** — A comprehensive Domain-Driven Design example with problem space strategic analysis and various tactical patterns.

- Repository: https://github.com/ddd-by-examples/library
- Stars: 5,873 · Forks: 831
- Language: Java
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/ddd-by-examples-library

## The Domain Problem and Why It Was Chosen

The project models a public library with patron accounts, book catalogues, holds, and checkouts. The domain was chosen deliberately because it contains both complex business rules and simpler administrative concerns, making it possible to show how the architecture should differ between them. The README describes rules such as: a regular patron is limited to five simultaneous holds while a researcher patron has no limit; a restricted book can only be placed on hold by a researcher; any patron with more than two overdue checkouts at a specific branch gets rejected for new holds at that same branch; an open-ended hold duration is only available to researchers. These rules have enough interdependencies to justify a domain model with aggregates, events, and guard clauses, rather than a flat CRUD service that writes directly to a database table.

## Architecture: Modular Monolith with Per-Context Local Architecture

The project is structured as a modular monolith. Each bounded context is a separate Java package. The README is explicit that this is a deliberate starting point: there are no obstacles to moving contexts into separate Maven modules or microservices later, but the project avoids premature distribution. The lending context, where Event Storming revealed genuine business logic, uses hexagonal architecture. This separates the domain model from Spring and from the database, so the most important business rules can be unit-tested without any infrastructure dependency. Contexts where Event Storming found no complex logic, such as the catalogue, use a simpler CRUD-style local architecture. The mismatch in architectural approach between contexts is intentional and documented: it shows that architecture should be driven by problem complexity, not applied uniformly across all modules.

## Running the Application with Docker Compose

The repository includes a Docker Compose file that starts the application alongside a Prometheus and Grafana monitoring stack. The application container is defined as:

```yaml
services:
  app:
    image: spring/library:latest
    environment:
      - "SPRING_PROFILES_ACTIVE=local"
    ports:
      - '8080:8080'
```

Prometheus runs on port 9090 and Grafana on port 3000. The Dockerfile for the application uses `openjdk:11.0.1-jre-slim-stretch` and exposes port 8080. To build the JAR before composing, the project provides a Maven wrapper (`mvnw`) in the repository root. The Dockerfile copies a file named `library-0.0.1-SNAPSHOT.jar` from the `target` directory, so the Maven build must complete first.

## Aggregates, Events, and the Consistency Choice

Aggregates in the project communicate through domain events. The README addresses the consistency question directly: immediate consistency is simpler but eventual consistency scales better for distributed systems. The team chose to start with immediate consistency and made it straightforward to switch, providing test implementations for both. The consistency model is backed by a `DomainEvents` interface that can be swapped out. The README quotes: 'Good architecture is the one which postpones all important decisions.' The test suite contains Groovy (Spock) specifications that demonstrate both models, with `given/when/then` blocks expressing business scenarios. An example shows a patron hold being published as an event and consumed by a DailySheet read model.

## No ORM and Architecture Tests with ArchUnit

One of the sharper design decisions in the project is the absence of an ORM. The README lists it as a deliberate choice alongside functional-style thinking and architecture-code gap reduction. Using no ORM forces the domain model to stay free of annotations and framework constraints, which is a goal in hexagonal architecture where the domain layer should not depend on infrastructure. The trade-off is more explicit persistence code in the infrastructure layer. Architecture constraints are enforced by ArchUnit, a Java library that lets developers write unit tests against the structure of the codebase. These tests verify that the domain layer does not import Spring classes, that aggregates follow expected naming conventions, and that cross-context dependencies respect the bounded-context boundaries. This means architecture violations are caught in CI rather than in code review.

## CQRS: Patron Profiles and Daily Sheets as Separate Read Models

The project applies CQRS in the lending context. The write model handles holds, checkouts, and returns as commands that publish events. The read models are separate: a patron profile shows a single patron's current holds and checkouts, while daily sheets show branch-wide views for expiring holds and overdue checkouts. The daily sheet check is described as running at the beginning of each day. These read models are populated from events, not by querying the aggregate state directly. This separation is visible in the package structure and tested independently. The README points to the Patron Profiles and Daily Sheets as the concrete CQRS examples to study first.

## Limitations: JDK Version, Missing Migration Tools, and Scope

The Dockerfile targets `openjdk:11.0.1-jre-slim-stretch`, a JDK version that is several years old and no longer actively updated. Teams running JDK 17 or 21 will need to update the image tag before using the Docker setup. The README does not document a migration tool or schema evolution strategy; the project focuses on demonstrating DDD patterns rather than production operational concerns. There are no GitHub releases, so pinning to a specific stable version requires using a commit hash. The project is also intentionally scoped to demonstrating techniques: it does not include authentication, multi-tenancy, or deployment automation, and the README states it does not address specific business logic beyond the library domain.

## Conclusion

Java teams that are adopting DDD and want a worked example with documented Event Storming output, ArchUnit architecture tests, and CQRS read models will find this repository more useful than a generic Spring Boot tutorial. Teams building a SaaS product or a data-intensive system with no domain complexity should look elsewhere: the project explicitly notes that where Event Storming reveals no business logic worth protecting, it uses plain CRUD. The Dockerfile targets openjdk:11.0.1, so teams on newer JDK versions will need to update the image. Verify that the Docker Compose setup starts correctly before relying on it for demonstrations: the monitoring stack (Prometheus on port 9090, Grafana on port 3000) is included but its configuration details are in the monitoring directory rather than the main README.

## FAQ

### Does ddd-by-examples/library use an ORM like Hibernate?

No. The README lists the absence of an ORM as a deliberate design choice to keep the domain model free of framework annotations and infrastructure dependencies. Persistence is handled explicitly in the infrastructure layer.

### Can I run the ddd-by-examples/library project with Docker?

Yes. The repository includes a Docker Compose file that starts the application on port 8080 alongside Prometheus on port 9090 and Grafana on port 3000. The Dockerfile uses openjdk:11.0.1-jre-slim-stretch as the base image.

### What bounded contexts does the library project define?

The README describes lending as the context with genuine business logic, implemented with hexagonal architecture. The catalogue is a simpler context with CRUD-style architecture. Each is a separate Java package within the modular monolith structure.

## Sources

- [ddd-by-examples/library on GitHub](https://github.com/ddd-by-examples/library)
- [Issues](https://github.com/ddd-by-examples/library/issues)
- [License: MIT](https://github.com/ddd-by-examples/library/blob/master/LICENSE)
- [README](https://github.com/ddd-by-examples/library/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ddd-by-examples-library
