Skip to content

Dependency Injection and Layers

Dependency injection supplies collaborators from outside an object instead of letting it construct infrastructure internally. Constructor injection makes required dependencies explicit and supports immutable fields.

@Service
final class BookService {
    private final BookRepository repository;
    private final Clock clock;

    BookService(BookRepository repository, Clock clock) {
        this.repository = repository;
        this.clock = clock;
    }
}

Injecting Clock makes time an explicit dependency and permits deterministic tests. Avoid using the container as a global service locator.

Responsibilities

Layer Typical responsibility
Transport HTTP parsing, representation, status, headers
Application Use-case orchestration and transaction intent
Domain Business invariants and domain decisions
Infrastructure Persistence, messaging, remote-system adapters

This is a dependency guideline, not a requirement for four packages in every small application. Complexity should justify structure.

Ports and adapters

The application can depend on a domain-oriented repository interface while an adapter implements it with JPA or another mechanism. The benefit is not “zero framework imports” as an end in itself; it is an inward dependency direction that keeps core policy testable and changeable.

Bean lifecycle cautions

Singleton scope means one bean instance per application context, not global immutability. Avoid mutable request-specific fields in singleton beans. Know when proxies provide behavior such as transactions, method security, caching, or async execution; calls that bypass a proxy can bypass its advice.

Configuration

Use @ConfigurationProperties for cohesive, type-safe external configuration. Validate required properties at startup. Treat profiles as coarse environment or feature groupings, not as a substitute for well-defined configuration.