Building the Spring Boot Application
Dependency Injection
আপনি একটি free preview lesson দেখছেন।
আমাদের Order Management Backend এখনো implementation-এর শুরুর দিকে।
বর্তমানে application bootstrap হয়েছে, কিন্তু সামনে যখন real features আসবে তখন একটি component আরেকটি component-এর উপর depend করবে।
For example:
CreateOrderHandler
↓
CreateOrderUseCase
↓
ProductRepository
InventoryRepository
OrderRepository
Payment flow-এ:
PayOrderHandler
↓
PayOrderUseCase
↓
OrderRepository
PaymentService
এখানে একটি গুরুত্বপূর্ণ প্রশ্ন আসে:
একটি object তার প্রয়োজনীয় collaborators কীভাবে পাবে?
এই problem থেকেই আসে Dependency Injection।
What Is a Dependency?
একটি class যদি অন্য কোনো collaborator ছাড়া তার responsibility complete করতে না পারে, সেই collaborator তার dependency।
Example:
public class CreateOrderUseCase {
private final OrderRepository orderRepository;
}
CreateOrderUseCase order persist করতে OrderRepository প্রয়োজন।
Therefore:
CreateOrderUseCase
↓ depends on
OrderRepository
OrderRepository হলো CreateOrderUseCase-এর dependency।
Dependencies Are Normal
Real software objects একে অপরের সঙ্গে collaborate করে।
For example:
CreateOrderHandler
↓
CreateOrderUseCase
CreateOrderUseCase
↓
ProductRepository
CreateOrderUseCase
↓
InventoryRepository
CreateOrderUseCase
↓
OrderRepository
Dependency থাকা কোনো design problem নয়।
Problem হলো:
dependency কে create করছে, এবং object সেটি কীভাবে obtain করছে?
The Naive Approach
Suppose আমরা এমন code লিখলাম:
public class CreateOrderUseCase {
private final OrderRepository orderRepository =
new PostgresOrderRepository();
public void execute() {
// ...
}
}
এখানে CreateOrderUseCase নিজেই concrete repository তৈরি করছে।
এতে UseCase শুধু order creation workflow জানছে না।
এখন এটি এটাও জানে:
which repository implementation exists
how repository is constructed
which persistence technology is used
Responsibilities mix হয়ে যাচ্ছে।
Construction Is a Different Responsibility
CreateOrderUseCase-এর কাজ:
coordinate order creation
এর কাজ নয়:
construct database infrastructure
Better direction:
Application composition
↓
creates dependencies
↓
injects them into UseCase
Then UseCase শুধু তার business workflow নিয়ে কাজ করে।
Dependency Injection in One Sentence
Dependency Injection means an object receives the collaborators it needs instead of constructing them internally.
Example:
public class CreateOrderUseCase {
private final OrderRepository orderRepository;
public CreateOrderUseCase(
OrderRepository orderRepository
) {
this.orderRepository = orderRepository;
}
}
এখন CreateOrderUseCase আর repository create করছে না।
Repository বাইরে থেকে দেওয়া হচ্ছে।
Constructor Injection
যখন dependency constructor-এর মাধ্যমে দেওয়া হয়, আমরা বলি:
Constructor Injection
Example:
public CreateOrderUseCase(
OrderRepository orderRepository
) {
this.orderRepository = orderRepository;
}
Class-এর constructor দেখেই বোঝা যাচ্ছে:
CreateOrderUseCase requires OrderRepository.
Dependency explicit।
Why Explicit Dependencies Matter
Compare two approaches।
Hidden Dependency
public class CreateOrderUseCase {
private final OrderRepository repository =
new PostgresOrderRepository();
}
Caller দেখে বুঝতে পারে না object ভিতরে কী infrastructure create করছে।
Explicit Dependency
public class CreateOrderUseCase {
private final OrderRepository repository;
public CreateOrderUseCase(
OrderRepository repository
) {
this.repository = repository;
}
}
এখন dependency object-এর construction contract-এর অংশ।
এটি improve করে:
clarity
testability
replaceability
configuration
architecture visibility
Dependency Injection Is Not Spring-Specific
Dependency Injection একটি general software design concept।
Spring ছাড়াও plain Java দিয়ে DI করা যায়।
Example:
OrderRepository orderRepository =
new PostgresOrderRepository();
CreateOrderUseCase createOrderUseCase =
new CreateOrderUseCase(
orderRepository
);
এখানে caller dependency তৈরি করেছে এবং UseCase-এ inject করেছে।
এটাই DI।
Spring পরে এই wiring automate করবে।
Manual Dependency Injection
Suppose:
public class ProductRepository {
}
and:
public class CreateProductUseCase {
private final ProductRepository productRepository;
public CreateProductUseCase(
ProductRepository productRepository
) {
this.productRepository = productRepository;
}
}
Manual wiring:
ProductRepository productRepository =
new ProductRepository();
CreateProductUseCase createProductUseCase =
new CreateProductUseCase(
productRepository
);
Works perfectly।
Spring-এর value আসে যখন application-এর dependency graph বড় হয়।
Our Application Dependency Flow
আমাদের preferred internal application flow:
Handler
↓
UseCase
↓
Repository
External dependency থাকলে:
Handler
↓
UseCase
├── Repository
└── Third-party Service
For example:
PayOrderHandler
↓
PayOrderUseCase
├── OrderRepository
└── PaymentService
এখানে PaymentService third-party/external system represent করে।
Internal business workflow-এর জন্য আমরা Service terminology ব্যবহার করছি না।
What Does a Handler Do?
Handler application boundary থেকে input গ্রহণ করে এবং appropriate UseCase invoke করে।
HTTP context-এ later:
HTTP Request
↓
Handler
↓
UseCase
Handler-এর responsibility হতে পারে:
receive input
perform transport-level mapping
obtain authenticated context
invoke UseCase
map result
Business workflow Handler-এর মধ্যে থাকা উচিত নয়।
What Does a UseCase Do?
UseCase একটি specific application operation coordinate করে।
Examples:
CreateOrderUseCase
CancelOrderUseCase
PayOrderUseCase
CreateProductUseCase
AdjustInventoryUseCase
UseCase সাধারণত:
load required state
coordinate domain behaviour
apply ownership/business rules
call repositories
call external services when required
persist resulting state
করবে।
What Does a Repository Do?
Repository application-owned persistent state access করে।
Examples:
ProductRepository
InventoryRepository
OrderRepository
Repository-এর responsibility:
load data
save data
perform persistence-specific queries
hide database mechanics from UseCase
UseCase-এর মধ্যে raw SQL/JPA mechanics থাকা উচিত নয়।
What Does "Service" Mean in Our Project?
এই course-এ Service reserve থাকবে external/third-party capability-এর জন্য।
For example:
PaymentService
IdentityService
NotificationService
যদি সেগুলো external systems-এর integration boundary represent করে।
Internal application workflow-এর জন্য:
CreateOrderUseCase
not:
OrderService
এটি responsibility naming clearer রাখে।
Dependency Injection Supports This Flow
Consider:
CreateOrderHandler
↓
CreateOrderUseCase
↓
Repositories
Dependencies constructor-এর মাধ্যমে explicit হতে পারে।
Example:
public class CreateOrderHandler {
private final CreateOrderUseCase createOrderUseCase;
public CreateOrderHandler(
CreateOrderUseCase createOrderUseCase
) {
this.createOrderUseCase = createOrderUseCase;
}
}
Then:
public class CreateOrderUseCase {
private final ProductRepository productRepository;
private final InventoryRepository inventoryRepository;
private final OrderRepository orderRepository;
public CreateOrderUseCase(
ProductRepository productRepository,
InventoryRepository inventoryRepository,
OrderRepository orderRepository
) {
this.productRepository = productRepository;
this.inventoryRepository =
inventoryRepository;
this.orderRepository = orderRepository;
}
}
Architecture এখন constructor signatures-এ visible।
Why Constructor Injection Is Our Default
Spring multiple injection styles support করে:
Constructor Injection
Field Injection
Setter Injection
Required collaborators-এর জন্য আমরা Constructor Injection prefer করব।
Reason 1 — Dependencies Are Visible
Example:
public PayOrderUseCase(
OrderRepository orderRepository,
PaymentService paymentService
) {
}
যে engineer class খুলবে, immediately বুঝবে:
PayOrderUseCase needs order persistence
and external payment capability.
Reason 2 — Required Collaborators Stay Required
If object cannot work without repository:
OrderRepository
then constructor ছাড়া object create করা যায় না।
That matches reality।
We don't want partially configured UseCases।
Reason 3 — Dependencies Can Be final
Example:
private final OrderRepository orderRepository;
Dependency construction-এর সময় assign হয়।
তারপর reference change করার প্রয়োজন নেই।
This makes object state easier to reason about।
Reason 4 — Tests Stay Simple
Suppose:
public class CancelOrderUseCase {
private final OrderRepository orderRepository;
public CancelOrderUseCase(
OrderRepository orderRepository
) {
this.orderRepository = orderRepository;
}
}
Test:
OrderRepository repository =
new FakeOrderRepository();
CancelOrderUseCase useCase =
new CancelOrderUseCase(repository);
Spring context ছাড়াই object test করা যায়।
Field Injection
Spring code-এ আমরা দেখতে পারি:
@Autowired
private OrderRepository orderRepository;
এটি Field Injection।
Shorter হলেও required dependencies-এর জন্য আমাদের preferred style নয়।
Why We Avoid Field Injection
Field Injection:
hides construction requirements
makes plain Java construction harder
makes dependencies less obvious
encourages framework-dependent tests
Constructor injection-এর তুলনায় architecture কম visible হয়।
Setter Injection
Example:
public void setOrderRepository(
OrderRepository orderRepository
) {
this.orderRepository = orderRepository;
}
Setter injection genuinely optional/changeable dependency-এর জন্য useful হতে পারে।
কিন্তু আমাদের repositories বা payment dependency optional নয়।
So constructor injection is clearer।
Modern Spring and @Autowired
Suppose Spring-managed class-এর একটাই constructor আছে:
public CreateOrderHandler(
CreateOrderUseCase createOrderUseCase
) {
this.createOrderUseCase = createOrderUseCase;
}
Modern Spring সাধারণত এই constructor automatically ব্যবহার করতে পারে।
Explicit:
@Autowired
লিখতে হয় না।
This keeps the class normal Java-এর মতো readable।
Dependency Injection Does Not Require Interfaces
Important:
DI ব্যবহার করছি মানেই প্রতিটি dependency interface হতে হবে না।
Spring concrete class-ও inject করতে পারে।
For example:
public class CreateOrderHandler {
private final CreateOrderUseCase createOrderUseCase;
}
CreateOrderUseCase concrete class হওয়া perfectly fine।
Avoid Interface + Impl by Habit
আমরা automatically করব না:
CreateOrderUseCase
CreateOrderUseCaseImpl
or:
OrderRepository
OrderRepositoryImpl
unless abstraction-এর real purpose আছে।
Interface should represent a meaningful boundary, not ceremony।
Where an Interface Makes Sense
External service boundary একটি strong example।
Application wants:
process payment
but external provider details should remain isolated।
Possible boundary:
public interface PaymentService {
PaymentResult pay(
PaymentRequest request
);
}
Provider implementation:
public class ProviderPaymentService
implements PaymentService {
@Override
public PaymentResult pay(
PaymentRequest request
) {
// provider-specific HTTP logic
}
}
Then:
public class PayOrderUseCase {
private final PaymentService paymentService;
public PayOrderUseCase(
PaymentService paymentService
) {
this.paymentService = paymentService;
}
}
UseCase knows application-facing capability, not provider protocol।
External Services Are Different From UseCases
This distinction matters।
PayOrderUseCase owns:
business operation coordination
PaymentService owns:
communication with external payment capability
Therefore:
PayOrderUseCase
↓
PaymentService
is natural।
But:
CreateOrderService
for internal use-case workflow would violate our naming convention।
Domain Objects Are Not Dependencies in the Same Sense
Suppose an Order is created:
Order order =
new Order(
customerId,
items
);
Order এখানে request/business state।
এটি Spring-managed application collaborator নয়।
Therefore we do not inject one shared Order into CreateOrderUseCase।
Component Dependency vs Business State
Constructor dependencies of UseCase:
Repositories
External Services
Method input:
CustomerId
OrderId
Command
Domain objects:
Order
OrderItem
Inventory
Each has different role।
Do Not Inject Domain Entities
Bad:
public class CreateOrderUseCase {
private final Order order;
public CreateOrderUseCase(
Order order
) {
this.order = order;
}
}
Which Order?
Every request creates or loads a different Order।
UseCase should work with request-specific domain objects, not a shared injected entity।
Spring Manages Application Components
Conceptually Spring will eventually manage things like:
Handlers
UseCases
Repositories
Security components
External Service clients
Configuration components
But individual:
Order
OrderItem
Product
Inventory
instances are business objects created or reconstructed during workflows।
Example: Handler Injection
Future HTTP-facing Handler:
public class CreateOrderHandler {
private final CreateOrderUseCase createOrderUseCase;
public CreateOrderHandler(
CreateOrderUseCase createOrderUseCase
) {
this.createOrderUseCase = createOrderUseCase;
}
public OrderResponse handle(
CreateOrderRequest request
) {
// map request
// call use case
// map response
return null;
}
}
Handler does not construct UseCase।
Spring injects it।
Example: UseCase Injection
public class CreateOrderUseCase {
private final ProductRepository productRepository;
private final InventoryRepository inventoryRepository;
private final OrderRepository orderRepository;
public CreateOrderUseCase(
ProductRepository productRepository,
InventoryRepository inventoryRepository,
OrderRepository orderRepository
) {
this.productRepository = productRepository;
this.inventoryRepository =
inventoryRepository;
this.orderRepository = orderRepository;
}
}
UseCase does not create persistence implementations।
Why Handler Shouldn't Construct UseCase
Bad:
public class CreateOrderHandler {
private final CreateOrderUseCase useCase =
new CreateOrderUseCase(
new ProductRepository(),
new InventoryRepository(),
new OrderRepository()
);
}
Now Handler knows:
UseCase construction
Repository construction
persistence composition
Its transport-boundary responsibility is polluted।
Why UseCase Shouldn't Construct Repositories
Bad:
public class CreateOrderUseCase {
private final OrderRepository orderRepository =
new JpaOrderRepository();
}
Now application logic knows concrete persistence technology।
If persistence implementation changes, UseCase changes unnecessarily।
DI avoids that construction coupling।
DI and Repository Boundaries
UseCase should depend on repository operations relevant to the business workflow।
Conceptually:
CreateOrderUseCase
↓
ProductRepository
InventoryRepository
OrderRepository
Database details remain below repository boundary।
UseCase should not directly instantiate:
EntityManager
DataSource
PostgreSQL connection
DI and Third-Party Services
Payment example:
Bad:
public class PayOrderUseCase {
private final ProviderPaymentClient client =
new ProviderPaymentClient(
"https://...",
"secret"
);
}
This couples UseCase to:
provider implementation
provider URL
credentials
construction details
Better:
public class PayOrderUseCase {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
public PayOrderUseCase(
OrderRepository orderRepository,
PaymentService paymentService
) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
}
}
Infrastructure wiring supplies the actual provider implementation।
Dependency Injection and Configuration
External service implementation may require:
base URL
API key
timeout
Those values should come from application configuration।
Conceptually:
Configuration
↓
ProviderPaymentService
↓
PayOrderUseCase
UseCase should not read environment variables itself।
We will cover configuration in later lessons।
DI Does Not Mean "Never Use new"
This is a common misconception।
We will use new for normal domain objects।
Example:
OrderItem orderItem =
new OrderItem(
productId,
quantity,
unitPrice
);
That's correct।
What we want to avoid is a high-level component hard-wiring its infrastructure collaborators।
A Useful Question About new
Whenever you see:
new Something(...)
ask:
Is this business state being created, or a long-lived collaborator being hard-wired?
Business state:
new Order(...)
Normal।
Infrastructure collaborator:
new ProviderPaymentService(...)
inside UseCase can be a warning।
DI and Testability
Suppose we want to test PayOrderUseCase।
Constructor:
public PayOrderUseCase(
OrderRepository orderRepository,
PaymentService paymentService
) {
}
Test can supply controlled implementations:
OrderRepository orderRepository =
new FakeOrderRepository();
PaymentService paymentService =
new SuccessfulPaymentService();
PayOrderUseCase useCase =
new PayOrderUseCase(
orderRepository,
paymentService
);
No real PostgreSQL।
No real payment provider।
This allows targeted behaviour testing।
A Fake External Service
Example:
public class SuccessfulPaymentService
implements PaymentService {
@Override
public PaymentResult pay(
PaymentRequest request
) {
return PaymentResult.success();
}
}
A failure fake could return a different controlled outcome।
This is useful for testing workflow decisions।
Mockito Can Also Supply Dependencies
Later we may use:
OrderRepository repository =
mock(OrderRepository.class);
PaymentService paymentService =
mock(PaymentService.class);
PayOrderUseCase useCase =
new PayOrderUseCase(
repository,
paymentService
);
Constructor injection makes this straightforward।
But Mockito is not the reason we use DI।
The real reason is clear composition and responsibility boundaries।
Do Not Mock Domain Objects by Default
Objects like:
Order
OrderItem
Inventory
often should be real objects in tests।
Mocks/fakes are more useful around boundaries such as:
Repositories
External Services
where side effects or infrastructure exist।
Dependencies Make Architecture Visible
Suppose constructor:
public CancelOrderUseCase(
OrderRepository orderRepository,
InventoryRepository inventoryRepository
) {
}
This communicates:
Cancellation coordinates Order and Inventory.
That matches our technical design।
Too Many Dependencies Can Reveal a Problem
Suppose:
public OrderUseCase(
OrderRepository orderRepository,
ProductRepository productRepository,
InventoryRepository inventoryRepository,
PaymentService paymentService,
IdentityService identityService,
NotificationService notificationService,
FileRepository fileRepository,
MetricsRepository metricsRepository
) {
}
This may indicate the class owns too much।
DI makes that coupling visible।
Don't hide the signal with field injection or Lombok।
Ask whether responsibilities should be split।
Constructor Size Is Design Feedback
A constructor with several cohesive dependencies is normal।
For example CreateOrderUseCase needing:
ProductRepository
InventoryRepository
OrderRepository
makes sense।
A constructor with many unrelated dependencies should trigger review।
The point is not a magic maximum number।
The point is cohesion।
Avoid Circular Dependencies
Imagine:
OrderUseCase
↓
InventoryUseCase
and:
InventoryUseCase
↓
OrderUseCase
This creates a cycle।
Spring may struggle with wiring, but more importantly the design itself is unclear।
Cross-Capability Coordination Belongs in the Right UseCase
For cancellation we do not want:
OrderUseCase
↓
InventoryUseCase
↓
OrderUseCase
Instead:
CancelOrderUseCase
├── OrderRepository
└── InventoryRepository
One UseCase owns the workflow।
This avoids circular application dependencies।
Avoid Service Locator Style
A component could theoretically fetch dependencies from Spring manually:
applicationContext.getBean(
OrderRepository.class
);
inside business code।
This hides dependencies again।
Class signature no longer tells us what the component needs।
We avoid this style।
Do Not Inject ApplicationContext Into UseCases
Bad:
public class CreateOrderUseCase {
private final ApplicationContext context;
}
Then dynamically requesting collaborators makes the container act like a global registry।
Prefer explicit:
public CreateOrderUseCase(
ProductRepository productRepository,
InventoryRepository inventoryRepository,
OrderRepository orderRepository
)
Much clearer।
Avoid Static Global Dependencies
Bad:
PaymentServiceHolder.get().pay(...);
or:
GlobalRepositories.orderRepository()
These hide dependency relationships and introduce global state।
Explicit constructor dependencies are safer and easier to understand।
Request Data Is Not a Constructor Dependency
UseCase constructor:
public CreateOrderUseCase(
ProductRepository productRepository,
InventoryRepository inventoryRepository,
OrderRepository orderRepository
)
Operation input:
public Order execute(
CustomerId customerId,
CreateOrderCommand command
) {
}
This is important।
Dependencies are stable collaborators।
CustomerId and CreateOrderCommand change per request।
Keep Request-Specific State Out of Fields
Bad:
public class CreateOrderUseCase {
private CustomerId currentCustomer;
public void execute(
CustomerId customerId
) {
this.currentCustomer = customerId;
}
}
Spring-managed application components may be reused across concurrent requests।
Mutable request-specific fields can create concurrency bugs।
Better:
public Order execute(
CustomerId customerId,
CreateOrderCommand command
) {
// method-local state
}
Application Components Should Generally Be Stateless
CreateOrderUseCase can hold:
repositories
because they are stable collaborators।
It should not hold:
current Order
current Customer
current request
as mutable shared fields।
This keeps concurrent usage safe and easier to reason about।
Dependency Injection Does Not Remove Coupling
Suppose:
public PayOrderUseCase(
ProviderPaymentService paymentService
)
This is still coupled to one concrete provider implementation।
Dependency is injected, but architectural coupling remains।
Better, if provider isolation matters:
public PayOrderUseCase(
PaymentService paymentService
)
where application-facing contract hides provider details।
DI Makes Coupling Explicit
A better statement:
DI does not eliminate coupling; it makes collaborators externally supplied and easier to see, configure, test, and replace.
Good architecture still requires choosing the right dependency boundaries।
Not Every Dependency Needs Abstraction
Suppose Handler depends directly on:
CreateOrderUseCase
That's fine।
We don't need:
CreateOrderUseCaseInterface
for no reason।
The Handler and UseCase are internal application components with clear responsibility।
External Systems Deserve Stronger Boundaries
Third-party system:
Payment provider
has reasons for abstraction:
network failures
provider protocol
credentials
request/response mapping
timeouts
provider-specific changes
Therefore an application-facing PaymentService boundary can be justified।
Repository Boundaries Are Also Meaningful
UseCase should ask:
find Order
save Order
load Inventory
without knowing:
SQL syntax
EntityManager
PostgreSQL connection
Repository abstraction represents persistence capability।
Exact repository implementation comes later।
Dependency Injection and Transactions Are Different
Constructor injection:
CreateOrderUseCase(
ProductRepository,
InventoryRepository,
OrderRepository
)
does not automatically make the workflow transactional।
DI answers:
Which collaborators does the UseCase receive?
Transaction management answers:
Which database operations must commit or roll back together?
Different concerns।
Dependency Injection and Error Handling Are Different
Injecting:
PaymentService
does not answer:
What happens when payment provider times out?
That belongs to payment workflow/integration design।
DI only manages object collaboration।
Dependency Injection and Security Are Different
An authenticated customer ID may be passed:
Handler
↓
UseCase
as request context।
DI is not authentication。
But DI may provide stable security infrastructure components where needed।
Again, separate responsibilities।
Spring's Role
Spring eventually needs to know:
which Handlers exist
which UseCases exist
which Repositories exist
which external Service implementations exist
Then Spring can construct and connect them।
Conceptually:
Spring
↓
creates Repository
↓
creates UseCase(repository)
↓
creates Handler(useCase)
This builds the application object graph।
Dependency Graph
Future order creation path:
CreateOrderHandler
↓
CreateOrderUseCase
├── ProductRepository
├── InventoryRepository
└── OrderRepository
Payment:
PayOrderHandler
↓
PayOrderUseCase
├── OrderRepository
└── PaymentService
The graph represents actual application collaboration।
Spring Wiring Failure
Suppose:
public class CreateOrderUseCase {
public CreateOrderUseCase(
OrderRepository orderRepository
) {
}
}
but Spring cannot find any managed OrderRepository candidate।
Application startup may fail।
That's useful feedback:
required dependency is missing
rather than discovering the problem only when a production request arrives।
Fail Fast
Required component wiring should normally fail early if incomplete।
A backend missing core dependency should not start as if everything is fine।
Spring's dependency resolution helps catch composition errors during startup।
Multiple Implementations
Suppose:
PaymentService
has two implementations:
ProviderAPaymentService
ProviderBPaymentService
Now Spring needs additional information to know which implementation should be injected।
Spring supports mechanisms for resolving this ambiguity।
But our v1 has one payment provider।
We do not need to introduce multiple provider complexity now।
Do Not Create Fake Implementations in Production Just to Teach DI
We will not add:
DemoPaymentService
FakePaymentService
MockPaymentService
to production code merely to demonstrate polymorphism।
Test doubles belong in tests।
Production abstractions should correspond to real boundaries।
Spring Components vs Domain Objects
A useful distinction:
Spring-managed application components:
Handlers
UseCases
Repositories
External Service adapters
Security/configuration components
Ordinary domain objects:
Product
Inventory
Order
OrderItem
Framework manages application composition।
Domain objects represent business state and behaviour।
Handler Example
Conceptually:
public class CancelOrderHandler {
private final CancelOrderUseCase cancelOrderUseCase;
public CancelOrderHandler(
CancelOrderUseCase cancelOrderUseCase
) {
this.cancelOrderUseCase =
cancelOrderUseCase;
}
}
Handler requires one internal workflow collaborator।
UseCase Example
public class CancelOrderUseCase {
private final OrderRepository orderRepository;
private final InventoryRepository inventoryRepository;
public CancelOrderUseCase(
OrderRepository orderRepository,
InventoryRepository inventoryRepository
) {
this.orderRepository = orderRepository;
this.inventoryRepository =
inventoryRepository;
}
}
This communicates the cross-domain coordination clearly।
Domain Behaviour Remains in Domain
DI does not mean all business rules move into UseCases।
For example:
order.cancel();
is still better than:
order.setStatus(CANCELLED);
when cancellation rules belong to Order।
UseCase coordinates:
load Order
verify ownership
Order.cancel()
restore Inventory
save changes
Domain objects protect local invariants।
Avoid Injectable "Rule Services" Without Need
Bad tendency:
OrderCancellationService
OrderTotalService
InventoryValidationService
for every small domain behaviour।
If behaviour naturally belongs to:
Order
or:
Inventory
keep it there।
Spring DI should not turn an object-oriented domain into procedural service classes।
Handler → UseCase → Repository in Practice
A typical path:
HTTP Request
↓
CreateOrderHandler
↓
CreateOrderUseCase
↓
Repositories
↓
PostgreSQL
With third-party integration:
HTTP Request
↓
PayOrderHandler
↓
PayOrderUseCase
├── OrderRepository
└── PaymentService
↓
Payment Provider
This is the terminology we will use throughout the application code।
Constructor Dependencies as Architecture Documentation
Suppose:
public PayOrderUseCase(
OrderRepository orderRepository,
PaymentService paymentService
) {
}
This alone reveals:
PayOrderUseCase
coordinates local Order state
with an external payment capability.
Good constructor signatures make code easier to review।
A Large Constructor Is a Signal
Suppose:
public SomeUseCase(
ProductRepository productRepository,
InventoryRepository inventoryRepository,
OrderRepository orderRepository,
PaymentService paymentService,
NotificationService notificationService,
IdentityService identityService,
AnotherRepository anotherRepository
) {
}
Maybe valid।
But we should ask:
Does one UseCase really own all of this?
Do not hide constructor complexity with field injection।
Use it as design feedback।
Avoid Generic UseCases
We do not want:
OrderUseCase
that does:
create
cancel
pay
history
admin updates
Prefer operation-oriented:
CreateOrderUseCase
CancelOrderUseCase
PayOrderUseCase
when workflows have meaningful separate responsibilities।
This also keeps dependency sets focused।
Handler Naming
Handler should generally correspond to an application entry point/use-case invocation।
Examples:
CreateOrderHandler
CancelOrderHandler
PayOrderHandler
Later HTTP framework details may influence actual class naming, but architecture remains:
Handler → UseCase
What About Controllers?
Spring MVC uses concepts like:
@RestController
We may use framework annotations on Handler classes or have a thin HTTP adapter depending on implementation style।
The important architectural terminology for course logic remains:
Handler
for incoming application handling।
We will finalize exact REST structure in the API module rather than prematurely mixing terminology here।
Spring Annotations Do Not Define Responsibilities
A class annotated:
@Component
is not automatically well-designed।
A class annotated:
@Service
is not automatically the correct place for business logic।
In our codebase, Service has specific meaning for third-party/external capabilities।
Architecture comes from responsibility, not annotation name।
Avoid Naming Internal Components Service
We will avoid internal classes such as:
OrderService
ProductService
InventoryService
because those names blur application workflow responsibilities।
Instead:
CreateOrderUseCase
CreateProductUseCase
AdjustInventoryUseCase
and:
CreateOrderHandler
make intent explicit।
Service Is Reserved for External Capabilities
Examples:
PaymentService
IdentityService
NotificationService
when these represent integrations with external systems.
This distinction keeps vocabulary consistent।
Dependency Injection Review Checklist
When creating a component, ask:
What stable collaborators does this component need?
Are they explicit constructor dependencies?
Is this a Handler, UseCase, Repository,
or third-party Service?
Am I constructing infrastructure directly?
Am I injecting request-specific data by mistake?
Does this dependency belong to this responsibility?
Am I adding an interface without a real boundary?
Does the constructor reveal too much responsibility?
Is there a circular dependency?
Am I using static/global/container lookup
instead of explicit injection?
Common Mistake 1 — Field Injection
@Autowired
private OrderRepository orderRepository;
For required dependencies, prefer constructor injection।
Common Mistake 2 — Infrastructure Construction Inside UseCase
new PostgresOrderRepository();
inside CreateOrderUseCase।
Repository should be injected।
Common Mistake 3 — Third-Party Client Construction Inside UseCase
new ProviderPaymentService(...);
inside PayOrderUseCase।
External Service should be injected through its boundary।
Common Mistake 4 — Injecting Domain Entities
CreateOrderUseCase(Order order)
A specific Order is business state, not shared infrastructure।
Common Mistake 5 — Service Locator
applicationContext.getBean(...)
inside UseCase।
This hides dependencies।
Common Mistake 6 — Static Globals
GlobalRepositories.orderRepository()
Hidden and hard to test।
Common Mistake 7 — Circular UseCases
UseCase A
↓
UseCase B
↓
UseCase A
Usually indicates unclear workflow ownership।
Common Mistake 8 — Request State in Component Fields
Bad:
private OrderId currentOrderId;
inside a shared UseCase instance।
Keep request data in method parameters/local state।
Common Mistake 9 — Interface for Everything
CreateOrderUseCase
CreateOrderUseCaseImpl
without meaningful reason।
DI does not require this।
Common Mistake 10 — Moving Domain Behaviour Into Injectable Components
Avoid:
OrderStateService
for rules that naturally belong inside Order।
UseCase coordinates; domain decides local business state।
Dependency Injection and OOP
Our Java/OOP foundation established that objects collaborate।
DI makes collaboration explicit:
Handler
receives UseCase
UseCase
receives Repository / external Service
rather than each object secretly constructing all collaborators।
This supports:
encapsulation
clear responsibilities
replaceable boundaries
testability
Encapsulation + DI
Domain:
Order
↓
protects order lifecycle
Application:
CancelOrderUseCase
↓
coordinates Order + Inventory
Composition:
Spring
↓
provides repositories and components
Each layer has a distinct role।
DI Is About Composition
Dependency Injection answers:
How does an application component receive the collaborators it needs?
It does not answer:
what business rules exist
where transaction boundaries are
how payment timeout works
how inventory concurrency is handled
Those are separate design concerns।
What Spring Will Do Next
At a high level:
Spring discovers managed components
↓
creates them
↓
resolves constructor dependencies
↓
builds the application object graph
But this leads to several new questions:
What exactly is a Spring-managed component?
What is a Bean?
What is the Application Context?
How does Spring discover Beans?
When does Spring create them?
How does Spring choose what to inject?
Those belong to the next lesson।
Engineering Principle
The core principle:
A component should declare the collaborators it needs instead of constructing or globally discovering them itself.
Another:
Handler receives the request, UseCase coordinates the operation, Repository owns persistence, and Service represents an external capability.
And:
Dependency Injection is a composition mechanism; good architecture still determines which dependencies are appropriate.
Summary
In this lesson, we learned that:
- A dependency is a collaborator required by a component.
- Dependency Injection means receiving collaborators instead of constructing them internally.
- DI is a general software design technique, not something unique to Spring.
- Constructor Injection is our default for required dependencies.
- Constructor Injection makes dependencies explicit and test-friendly.
- Field Injection hides construction requirements and is not our preferred style.
- Modern Spring can use a single constructor without explicit
@Autowired. - Our internal application flow is
Handler → UseCase → Repository. Handlerowns incoming application/transport handling.UseCaseowns one application workflow.Repositoryowns persistence access.Serviceis reserved for third-party/external capabilities.- Internal application classes will not use generic names like
OrderService. - Domain objects such as
Order,OrderItem,Product, andInventoryare not injected as shared application components. - Stable collaborators belong in constructor dependencies.
- Request-specific input belongs in method parameters/local state.
- UseCases should generally remain stateless across requests.
- Using
newfor domain objects is normal. - Hard-wiring repositories or external Services inside UseCases creates unnecessary coupling.
- DI does not require interfaces everywhere.
- Interfaces are useful when they represent real boundaries, especially third-party Services.
- Constructor dependency lists provide useful architecture feedback.
- Circular dependencies often reveal unclear responsibility ownership.
- Business code should not fetch dependencies from
ApplicationContext. - Static/global dependency access should be avoided.
- DI does not solve transactions, concurrency, security, or external failure handling by itself.
- UseCases coordinate workflows while domain objects retain the business behaviour that naturally belongs to them.
Next lesson:
Beans and the Application Context
There we will understand what Spring actually creates and manages, what a Bean is, how the ApplicationContext stores and resolves application components, how component scanning works, and how Spring builds the Handler → UseCase → Repository dependency graph during application startup.