Building the Spring Boot Application

Dependency Injection

ReadingPreview

আপনি একটি 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.
  • Handler owns incoming application/transport handling.
  • UseCase owns one application workflow.
  • Repository owns persistence access.
  • Service is reserved for third-party/external capabilities.
  • Internal application classes will not use generic names like OrderService.
  • Domain objects such as Order, OrderItem, Product, and Inventory are 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 new for 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.