Spring REST 1 - Anatomy of a Non-Anemic Spring Demo Application

Posted by bit1 on March 3, 2026

I wanted to write a simple-ish Spring REST application to experiment with these technologies:

  • Java 21
  • Spring Boot 4.x
  • Spring Data JPA
  • Hibernate 7
  • H2 (test database)
  • AssertJ

… and these aims:

  • non-anemic domain model using aggregates
  • separate Command and Query operations
  • sensible tests aimed at specific layers of the application

The domain looks like this:

Image alt

The application is broken down into layers.

  • Domain / JPA Repository Layer
  • Application Service Layer
  • REST Controller Layer

Image alt

I will briefly summarise each of these layers, and point to where the important code and tests are.

Domain / JPA Layer

The main classes in this layer are:

A focus was to avoid an anemic domain model by using aggregates:

An aggregate is a cluster of associated objects treated as a single unit for data changes, with a clearly defined boundary and a root entity that enforces invariants.
— Eric Evans, Domain-Driven Design

Our application has 2 aggregates, Customer and Tag. In this first iteration I am going to focus on the Customer aggregate:

Image alt

The basic idea is that by grouping entities into logical groups (called an aggregate) and controlling access to this group through a single entity (called the aggregate root) we can create better systems.

Enforcement of invariants becomes simpler, because it happens inside the aggregate, rather than leaking into services. And since all traffic to the aggregate goes via the aggregate root, its easier to reason over. In fact, the aggregate root never returns entity objects from within the aggregate.

For example, take Customer, our aggregate root. It has no entity objects as return values. And it is the only entity in the aggregate with public methods - neither Ticket or Profile has any.

Also note that all the entities in the aggregate focus on domain behaviour (like resolveTicket()) rather than setters (like setStatus('resolved')).

Domain tests for the sample application include unit tests that run without any JPA context. And @DataJpaTest tests:

Application Service Layer

The main classes in this layer are:

It can be useful to separate the processes that query a system from the processes that change it. That way we can implement different approaches (or even different models) for the query and the mutate parts. This is the basis of the generic Object Oriented pattern Command Query Separation, and the more involved Command Query Responsibility Segregation (CQRS).

The application services are split between CustomerCommandService and CustomerQueryService.

CustomerCommandService talks directly to the Customer aggregate root to apply changes to the data using Command objects. One outcome of this approach is you can end up with a lot of Command objects, since the recomendation is usually to use one object per command. These simple data carriers can be represented with records for convenience.

CustomerQueryService uses its CustomerQueryRepository and JPQL, avoiding the domain model entirely. The Query side returns projections in the form of records.

Tests for this layer can be found in:

REST Controller Layer

The main classes in this layer are:

This is a vanilla Spring REST controller layer. Its thin, uses DTOs for requests, and basically calls the the Application Layer.

Tests for this layer can be found in:

Other bits (deployment and a little load testing for sanity)

Apart from the Layers its worth mentioning the application has a few other features:

Seeder

The seeder classes are:

The SeedCommandLineRunner runs DemoDataSeederService when the Spring profile is “seed”.

Docker

The Docker classes are:

Running the following will build and seed the application:

docker-compose up --build --detach

Then provided K6 is installed, you can run some basic load tests:

k6 run customer-write-smoke-test.js

To just run the application and Postgres (no seeding):

docker-compose -f docker-compose-no-seed.yaml up --build --detach

GitHub build script

I have included a GitHub Actions script to publish the container image produced by the Spring Demo application. It is published to GitHub Container Registry

Resources