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.