Start Learning
Javaneer
Back to stage
Module 9·Capstone: Build BookVault

Scaffolding & the Domain Model

Start from Spring Initializr and shape the core: the Book, Member, and Loan model, typed configuration, and the container wiring it together.

16 min readIntermediate
On this page

Every Spring app starts the same way: generate a project, then model the problem. This lesson builds BookVault's foundation - the scaffold, the domain model, typed configuration, and the container that wires it together. Get this layer right and everything above it falls into place.

Scaffold with Spring Initializr

Head to start.spring.io (or your IDE's Spring initializer) and generate a Maven, Java 21, Spring Boot project. Start with the dependencies you know you'll need first - Spring Web and Validation - and add the rest (Data JPA, Security, Actuator) as each layer arrives. The generated project boots immediately:

@SpringBootApplication
public class BookVaultApplication {
    public static void main(String[] args) {
        SpringApplication.run(BookVaultApplication.class, args);
    }
}

@SpringBootApplication triggers component scanning and auto-configuration - the container starts, finds your beans, and (once Web is present) an embedded Tomcat comes up. ./mvnw spring-boot:run and you have a running app to build on.

Model the domain

BookVault's domain is three concepts. Start them as plain Java, focused on the rules, before any persistence annotations:

  • Book — an ISBN, title, author, and a count of copies available. It owns the rule you can't reserve a copy that isn't there.
  • Member — someone who can borrow, with a role (MEMBER or LIBRARIAN).
  • Loan — links a member to a borrowed book, with borrowed/due/returned dates.

Put behavior on the objects, not in a service that reaches into their fields:

public class Book {
    private int copiesAvailable;

    public void reserveCopy() {
        if (copiesAvailable <= 0) {
            throw new NoCopiesAvailableException(isbn);
        }
        copiesAvailable--;          // the object guards its own invariant
    }
}

Rich domain objects, not anemic ones

A common trap is 'anemic' objects - all getters/setters, with every rule living in services. Keeping reserveCopy() on Book means the invariant ('never below zero') can't be violated from elsewhere. The object protects itself. This is the same encapsulation you learned in core Java, now paying off in a real service.

Typed configuration

BookVault has knobs - how long a loan lasts, how many books a member may hold. Don't scatter magic numbers; bind them to a typed @ConfigurationProperties record:

@ConfigurationProperties("bookvault")
public record BookVaultProperties(int loanDays, int maxActiveLoans) {}

Now application.yml supplies the values, they're validated and type-safe, and any environment can override them without a code change - the externalized-config habit from the start.

Let the container wire it

Services declare what they need in the constructor; the container supplies it. No new, no lookup

  • just dependencies you can see and test:
@Service
public class LendingService {
    private final BookRepository books;
    private final BookVaultProperties properties;

    // constructor injection: the container provides these
    public LendingService(BookRepository books, BookVaultProperties properties) {
        this.books = books;
        this.properties = properties;
    }
}
Foundations and framing before the fittings

You don't hang doors before the walls stand. Scaffolding and the domain model are BookVault's foundation and frame: the shape everything else attaches to. Rush the frame and every later layer inherits the wobble; get it square and plumb, and the web, data, and security layers snap on cleanly.

Where does the rule live?

A teammate suggests putting the 'can't reserve when copies are zero' check inside LendingService, leaving Book a bag of getters and setters. Explain why placing reserveCopy() on Book itself is the better design, and what could go wrong with the service-only approach.

In BookVault's foundation, why bind settings to a @ConfigurationProperties record and put rules like reserveCopy() on the domain object?

Key takeaways

  • Generate the project with Spring Initializr (Maven, Java 21); add dependencies per layer as you go.
  • @SpringBootApplication starts the container, scans for beans, and auto-configures an embedded server.
  • Model the domain (Book, Member, Loan) as focused objects that own their rules - rich, not anemic.
  • Bind settings to a typed @ConfigurationProperties record so config is externalized, validated, and overridable per environment.
  • Declare dependencies via constructor injection and let the container wire them - no new, no lookups.
  • A square, well-encapsulated foundation is what every later layer attaches to cleanly.
Was this lesson helpful?
Edit this page on GitHub