Building the Spring Boot Application
Understanding Spring Boot Project Structure
আপনি একটি free preview lesson দেখছেন।
আগের lesson-এ আমরা BACKEND-101 — Bootstrap Order Management Backend শুরু করেছি এবং একটি minimal Spring Boot application foundation তৈরি করেছি।
Initial structure ছিল:
order-management/
├── build.gradle
├── settings.gradle
├── gradlew
├── gradlew.bat
├── gradle/
│ └── wrapper/
│
└── src/
├── main/
│ ├── java/
│ │ └── io/
│ │ └── liveklass/
│ │ └── ordermanagement/
│ │ └── OrderManagementApplication.java
│ │
│ └── resources/
│ └── application.yml
│
└── test/
└── java/
└── io/
└── liveklass/
└── ordermanagement/
দেখতে simple।
কিন্তু এই structure-এর প্রতিটি অংশের specific responsibility আছে।
এই lesson-এর goal:
Project structure memorise করা নয়; কোন code কোথায় থাকে এবং কেন থাকে সেটা বোঝা।
Project Structure Is Part of Architecture
একটি backend project grow করলে dozens বা hundreds of classes হতে পারে।
যদি files random জায়গায় রাখা হয়:
OrderService.java
Utils.java
Controller2.java
Helper.java
Manager.java
DatabaseThing.java
তাহলে engineer-এর জন্য codebase-এর mental model তৈরি করা কঠিন হয়ে যায়।
একটি useful project structure answer করে:
Where does application code live?
Where does configuration live?
Where do tests live?
Where does build configuration live?
Where should a new capability be added?
Which files belong to the framework,
and which files represent our business domain?
Structure কোনো cosmetic concern নয়।
It affects:
discoverability
dependency direction
maintainability
Spring component scanning
testing
code review
Repository Root
Let's begin from the top।
order-management/
এই directory পুরো repository represent করে।
এর মধ্যে থাকবে:
source code
build configuration
documentation
database migrations
container configuration
CI configuration
as the project evolves।
Repository root should make it possible for a new engineer to understand:
How do I build this?
How do I run this?
Where is the application?
Where is the documentation?
settings.gradle
File:
settings.gradle
At the moment:
rootProject.name = 'order-management'
এর responsibility:
define the Gradle project identity
Simple project-এ এর content খুব ছোট হতে পারে।
Later multi-project Gradle build হলে এখান থেকে subprojects include করা সম্ভব।
কিন্তু আমাদের current application:
single Gradle project
So no need to introduce Gradle submodules।
Gradle Subprojects Are Not Domain Modules
এটি একটি important distinction।
আমরা architecture-এ বলেছি:
Product
Inventory
Order
Payment
logical capabilities।
তার মানে এই নয় যে আমাদের তৈরি করতে হবে:
:product
:inventory
:order
:payment
Gradle subprojects।
Domain/module boundary এবং build module একই জিনিস নয়।
Current system simple enough যে:
one Gradle project
+
capability-oriented Java packages
sufficient।
build.gradle
build.gradle defines how the application is built।
It typically contains:
plugins
Java version
repositories
dependencies
test configuration
build tasks
Example:
plugins {
id 'java'
id 'org.springframework.boot' version '<version>'
id 'io.spring.dependency-management' version '<version>'
}
This tells Gradle which build capabilities are needed।
Build File Is Not Runtime Configuration
Important distinction:
build.gradle answers:
What does the project need to compile,
test, and package?
application.yml answers:
How should the application behave
when it runs?
Example:
implementation 'org.springframework.boot:spring-boot-starter'
belongs to build configuration।
Whereas:
spring:
application:
name: order-management
belongs to runtime application configuration।
Don't mix these concerns।
Gradle Wrapper
Files:
gradlew
gradlew.bat
gradle/wrapper/
form the Gradle Wrapper।
Typical commands:
./gradlew build
./gradlew test
./gradlew bootRun
The wrapper ensures project-defined Gradle tooling can be used consistently।
This matters for:
local development
CI
other engineers
fresh machines
src/main/java
This is where main Java application source lives।
src/main/java/
Java packages map to directory structure।
For:
package io.liveklass.ordermanagement;
file lives under:
src/main/java/
└── io/
└── liveklass/
└── ordermanagement/
The directories are not arbitrary।
They reflect Java package names।
Packages Are Namespaces
Suppose we have two classes:
io.liveklass.ordermanagement.order.Order
and:
io.liveklass.ordermanagement.api.Order
They technically can both be named Order because packages distinguish them।
But just because Java allows something does not mean it is a good naming decision।
Packages help:
group related concepts
avoid naming conflicts
express architecture
control visibility
support framework discovery
Base Package
Our root package:
io.liveklass.ordermanagement
This is the application namespace।
Later:
io.liveklass.ordermanagement.product
io.liveklass.ordermanagement.inventory
io.liveklass.ordermanagement.order
io.liveklass.ordermanagement.payment
io.liveklass.ordermanagement.security
can represent capability boundaries।
Why Reverse-Domain Package Naming?
Java projects often use names such as:
com.company.application
or:
io.liveklass.ordermanagement
because internet-domain-style naming helps create globally distinct namespaces।
For our course project:
io.liveklass.ordermanagement
is clear and consistent।
We do not need to spend more architecture effort on this naming convention।
The Application Class
Our entry point:
package io.liveklass.ordermanagement;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OrderManagementApplication {
public static void main(String[] args) {
SpringApplication.run(
OrderManagementApplication.class,
args
);
}
}
This class should stay near the root package।
Why?
One reason is Spring's default component scanning behaviour।
Component Scanning
Spring needs to discover Spring-managed components।
For example, later we may have:
@RestController
class ProductController {
}
@Service
class ProductApplicationService {
}
@Repository
class ProductRepository {
}
Spring Boot normally scans packages beginning from the package containing the @SpringBootApplication class and its subpackages।
Our main class is:
io.liveklass.ordermanagement
So Spring can naturally discover:
io.liveklass.ordermanagement.product
io.liveklass.ordermanagement.inventory
io.liveklass.ordermanagement.order
because they are below that root।
Why Main Class Placement Matters
Suppose we incorrectly place the application class at:
io.liveklass.ordermanagement.bootstrap
Then default component scanning begins around that package।
Classes in:
io.liveklass.ordermanagement.order
are not children of:
io.liveklass.ordermanagement.bootstrap
So Spring may not discover them automatically।
You could configure scanning manually, but now you've created unnecessary configuration।
Better structure prevents the problem।
Prefer Structure Over Extra Configuration
You could solve component scanning using:
@ComponentScan(...)
with explicit packages।
But if normal package placement solves the problem naturally, additional configuration gives little value।
Principle:
Use Spring Boot conventions when they match the architecture instead of overriding them without a reason.
@SpringBootApplication Is More Than a Marker
Conceptually, @SpringBootApplication combines several Spring concerns।
At a high level it enables:
application configuration
auto-configuration
component scanning
We do not need to memorize its internal annotation composition right now।
Important is understanding why package placement underneath it matters।
src/main/resources
This directory stores non-Java resources that are packaged with the application।
src/main/resources/
For our project, initial content:
application.yml
Later it may contain:
db/migration/
logging configuration
static configuration resources
depending on application requirements।
Resources Are Included in the Application Artifact
When Gradle packages the application, resources under:
src/main/resources
become part of the application's classpath।
That is why Spring Boot can automatically find:
application.yml
and later Flyway can discover:
db/migration/
from its conventional location।
application.yml
Current:
spring:
application:
name: order-management
Later this file may contain safe default configuration such as:
application settings
database connection placeholders
integration configuration keys
But we should avoid turning it into an uncontrolled list of every environment's secrets।
Configuration Is Not Code Organization
Suppose payment provider URL exists।
Bad:
private static final String PAYMENT_URL =
"https://provider.example.com";
inside business code।
Better architecture:
configuration
↓
integration component
But that comes later।
For now, remember:
src/main/java
→ behaviour/code
src/main/resources
→ resources/configuration
src/test/java
Automated test source lives here:
src/test/java/
Package structure commonly mirrors main source where appropriate।
Main class:
src/main/java/io/liveklass/ordermanagement/
OrderManagementApplication.java
Test:
src/test/java/io/liveklass/ordermanagement/
OrderManagementApplicationTests.java
This makes test location predictable।
Tests Belong Near Their Concept Semantically
Later:
order/domain/Order.java
may have:
order/domain/OrderTest.java
under test source।
Product workflow test may mirror:
product/application/
This helps engineers find tests associated with implementation।
Main Code Must Not Depend on Test Code
Dependency direction is:
test
↓
main application code
not:
main application code
↓
test utilities
Test source is excluded from production runtime artifact।
So production code should never rely on classes existing only under:
src/test/java
Test Resources
Later we may also use:
src/test/resources/
for test-specific resources।
For example:
test configuration
fixtures
sample files
if needed।
Again, don't create resources without a real test need।
What About src/main/webapp?
Traditional Java web applications sometimes use:
src/main/webapp
for WAR-based servlet deployment।
Our Spring Boot backend is an executable application and REST backend।
We do not need that structure।
Avoid copying conventions from unrelated Java project types।
Build Output
Gradle generates:
build/
This is not source।
It may contain:
compiled classes
test reports
generated resources
JAR artifacts
Example:
build/libs/
contains packaged application artifacts।
Because this is generated output:
build/
should stay out of Git।
.gradle/
Local Gradle caches/state may appear under:
.gradle/
This also should not normally be committed।
Distinguish it from:
gradle/wrapper/
which should be committed।
A Useful Mental Model
Repository:
build definition
+
source
+
resources
+
tests
+
documentation
Generated locally:
build output
+
IDE state
+
tool caches
The first category belongs in Git।
The second generally does not।
Capability-Oriented Structure
We previously decided our architecture is capability-oriented।
As implementation grows, source may evolve toward:
io/liveklass/ordermanagement/
├── OrderManagementApplication.java
│
├── product/
├── inventory/
├── order/
├── payment/
└── security/
Important:
Top-level packages express the business capabilities of the application.
Layer-First vs Capability-First
A layer-first structure might look like:
controller/
service/
repository/
entity/
dto/
This is common।
For a tiny project it can work।
But our backend will grow across multiple capabilities।
Suppose there are 30 controllers and services।
Then:
service/
doesn't tell you which business area you are looking at।
Capability-first structure improves locality।
Example: Layer-First
controller/
├── ProductController.java
├── InventoryController.java
└── OrderController.java
service/
├── ProductService.java
├── InventoryService.java
└── OrderService.java
repository/
├── ProductRepository.java
├── InventoryRepository.java
└── OrderRepository.java
Product-related code is spread across the entire project tree।
Example: Capability-First
product/
├── ProductController.java
├── ProductApplicationService.java
├── Product.java
└── ProductRepository.java
inventory/
├── InventoryController.java
├── InventoryApplicationService.java
├── Inventory.java
└── InventoryRepository.java
order/
├── OrderController.java
├── CreateOrder.java
├── Order.java
├── OrderItem.java
└── OrderRepository.java
Now related code is easier to discover।
But Avoid Flat Capability Packages Becoming Crowded
As order/ grows, having everything in one directory may become noisy:
order/
├── OrderController
├── OrderResponse
├── CreateOrderRequest
├── CreateOrder
├── CancelOrder
├── GetOrders
├── Order
├── OrderItem
├── OrderStatus
├── OrderRepository
├── JpaOrderRepository
...
Then responsibility-oriented subpackages may help:
order/
├── api/
├── application/
├── domain/
└── persistence/
We introduce them when needed।
Structure Should Evolve With Complexity
Initial capability:
product/
might have four classes।
No reason to create:
product/api/rest/v1/request/
product/api/rest/v1/response/
product/domain/model/entity/
product/domain/model/value/
product/infrastructure/persistence/jpa/
before complexity requires it।
That kind of structure can make a five-class feature difficult to navigate।
Too Few Folders Can Hurt
The opposite is also possible।
Putting 80 classes in:
order/
with no internal organization becomes difficult।
Therefore structure should evolve gradually։
A useful rule:
Create structure to reduce real cognitive complexity, not to predict imaginary future complexity.
What Belongs in domain/?
When we introduce:
order/domain/
it should contain Order business concepts।
Examples:
Order
OrderItem
OrderStatus
Potential value types later।
It should not contain:
OrderController
JpaOrderRepository
PaymentProviderClient
because those are not domain concepts।
What Belongs in application/?
Application-level use cases and workflow coordination।
For example:
CreateOrder
CancelOrder
GetCustomerOrders
These may depend on repositories and domain objects।
They should not parse HTTP JSON or provider-specific responses।
What Belongs in api/?
HTTP-facing components։
For example:
OrderController
CreateOrderRequest
OrderResponse
Responsibilities:
REST contract
transport validation
request/response mapping
Not:
database queries
payment-provider HTTP calls
domain state manipulation directly
What Belongs in persistence/?
Database-specific concerns।
For example:
Spring Data repositories
JPA mappings
custom database queries
persistence adapters
exact structure depending on our implementation choice।
This is where PostgreSQL/JPA details belong।
What Belongs in integration/?
External service protocol code।
For Payment:
payment/
└── integration/
├── PaymentGateway
└── ProviderPaymentClient
Potentially request/response representations specific to the provider।
These should not leak into order/domain/।
What Belongs in security/?
Application-wide authentication/security integration।
Examples later:
SecurityConfiguration
AuthenticatedUser
CurrentUserProvider
But customer-specific business ownership rules remain inside relevant application workflows।
Security package should not become:
all authorization logic
for the entire system।
Avoid a utils/ Dumping Ground
A common package:
utils/
often becomes a warning sign।
It may eventually contain:
OrderUtils
PriceUtils
InventoryUtils
DateUtils
ValidationUtils
The problem:
Utilities often hide responsibilities that actually belong to a domain or application component.
For example:
OrderUtils.calculateTotal(...)
probably belongs to:
Order
not generic utilities।
Not Every Utility Is Bad
Something truly technical and stateless may reasonably exist as a utility।
But ask first:
Which capability owns this behaviour?
If answer is clear, put it there।
Only genuinely generic technical functionality should become shared utility code।
Avoid common/ for Domain Concepts
Suppose Product and Order both use money values।
Someone may put:
common/Money.java
This might eventually make sense if Money truly has shared domain semantics।
But don't move a concept into common merely because two classes reference it।
Shared abstractions should have coherent meaning।
Package Visibility Can Help Boundaries
Java supports package-level visibility when no access modifier is specified।
Example:
class OrderCalculator {
}
may be package-private।
This can help keep implementation details internal।
Not every class needs to be:
public
Reducing unnecessary public surface makes architecture easier to change।
Public Classes Create Coupling
If every internal component is public, other packages can depend on it easily։
Then later refactoring becomes harder।
A useful mindset:
Make something public because another package genuinely needs it, not by default.
We will apply this selectively as code emerges।
Spring Components and Package Placement
Later Spring-managed classes may use annotations such as:
@RestController
@Service
@Repository
@Component
If they live below our root package:
io.liveklass.ordermanagement
Spring's component scanning can discover them।
That means project structure and runtime behaviour are connected।
A Component Outside the Scan Path
Suppose someone creates:
io.liveklass.payment.ProviderPaymentClient
outside:
io.liveklass.ordermanagement
Spring Boot may not discover it automatically।
This can produce runtime errors such as missing beans।
Better:
io.liveklass.ordermanagement.payment.integration
keeps component under application root and architecture boundary।
Don't Fix Bad Placement With Broad Scanning
You could write:
@ComponentScan("io.liveklass")
to scan everything।
But broad scanning can accidentally pull unrelated Spring components into the application।
Prefer deliberate package organization।
Configuration Classes
Later we may add configuration classes such as:
PaymentClientConfiguration
SecurityConfiguration
Where should they live?
Near the capability they configure when possible।
Example:
payment/integration/PaymentClientConfiguration
or:
security/SecurityConfiguration
rather than one huge:
config/
package containing every unrelated configuration class।
Is a config/ Package Always Bad?
No।
For truly application-wide configuration it may be useful।
But capability-specific configuration should usually remain close to its capability।
Otherwise config/ becomes another technical-layer dumping ground।
Resources Should Follow Ownership Too
Later Flyway migrations will live conventionally under:
src/main/resources/db/migration/
because Flyway expects database migration resources there।
This is one case where framework/tool convention appropriately shapes structure।
Not every architecture concern must be mirrored by capability directories under resources।
Source Structure vs Runtime Structure
Java package structure describes source organization।
It does not imply separate runtime processes।
For example:
product/
inventory/
order/
payment/
all compile into one application artifact।
At runtime:
one JVM
one Spring Application Context
one deployment
still holds।
This is our modular monolith।
Package Is Not a Service
This deserves emphasis:
order/
does not mean:
Order microservice
and:
inventory/
does not mean:
Inventory microservice
Packages are source-code boundaries inside one application।
Package Is Also Not Automatically an Aggregate
Domain aggregate and package are different concepts।
For example:
order/domain/
can contain:
Order
OrderItem
OrderStatus
where Order + Order Items form an aggregate।
The package itself may contain other domain concepts too।
Don't confuse:
package boundary
with:
domain consistency boundary
Build Structure Should Tell a Story
A new engineer browsing:
src/main/java/io/liveklass/ordermanagement/
should eventually see:
product
inventory
order
payment
security
and recognize the main responsibilities of the application।
This is better than first seeing:
impl
manager
helper
common
util
misc
which say almost nothing about the business system।
Naming Matters
A package/class name should communicate purpose।
Good:
order
inventory
payment
CreateOrder
OrderRepository
PaymentGateway
Weak:
processor
handler
manager
common
helper
data
Generic names are sometimes necessary, but too many are a sign that responsibilities are unclear।
One Class Per File
Standard Java convention:
Order.java
contains the primary:
class Order
This improves discoverability and tooling।
Nested classes may have legitimate uses, but don't pack unrelated classes into a single file to reduce file count।
Should Interfaces and Implementations Be Separated by Package?
Some codebases use:
service/
service/impl/
with:
OrderService
OrderServiceImpl
This often adds little value if there is one implementation।
We are not adopting that pattern automatically।
If an interface represents a real boundary, package it according to responsibility।
Example:
payment/integration/PaymentGateway
and provider implementation nearby।
No generic /impl convention required।
Don't Mirror Framework Terminology Everywhere
Spring uses concepts such as:
Controller
Service
Repository
Component
But our packages should primarily express the application architecture։
We don't need:
controllers/
services/
repositories/
components/
at root just because Spring has those stereotypes।
Framework is supporting our system; it is not our domain model।
Test Structure Should Follow Production Structure
If main source becomes:
order/domain/Order.java
test source should commonly become:
order/domain/OrderTest.java
Application workflow:
order/application/CreateOrder.java
test:
order/application/CreateOrderTest.java
This creates predictable navigation।
Integration Tests May Need Different Naming
Later we may distinguish tests by intent:
OrderTest
for unit/domain tests։
OrderRepositoryIntegrationTest
for PostgreSQL integration tests։
OrderApiIntegrationTest
for HTTP-level integration tests।
Naming should communicate the test boundary।
Don't Create a Separate integration-test Source Set Yet
Gradle supports custom source sets such as:
src/integrationTest/java
That can be useful in mature projects।
But our current test suite is tiny।
We should not introduce complex Gradle test source-set configuration before needed।
Later test volume may justify it।
Docs Structure
Our design phase established:
docs/
├── rfcs/
├── adr/
└── incidents/
Unlike application source, these are engineering artifacts।
Once added:
docs/rfcs/001-order-management-architecture.md
docs/adr/001-use-modular-monolith.md
etc.
Documentation belongs in repository because it explains the system alongside code।
Why Not Put RFC in Confluence Only?
Companies may use external documentation platforms։
But repository-local documents have advantages:
versioned with code
easy to review in PRs
discoverable by engineers
preserve architecture history
A real company might use both।
For our project, repository docs give a coherent engineering history।
The Repository Will Grow Gradually
Current:
order-management/
├── build.gradle
├── settings.gradle
├── gradle/
└── src/
Later:
order-management/
├── build.gradle
├── settings.gradle
├── gradle/
├── src/
├── docs/
├── Dockerfile
├── compose.yaml
└── README.md
But each addition should correspond to a real project need।
Don't Create Docker Structure Yet
Production Docker setup is not part of current ticket।
Likewise:
docker/
compose.yaml
should arrive when local infrastructure/container work requires them।
The fact that final repository architecture includes them does not mean they need to exist in the first commit।
Don't Create db/migration Before Flyway Ticket
Same principle।
BACKEND-102 introduces PostgreSQL + Flyway।
That's when:
src/main/resources/db/migration/
becomes meaningful।
An empty folder contributes nothing now।
How Spring Boot Finds application.yml
Spring Boot automatically looks for conventional configuration resources such as:
application.yml
on the application classpath।
Because:
src/main/resources
is included in the packaged classpath, Spring can load the configuration during startup।
This is convention over configuration in practice।
Convention Over Configuration
Spring Boot provides defaults and standard locations so we don't manually wire every infrastructure detail।
Examples:
application.yml location
component scanning
test integration
resource handling
Using conventions reduces configuration noise।
But convention does not mean:
don't understand what Spring is doing.
Good engineers know which convention they are relying on।
Application Class Should Stay Small
Our:
OrderManagementApplication
should mostly remain bootstrap code।
Bad:
@SpringBootApplication
public class OrderManagementApplication {
public static void main(...) {
...
}
public void createOrder() {
...
}
public void adjustInventory() {
...
}
}
The application class should not become a business service।
Its responsibility is application startup/configuration root।
Where Does Business Logic Go?
Not in:
OrderManagementApplication
Not in:
application.yml
Not in:
build.gradle
Business behaviour belongs inside capability code:
order/domain
order/application
inventory/domain
depending on responsibility।
Clear structural boundaries reinforce architectural boundaries।
How Do We Decide Where a New Class Goes?
Ask:
Which capability owns this?
Then:
What responsibility does this class have?
Example:
OrderController
Capability:
Order
Responsibility:
HTTP API
Therefore:
order/api/OrderController
Another Example
CreateOrder
Capability:
Order
Responsibility:
application workflow
Therefore:
order/application/CreateOrder
Another Example
Inventory
Capability:
Inventory
Responsibility:
domain state/behaviour
Therefore:
inventory/domain/Inventory
Another Example
ProviderPaymentClient
Capability:
Payment
Responsibility:
external integration
Therefore:
payment/integration/ProviderPaymentClient
This decision method scales well।
What If You Don't Know Where a Class Belongs?
That is often useful feedback।
Suppose class:
OrderHelper
could fit nowhere clearly।
Ask what it actually does।
If it:
calculates Order total
that behaviour likely belongs in Order domain।
If it:
maps Order to HTTP response
it belongs near API।
If it:
loads Orders from PostgreSQL
it belongs near persistence।
Unclear placement may expose unclear responsibility।
Structure Can Reveal Bad Design
Imagine:
shared/BusinessLogicHelper.java
used by Product, Inventory, and Order।
That may indicate several capabilities are accidentally coupled through generic code।
Folder organization is not just cleanliness।
It can make architectural smells visible।
Circular Dependencies
Capability-oriented structure should avoid circular dependency patterns such as:
order
↓
inventory
↓
order
We previously decided application workflows should coordinate cross-capability behaviour rather than domains calling each other arbitrarily।
Project structure helps us notice such cycles।
Example: Order Cancellation
Bad:
order/domain/Order
imports
inventory/application/InventoryService
Now domain is coupled to another application capability।
Better:
order/application/CancelOrder
coordinates:
Order
Inventory
while domain objects remain focused।
Package Boundaries Are Guidelines, Not Magic
Java packages cannot automatically enforce every architecture rule।
An engineer can still import the wrong class।
So structure must be supported by:
team conventions
code review
tests
design documentation
Architecture is a social + technical system।
Not just folders।
Avoid Deep Package Hierarchies
Too deep:
io.liveklass.ordermanagement.order.application.usecase.command.handler.impl
This is difficult to navigate and adds very little semantic value।
Prefer only levels that communicate meaningful boundaries।
For example:
io.liveklass.ordermanagement.order.application
is usually enough।
Shorter Is Not Always Better Either
Flat:
io.liveklass
with every class directly beneath it loses capability structure।
Balance matters।
Package Structure Should Reflect Stable Concepts
Technical details change more often than domain capabilities।
For example:
Order
Product
Inventory
are likely stable concepts।
But:
Jpa
Rest
WebClient
are implementation choices।
Capability-first organization therefore tends to remain understandable as infrastructure changes।
Should Version Appear in Java Package?
Some teams use:
api/v1/
for API versioning।
We have not yet chosen exact API versioning implementation।
So don't create:
order/api/v1/
before that decision is made।
API versioning comes in Module 5।
Should We Put internal in Packages?
Java does not have a first-class internal visibility concept like some languages।
Some teams use:
internal/
as convention।
We don't currently need it।
Package-private visibility and clear capability structure are sufficient for now।
Structure of a Healthy Small Backend
At early stages, healthy source tree should be boring।
For example, after Product implementation begins:
io/liveklass/ordermanagement/
├── OrderManagementApplication.java
│
└── product/
├── api/
├── application/
├── domain/
└── persistence/
Then Inventory arrives:
├── product/
├── inventory/
Then Order:
├── product/
├── inventory/
├── order/
The architecture emerges alongside real code।
Don't Scaffold the Entire Course
We know Payment comes later।
That does not justify generating:
payment/
payment/application/
payment/domain/
payment/integration/
payment/persistence/
today।
One principle from our design review was specifically:
Don't create a Payment domain package before a real Payment domain model exists.
The same discipline applies to code structure।
Spring Boot's Default Project Layout Is a Starting Point
Spring Boot doesn't require our exact capability packages।
It primarily expects a normal Java project and provides conventions।
Architecture remains our responsibility।
Framework can tell us:
where resources usually go
but it cannot decide:
whether Order owns Inventory
or:
where cancellation business logic belongs
Those are engineering decisions։
Framework Structure vs Domain Structure
Spring Boot gives:
main class
configuration
component scanning
resources
test support
Our architecture gives:
Product
Inventory
Order
Payment
responsibility boundaries
Both coexist।
Do not confuse framework layout with application design।
Application Structure and Future Deployment
Because everything is in one Gradle project and Spring Boot application:
all capability code
↓
one build artifact
↓
one container
↓
one deployed application
This matches ADR-001।
Our source structure supports modularity without changing deployment model։
Review the Current Repository
For BACKEND-101 at this point, repository should remain small।
order-management/
├── .gitignore
├── README.md
├── build.gradle
├── settings.gradle
├── gradlew
├── gradlew.bat
├── gradle/
│ └── wrapper/
│
└── src/
├── main/
│ ├── java/
│ │ └── io/liveklass/ordermanagement/
│ │ └── OrderManagementApplication.java
│ │
│ └── resources/
│ └── application.yml
│
└── test/
└── java/
└── io/liveklass/ordermanagement/
└── OrderManagementApplicationTests.java
Nothing here is unnecessary।
Nothing here pretends future features already exist।
Project Structure Review Checklist
When reviewing project structure, ask:
Is the main application class at the root package?
Are application components under that package?
Are business capabilities easy to identify?
Are generated files excluded from Git?
Is configuration separate from code?
Are tests placed predictably?
Are empty speculative packages being created?
Is `common` or `utils` becoming a dumping ground?
Are provider/database/framework details leaking into domain packages?
Is package nesting understandable?
These questions are more useful than enforcing a directory template blindly।
A Common Mistake: Organizing by File Type Forever
Technical-layer packages often begin well:
controller/
service/
repository/
But as application grows, this can hide business boundaries।
Our choice of capability-oriented structure is intentional and tied to the architecture we already reviewed।
This means folder organization is not random style preference।
It follows system responsibilities।
A Common Mistake: Overusing Service
Developers may place everything in:
service/
Then classes become:
OrderService
PaymentService
ProductService
InventoryService
NotificationService
ValidationService
The word Service tells little about whether the class:
coordinates a use case
implements domain behaviour
talks to an external API
loads data
Our structure should make responsibility more explicit।
A Common Mistake: DTO Everywhere
Developers sometimes create:
dto/
at root containing:
ProductDto
OrderDto
InventoryDto
PaymentDto
This again separates code by technical category।
Better:
order/api/OrderResponse
near the API that uses it।
Transport models belong close to their transport boundary।
A Common Mistake: Entity Package Means JPA
Some codebases use:
entity/
to mean JPA entities।
But our architecture uses the term "domain entity" differently।
To avoid confusion, package names such as:
order/domain/
are clearer for domain concepts।
Persistence representation, if separate later, can live under:
order/persistence/
A Common Mistake: Creating One Global Repository Package
Global:
repository/
makes database access easy to discover technically but weakens ownership։
For example:
InventoryRepository
should clearly belong to Inventory capability।
Capability ownership matters especially when cross-capability workflows begin।
A Common Mistake: Hiding Everything Behind Manager
Class:
OrderManager
could mean almost anything।
Does it:
create orders?
query orders?
manage state?
call payment provider?
perform admin tasks?
Prefer names based on meaningful responsibility।
Structure and Code Review
Suppose PR adds:
order/domain/PaymentProviderClient.java
Reviewer can immediately ask:
Why is external provider infrastructure inside Order domain?
A clear directory structure makes responsibility violations visually obvious।
That is valuable।
Structure and Onboarding
Imagine a new engineer joins the team six months later।
They need to change Order cancellation।
With clear structure:
order/
is the obvious starting point।
Within it:
application/CancelOrder
and:
domain/Order
provide a clear path।
Good architecture reduces time spent searching।
Structure and Refactoring
If Payment Provider implementation changes later:
payment/integration/
gives a predictable boundary।
If API changes:
order/api/
is the likely location।
If business lifecycle changes:
order/domain/
is where we expect core behaviour।
This is architecture helping maintenance।
Structure Is Not Immutable
Suppose Product capability remains tiny।
We may keep:
product/
flat for a while।
Later it grows。
Then refactor to:
product/
├── api/
├── application/
├── domain/
└── persistence/
Refactoring package organization as complexity changes is normal।
Don't treat initial directories as sacred architecture contracts।
But Avoid Constant Restructuring
The opposite problem:
reorganize packages every week
creates noise and merge conflicts।
Refactor structure when:
current layout creates real navigation or responsibility problems
not because a new blog post recommends another architecture style।
What We Should Remember About Spring Boot Structure
Spring Boot gives us several useful conventions:
main application class
component scanning from root package
src/main/resources
application.yml
src/test/java
Gradle integration
Our job is to build a business architecture cleanly inside those conventions।
BACKEND-101 Progress
After this lesson, our bootstrap ticket is stronger because we understand the project rather than merely generating it।
We now know:
why the main class lives at the root package
why package placement affects component scanning
why build and runtime config are separate
why tests mirror source structure
why capability-oriented packages will be introduced gradually
why generated artifacts don't belong in Git
Engineering Principle
The core principle:
Project structure should make system responsibilities easier to discover, not merely sort files by extension or framework stereotype.
Another:
Use Spring Boot conventions where they help, but let business capabilities shape the application architecture.
And:
Create packages and abstractions when real responsibilities exist—not as placeholders for future code.
Summary
In this lesson, we learned that:
- Repository structure is part of maintainability and architecture.
settings.gradledefines the Gradle project identity.build.gradledefines build-time concerns such as plugins, dependencies, Java version, and tests.- Runtime configuration belongs separately in resources such as
application.yml. - Gradle Wrapper is committed because it supports reproducible builds.
src/main/javacontains production Java source.src/main/resourcescontains runtime resources and configuration.src/test/javacontains automated test code.- Generated
build/and local.gradle/state do not belong in source control. - The main
@SpringBootApplicationclass should remain at the root application package. - Spring Boot's default component scanning naturally discovers components in subpackages beneath the main class.
- Good package placement is preferable to unnecessary custom component scanning.
- Our backend will organize code primarily around Product, Inventory, Order, Payment, and Security capabilities.
- Capability packages do not imply Gradle subprojects or microservices.
- Responsibilities such as API, application, domain, persistence, and integration may become subpackages when real complexity justifies them.
- We should avoid speculative empty package trees.
common,utils,service, andmanagerpackages can become dumping grounds if responsibilities are unclear.- Domain code should not contain HTTP, database, or provider-specific infrastructure concerns.
- Tests should usually mirror the concepts they protect.
- Package organization should evolve gradually with the system rather than being over-designed upfront.
- Structure should help a new engineer quickly understand where a change belongs.
Next lesson:
Dependency Injection
There we will introduce the first major Spring concept behind real backend composition: how components receive the dependencies they need without constructing infrastructure themselves, why direct object creation can create tight coupling, how constructor injection works, and how Dependency Injection supports the responsibility boundaries we already designed.