Building the Spring Boot Application

Beans and the Application Context

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

আগের lesson-এ আমরা Dependency Injection শিখেছি।

আমাদের application flow:

Handler
    ↓
UseCase
    ↓
Repository

Third-party dependency থাকলে:

Handler
    ↓
UseCase
    ├── Repository
    └── External Service

Example:

public CreateOrderHandler(
        CreateOrderUseCase createOrderUseCase
) {
    this.createOrderUseCase = createOrderUseCase;
}

এখানে CreateOrderHandler তার dependency নিজে তৈরি করছে না।

কিন্তু নতুন প্রশ্ন আসে:

CreateOrderUseCase object-টি কে তৈরি করবে?

আর CreateOrderUseCase যদি তিনটি repository-এর উপর depend করে:

public CreateOrderUseCase(
        ProductRepository productRepository,
        InventoryRepository inventoryRepository,
        OrderRepository orderRepository
) {
}

তাহলে repository objects-গুলো কে তৈরি করবে?

Spring এই object construction এবং wiring manage করতে পারে।

Spring যে objects manage করে, সেগুলোকে বলা হয়:

Beans

আর যে container Beans create, configure এবং connect করে, সেটি হলো:

Application Context


What Is a Spring Bean?

Simple definition:

A Spring Bean is an object whose creation and lifecycle are managed by Spring.

Suppose:

public class CreateOrderUseCase {
}

এটি একটি normal Java class।

আমরা যদি করি:

CreateOrderUseCase useCase =
        new CreateOrderUseCase(...);

তাহলে object-টি আমরা manually create করছি।

Spring এটি manage করছে না।

কিন্তু Spring যদি object-টি create এবং manage করে, সেই instance একটি Bean।

Conceptually:

Java Class
    ↓
Spring creates instance
    ↓
Spring Bean

Class and Bean Are Different

A class is a Java type definition.

A Bean is a Spring-managed object instance।

For example:

public class CancelOrderUseCase {
}

class।

Spring runtime-এ এর একটি managed instance তৈরি করতে পারে।

That instance is a Bean।


Not Every Object Should Be a Bean

আমাদের application-এ অনেক object থাকবে:

Order
OrderItem
Product
Inventory
CreateOrderCommand
OrderResponse

এগুলো automatically Spring Bean নয়।

For example:

Order order =
        new Order(customerId, items);

এটি একটি business object।

Spring-এর কাজ every Order instance manage করা নয়।


Which Objects Usually Become Beans?

আমাদের architecture অনুযায়ী natural Bean candidates:

Handlers

UseCases

Repositories

External Service implementations

Security components

Configuration components

For example:

CreateOrderHandler

CreateOrderUseCase

PostgresOrderRepository

ProviderPaymentService

এসব long-lived application collaborators।


Domain Objects Usually Remain Plain Java Objects

Domain objects:

Order
OrderItem
Product
Inventory

specific business state represent করে।

Suppose:

Order #1001
Order #1002
Order #1003

প্রতিটি আলাদা business entity।

তাই:

@Component
public class Order {
}

আমাদের model-এর জন্য ঠিক হবে না।

Question:

Spring কোন Order create করবে?

Domain entities workflow-এর মধ্যে create বা repository থেকে reconstruct হবে।


Bean vs Domain Instance

Think of:

CreateOrderUseCase

as reusable application behaviour।

One UseCase instance may handle many operations।

But:

Order #5001

is one specific domain entity।

Conceptually:

CreateOrderUseCase Bean
        ↓
handles many requests

Order A
Order B
Order C

What Is the Application Context?

Spring-এর runtime container হলো:

ApplicationContext

Useful mental model:

Application Context contains and manages the application's Spring Beans and their dependency relationships.

Conceptually:

ApplicationContext

├── CreateOrderHandler
├── CreateOrderUseCase
├── ProductRepository
├── InventoryRepository
├── OrderRepository
└── ProviderPaymentService

Spring এই graph create করে application startup-এর সময়।


What Happens During Startup?

Our application starts from:

SpringApplication.run(
        OrderManagementApplication.class,
        args
);

Conceptually:

JVM starts
    ↓
main(...)
    ↓
SpringApplication.run(...)
    ↓
ApplicationContext created
    ↓
Bean definitions discovered
    ↓
Beans created
    ↓
Dependencies resolved
    ↓
Application ready

This is why dependency problems often appear during startup।


Spring Builds a Dependency Graph

Suppose:

CreateOrderHandler
        ↓
CreateOrderUseCase
        ├── ProductRepository
        ├── InventoryRepository
        └── OrderRepository

Spring cannot create CreateOrderHandler until CreateOrderUseCase exists।

And CreateOrderUseCase cannot be created until required repositories exist।

Conceptually:

Repositories
    ↓
CreateOrderUseCase
    ↓
CreateOrderHandler

Spring resolves this graph।


Constructor Injection and the Application Context

Example:

public class CreateOrderHandler {

    private final CreateOrderUseCase useCase;

    public CreateOrderHandler(
            CreateOrderUseCase useCase
    ) {
        this.useCase = useCase;
    }
}

And:

public class CreateOrderUseCase {

    private final OrderRepository orderRepository;

    public CreateOrderUseCase(
            OrderRepository orderRepository
    ) {
        this.orderRepository = orderRepository;
    }
}

Spring can:

find OrderRepository
    ↓
create CreateOrderUseCase
    ↓
create CreateOrderHandler

This is Dependency Injection at runtime।


How Does Spring Know What to Manage?

Two important mechanisms:

Component Scanning

Explicit Bean Configuration

Component Scanning

Spring can scan packages for classes marked as managed components।

Common annotations include:

@Component

@Repository

@Controller

@RestController

Spring also has @Service, but in our project terminology Service is reserved for external/third-party capabilities rather than internal application workflows।


A UseCase as a Bean

For example:

@Component
public class CreateOrderUseCase {

    private final OrderRepository orderRepository;

    public CreateOrderUseCase(
            OrderRepository orderRepository
    ) {
        this.orderRepository = orderRepository;
    }
}

@Component tells Spring:

Create and manage this application component.

The class name tells us its application responsibility:

CreateOrderUseCase

These are separate concerns।


A Handler as a Bean

Conceptually:

@Component
public class CreateOrderHandler {

    private final CreateOrderUseCase useCase;

    public CreateOrderHandler(
            CreateOrderUseCase useCase
    ) {
        this.useCase = useCase;
    }
}

Later, when we build REST APIs, framework-specific HTTP annotations may define the exact Handler implementation।

But architecturally:

Handler → UseCase

remains the flow।


Repository as a Bean

Persistence implementation may look conceptually like:

@Repository
public class PostgresOrderRepository
        implements OrderRepository {
}

This tells Spring:

this is a managed persistence component

while:

OrderRepository

represents the persistence boundary used by the UseCase।

Exact JPA implementation comes later।


External Service as a Bean

Suppose:

public interface PaymentService {

    PaymentResult pay(
            PaymentRequest request
    );
}

Provider implementation:

@Component
public class ProviderPaymentService
        implements PaymentService {

    @Override
    public PaymentResult pay(
            PaymentRequest request
    ) {
        return null;
    }
}

Then:

public class PayOrderUseCase {

    private final PaymentService paymentService;

    public PayOrderUseCase(
            PaymentService paymentService
    ) {
        this.paymentService = paymentService;
    }
}

Spring can inject the provider implementation through the PaymentService boundary।


Why We Don't Call Internal Classes Services

Our internal terminology remains:

Handler
UseCase
Repository

Service is reserved for external/third-party capabilities such as:

PaymentService

IdentityService

NotificationService

So instead of:

OrderService
ProductService

we use:

CreateOrderUseCase

CreateProductUseCase

CancelOrderUseCase

This makes responsibility clearer।


Component Scanning Starts From the Application Package

Our main class lives at:

io.liveklass.ordermanagement
@SpringBootApplication
public class OrderManagementApplication {
}

Spring Boot normally scans its package and subpackages।

So components under:

io.liveklass.ordermanagement.order

io.liveklass.ordermanagement.product

io.liveklass.ordermanagement.inventory

can be discovered automatically।


Why Package Placement Matters

Suppose an application component accidentally lives at:

com.example.payment.ProviderPaymentService

outside the default scan path।

Spring may not discover it।

Then PayOrderUseCase requiring:

PaymentService

may fail during startup।

Correct package structure prevents unnecessary custom scanning।


Prefer Correct Structure Over Broad Scanning

You could configure:

@ComponentScan(...)

to scan many packages।

But if the component belongs to the application, better place it under:

io.liveklass.ordermanagement

unless there is a real reason otherwise।

Use framework conventions where they align naturally with our architecture।


What Happens if a Bean Is Missing?

Suppose:

@Component
public class CreateOrderUseCase {

    public CreateOrderUseCase(
            OrderRepository orderRepository
    ) {
    }
}

but Spring knows no OrderRepository Bean।

Then startup fails।

Conceptually:

CreateOrderUseCase
        ↓
OrderRepository
        ↓
missing

This is useful।


Fail Fast

A required dependency being missing should usually prevent application startup।

Better:

startup fails

than:

application starts
    ↓
first production request fails

Spring's container helps detect broken composition early।


How to Debug Missing Beans

When Spring cannot construct a component, ask:

Which Bean failed?

Which constructor dependency is missing?

Does Spring know an implementation?

Is the implementation inside the scan path?

Was it registered as a Bean?

Are there multiple candidates?

Think in terms of dependency graph rather than annotation magic।


Multiple Bean Candidates

Suppose:

public interface PaymentService {
}

and Spring finds:

ProviderAPaymentService

ProviderBPaymentService

Then:

public PayOrderUseCase(
        PaymentService paymentService
) {
}

is ambiguous।

Spring sees:

Need:
1 PaymentService

Found:
2 implementations

It cannot safely guess which one the application wants।


Resolving Multiple Candidates

Spring provides mechanisms such as:

@Qualifier

@Primary

explicit configuration

But our current v1 has only one payment provider।

So we do not need this complexity now।


Avoid Creating Multiple Implementations Without a Requirement

Don't add:

DefaultPaymentService

DemoPaymentService

BackupPaymentService

just to demonstrate DI।

Production code should reflect actual system requirements।

Test doubles stay in tests।


Explicit Bean Configuration

Component scanning is not the only way to create a Bean।

Spring configuration can explicitly define one।

Example:

@Configuration
public class PaymentConfiguration {

    @Bean
    public PaymentService paymentService() {
        return new ProviderPaymentService();
    }
}

The object returned by:

paymentService()

becomes a Spring Bean।


Why Use @Bean?

Explicit Bean configuration is useful when:

we don't own the class

construction needs runtime configuration

third-party clients need setup

composition should be explicit

For example, a Payment Service may need:

base URL
API key
timeout

That construction may belong naturally in configuration।


Component Scanning vs Explicit Configuration

Component scanning

@Component
public class CreateOrderUseCase {
}

Spring discovers it।

Explicit configuration

@Bean
public CreateOrderUseCase createOrderUseCase(
        OrderRepository orderRepository
) {
    return new CreateOrderUseCase(
            orderRepository
    );
}

Both approaches create a Bean।


Which Should We Use?

For our own straightforward application components:

Handlers
UseCases

component scanning may be convenient।

For infrastructure and third-party integration construction:

external clients
configured adapters

explicit configuration may be clearer।

The goal is predictable composition, not loyalty to one technique।


@Configuration

A class:

@Configuration
public class PaymentConfiguration {
}

contains application composition/configuration logic।

It should not contain business workflows।

Bad:

@Bean
public Order createOrder() {
    // business workflow
}

Bean configuration defines reusable components, not request-specific operations।


Beans Can Depend on Other Beans

Example:

@Bean
public PaymentService paymentService(
        PaymentClient paymentClient
) {
    return new ProviderPaymentService(
            paymentClient
    );
}

Spring resolves PaymentClient and passes it into the Bean factory method।

Same Dependency Injection principle।


Bean Scope

By default, most Spring Beans are singleton-scoped within one Application Context।

Meaning:

one managed instance

is typically reused across application requests।

For example:

CreateOrderUseCase Bean

may handle many requests।


Why UseCases Should Be Stateless

Bad:

@Component
public class CreateOrderUseCase {

    private CustomerId currentCustomer;

    public void execute(
            CustomerId customerId
    ) {
        this.currentCustomer = customerId;
    }
}

If the Bean is shared, concurrent requests may overwrite the same field।


Keep Request Data Local

Better:

public Order execute(
        CustomerId customerId,
        CreateOrderCommand command
) {
    return null;
}

Request-specific values remain:

method parameters

local variables

domain objects

Stable collaborators remain constructor fields।


Stable Dependency vs Request State

Constructor fields:

OrderRepository
PaymentService

Method parameters:

CustomerId
OrderId
CreateOrderCommand

Domain objects:

Order
OrderItem
Inventory

This distinction helps prevent shared mutable state bugs।


Singleton Bean Does Not Mean Global Singleton Pattern

Spring singleton means approximately:

one Bean instance per ApplicationContext

It does not mean:

one universal JVM object forever

And it has nothing to do with domain uniqueness।


Bean Identity vs Domain Identity

Spring Bean:

CreateOrderUseCase

Domain entity:

Order #5001

These are entirely different concepts।

Spring manages application components।

Domain IDs identify business records।


Do Not Store Business State in Beans

Bad production repository:

@Repository
public class OrderRepository {

    private final Map<Long, Order> orders =
            new HashMap<>();
}

if PostgreSQL is supposed to be the production source of truth।

A Bean being long-lived does not mean business state should live in its fields।


Test Fakes Are Different

A test-only:

FakeOrderRepository

may legitimately keep objects in memory।

That is controlled test infrastructure।

Production business state still belongs in PostgreSQL।


Plain Java Domain Objects Are a Good Thing

A strong Spring application should still contain many normal Java objects।

For example:

Order order =
        new Order(customerId, items);

order.cancel();

No Spring needed।

Spring manages composition around the domain, not the domain itself।


A Healthy Runtime Separation

Conceptually:

Spring-managed components

Handler
    ↓
UseCase
    ↓
Repository / External Service

During execution:

UseCase
    ↓
creates/loads
Order
Inventory
Product

This keeps framework composition separate from business state।


Application Context Is Not a Service Locator

Because Application Context contains Beans, someone might do:

applicationContext.getBean(
        CreateOrderUseCase.class
);

inside business code।

We avoid this।

Why?

Because dependencies become hidden।


Prefer Constructor Injection

Bad:

public class CreateOrderHandler {

    private final ApplicationContext context;

}

Better:

public class CreateOrderHandler {

    private final CreateOrderUseCase useCase;

    public CreateOrderHandler(
            CreateOrderUseCase useCase
    ) {
        this.useCase = useCase;
    }
}

Application Context should perform composition from outside the business code।


Don't Inject ApplicationContext Into Domain Code

Especially avoid:

public class Order {

    private ApplicationContext context;

}

Domain logic should not know Spring exists।

That would unnecessarily couple business behaviour to the framework।


Bean Lifecycle

At a high level:

Bean definition discovered
    ↓
Bean constructed
    ↓
dependencies supplied
    ↓
Bean initialized
    ↓
Bean used
    ↓
application shutdown

Spring can provide lifecycle hooks, but we don't need them for normal business workflows।


Bean Lifecycle Is Not Order Lifecycle

Spring Bean lifecycle:

created
used
destroyed

Order lifecycle:

UNPAID
    ↓
PAID

or:

UNPAID
    ↓
CANCELLED

These are completely different।

Do not model domain lifecycle using Spring component lifecycle।


Avoid Business Side Effects During Bean Creation

Bad:

public ProviderPaymentService() {
    chargeCustomer();
}

Constructors/configuration should create valid components।

They should not execute arbitrary business operations।

Startup should remain predictable।


Spring Data Repositories Later

When we add Spring Data JPA, we may define repository interfaces that Spring provides implementations for।

Conceptually:

public interface OrderJpaRepository
        extends JpaRepository<OrderEntity, Long> {
}

Spring Data can create a runtime implementation and register it as a Bean।

We will cover that in the persistence module।

For now, remember:

Repository contract
        ↓
runtime implementation
        ↓
Spring Bean

contextLoads() Now Makes More Sense

Our bootstrap test:

@SpringBootTest
class OrderManagementApplicationTests {

    @Test
    void contextLoads() {
    }
}

starts an Application Context।

If:

Bean is missing

dependency is ambiguous

configuration is invalid

the test may fail before the test method does anything।


What contextLoads() Does Not Prove

Passing context startup does not prove:

Order rules are correct

API behaviour is correct

SQL is correct

security is correct

It only gives confidence that application composition/startup works।


Use Plain Tests When Spring Is Irrelevant

If testing:

Paid Order cannot be cancelled

Spring is unnecessary।

Example:

Order order = ...;

order.markPaid();

assertThrows(
        IllegalStateException.class,
        order::cancel
);

This should remain a normal domain test।


Use Spring Tests When Spring Behaviour Matters

Examples:

Does Spring create the Handler?

Does it inject the UseCase?

Does repository configuration work?

Does security configuration work?

Then starting an Application Context makes sense।


Auto-Configuration

Spring Boot also provides auto-configuration

At a high level, it looks at things such as:

dependencies on the classpath

application configuration

existing Beans

and automatically configures common infrastructure where appropriate।

Later, when we add:

Spring Web
Spring Data JPA
Spring Security

we will see auto-configuration in practice।


Auto-Configuration Is Not Magic

When Spring creates something automatically, useful questions are:

Which dependency enabled this?

Which configuration influenced it?

Which Bean was created?

Can our own configuration override it?

Understanding these questions makes production debugging much easier।


Spring Is the Composition Mechanism

Our architecture decides:

Handler
UseCase
Repository
External Service
Domain

Spring decides:

how reusable application components
are constructed and connected

Don't reverse this relationship।

Spring should support the architecture, not define it।


Example Object Graph

Eventually:

ApplicationContext

CreateOrderHandler
        ↓
CreateOrderUseCase
        ├── ProductRepository
        ├── InventoryRepository
        └── OrderRepository

PayOrderHandler
        ↓
PayOrderUseCase
        ├── OrderRepository
        └── PaymentService

During execution those UseCases work with:

Product

Inventory

Order

OrderItem

which are business objects rather than application-wide Beans।


When Should a Class Become a Bean?

Ask:

Is this a reusable application collaborator?

Does it have dependencies Spring should compose?

Does framework-managed lifecycle/configuration help?

Or is this simply business state/value?

Examples:

CreateOrderUseCase:

Yes → likely Bean

OrderRepository implementation:

Yes → likely Bean

ProviderPaymentService:

Yes → likely Bean

Order:

No → normal domain object

OrderItem:

No → normal domain object

Common Mistake 1 — Everything Becomes a Bean

Avoid:

@Component
class Order {
}

just because Spring exists।


Common Mistake 2 — Nothing Uses the Container

Avoid manually constructing infrastructure everywhere:

new CreateOrderUseCase(
        new PostgresProductRepository(),
        new PostgresInventoryRepository(),
        new PostgresOrderRepository()
);

inside Handlers।

Centralized composition is one of Spring's main benefits।


Common Mistake 3 — ApplicationContext Lookup

Avoid:

context.getBean(...)

inside UseCases।

Constructor dependencies should remain explicit।


Common Mistake 4 — Request State in Beans

Avoid:

private OrderId currentOrderId;

inside shared UseCase Beans।

Keep operation-specific state local।


Common Mistake 5 — Bean for Every Rule

Avoid creating Spring components such as:

OrderTotalCalculator

OrderCancellationValidator

when behaviour naturally belongs to domain objects।


Common Mistake 6 — Broad Component Scanning

Don't fix poor package organization by scanning the entire company namespace।

Place application components deliberately।


Common Mistake 7 — Multiple Beans Without Intent

If two implementations satisfy one dependency, there must be a deliberate selection strategy।

Spring should not be expected to infer business intent।


Common Mistake 8 — Context Test as Business Test

contextLoads() is not evidence that business features are correct।

Testing must match the behaviour being protected।


How This Connects to Dependency Injection

Dependency Injection lesson:

A component receives its collaborators.

This lesson:

Spring's Application Context
creates and supplies those collaborators.

Together:

Bean definitions
    ↓
ApplicationContext
    ↓
Constructor Injection
    ↓
Handler → UseCase → Repository

Engineering Principle

The core principle:

A Spring Bean is a reusable application object managed by Spring, while the Application Context is the container responsible for creating and connecting those managed objects.

Another:

Spring should manage application collaborators, not every business object in the domain.

And:

Use the container to make dependencies explicit and predictable, not to hide them behind dynamic lookups.


Summary

In this lesson, we learned that:

  • A Java class and a Spring Bean are different concepts.
  • A Bean is an object managed by Spring.
  • The Application Context is Spring's runtime container.
  • During startup Spring discovers Bean definitions, creates Beans, and resolves their dependencies.
  • Handler → UseCase → Repository maps naturally to a Spring dependency graph.
  • External Services can also participate in that graph.
  • Handlers, UseCases, Repositories, external Service implementations, configuration, and security components are natural Bean candidates.
  • Domain objects such as Order, OrderItem, Product, and Inventory usually remain plain Java objects.
  • Spring can discover Beans through component scanning.
  • Spring can also create Beans explicitly using @Configuration and @Bean.
  • The application's root package affects component scanning.
  • Correct package placement is preferable to unnecessarily broad scanning.
  • Missing required Beans should normally cause startup to fail.
  • Multiple Bean candidates require an explicit selection decision.
  • Most application Beans are singleton-scoped by default within the Application Context.
  • Therefore UseCases and similar components should avoid mutable request-specific state.
  • Bean lifecycle and domain lifecycle are unrelated concepts.
  • Business state should not live in singleton Bean fields.
  • ApplicationContext should not be used as a service locator inside business code.
  • Plain Java domain objects remain important even inside a Spring application.
  • contextLoads() verifies application composition, not complete business correctness.
  • Spring Boot auto-configuration will become more visible as we add Web, JPA, and Security dependencies.
  • Spring is our composition mechanism; application architecture still determines the correct responsibilities and dependencies.

Next lesson:

Configuration and Application Properties

There we will learn how runtime values such as database settings, external Service URLs, credentials, and timeouts enter the application without being hardcoded inside Handlers or UseCases.