A production-ready Spring Boot microservice designed to manage the lifecycle of orders in a distributed microservices architecture.
The service exposes RESTful APIs for creating, updating, retrieving, and deleting orders, and is fully containerized using Docker to ensure consistent behavior across development, testing, and deployment environments.
The application follows industry-standard layered architecture (Controller, Service, Repository) and is designed to be easily extensible for database integration, security, and cloud deployment. CI pipelines are configured using GitHub Actions to automate builds and ensure code quality.
- Architecture Overview
- System Design Considerations
- Key Highlights
- Local Setup Instructions
- Tech Stack
- Project Structure
- CI/CD Workflow
- Swagger API Documentation
- Endpoints
- Future Improvements
- License
graph TD
Client[Client / API Consumer]
subgraph API Layer
Controller[Order Controller]
DTO[DTOs - Request / Response]
end
subgraph Business Layer
Service[Order Service]
end
subgraph Data Layer
Repository[Order Repository]
DB[(PostgreSQL Database)]
end
Client -->|HTTP Request| Controller
Controller -->|Request DTO| DTO
DTO -->|Validated Data| Service
Service -->|JPA Calls| Repository
Repository -->|SQL Queries| DB
Service -->|Response DTO| DTO
DTO -->|HTTP Response| Client
The service follows a standard layered architecture:
-
Controller Layer
Handles incoming HTTP requests and delegates processing to the service layer. -
DTO Layer
Defines request and response objects used to transfer data between the API layer and business layer. -
Service Layer
Contains the core business logic and orchestrates application workflows. -
Repository Layer
Manages persistence and database interaction using Spring Data JPA.
- Stateless Service – allows horizontal scaling in containerized environments.
- DTO Layer – prevents domain model leakage through API contracts.
- Liquibase Migrations – ensures consistent database schema evolution.
- Testcontainers Integration Tests – guarantees realistic database testing.
- CI Pipeline – ensures build reliability and automated validation.
- RESTful APIs built with Spring Boot
- Clean layered architecture (Controller, Service, Repository)
- DTO-based API design for clear separation of concerns
- PostgreSQL persistence with Liquibase database migrations
- Containerized deployment using Docker
- Integration testing using Testcontainers
- CI pipeline implemented with GitHub Actions
- OpenAPI/Swagger documentation for API exploration
- Designed following microservices and cloud-ready best practices
- JWT based authentication and authorization using Spring Security
Ensure the following are installed:
- Java 17
- Maven
- PostgreSQL
- Docker (required for running integration tests using Testcontainers)
Create a PostgreSQL database:
CREATE DATABASE orderdb;No manual table creation is required. The database schema is automatically managed through Liquibase migrations during application startup.
Update your application.properties file:
spring.datasource.url=${DB_URL:jdbc:mysql://localhost:3306/testdb}
spring.datasource.username=${DB_USER:root}
spring.datasource.password=${DB_PASSWORD:password}
jwt.secret=${JWT_SECRET:dev-secret}In production environments, credentials should be managed using environment variables or a secrets management service instead of hardcoding values.
Build and run the application:
mvn clean install mvn spring-boot:runThe application will start at:
http://localhost:8082
Swagger API documentation will be available at:
http://localhost:8082/swagger-ui/index.html
To execute all unit and integration tests:
mvn testIntegration tests use Testcontainers, which automatically:
- Spins up a PostgreSQL Docker container
- Applies Liquibase migrations
- Tears down the container after execution.
- Docker must be running locally for integration tests to execute successfully.
| Category | Technology |
|---|---|
| Language | Java 17 |
| Framework | Spring Boot |
| Build Tool | Maven |
| Database | PostgreSQL |
| Testing | JUnit5, Testcontainers |
| API Documentation | Swagger / OpenAPI |
| Database Migration | Liquibase |
| Containerization | Docker, Docker Compose |
| CI/CD | GitHub Actions |
| Code Quality | SonarQube |
| API Testing | Postman |
backend-order-service/
└── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.prashant.backendorderservice/
│ │ │ ├── auth/
│ │ │ │ ├── config/
│ │ │ │ │ ├── swagger/
│ │ │ │ │ │ └── AuthControllerDocs.java
│ │ │ │ │ ├── AppConfig.java
│ │ │ │ │ ├── OAuth2SuccessHandler.java
│ │ │ │ │ └── WebSecurityConfig.java
│ │ │ │ ├── controller/
│ │ │ │ │ └── AuthController.java
│ │ │ │ ├── dto/
│ │ │ │ │ ├── request/
│ │ │ │ │ │ └── LoginRequest.java
│ │ │ │ │ └── response/
│ │ │ │ │ ├── LoginResponse.java
│ │ │ │ │ └── SignupResponse.java
│ │ │ │ ├── entity/
│ │ │ │ │ ├── type/
│ │ │ │ │ │ └── AuthProvider.java
│ │ │ │ │ └── User.java
│ │ │ │ ├── exception/
│ │ │ │ │ ├── AuthExceptionHandler.java
│ │ │ │ │ ├── CustomAuthEntryPoint.java
│ │ │ │ │ ├── InvalidCredentialsException.java
│ │ │ │ │ └── UserAlreadyExistsException.java
│ │ │ │ ├── filter/
│ │ │ │ │ └── JwtAuthFilter.java
│ │ │ │ ├── repository/
│ │ │ │ │ └── UserRepository.java
│ │ │ │ ├── service/
│ │ │ │ │ ├── AuthService.java
│ │ │ │ │ └── CustomUserDetailsService.java
│ │ │ │ └── util/
│ │ │ │ └── AuthUtil.java
│ │ │ ├── orders/
│ │ │ │ ├── config/
│ │ │ │ │ └── swagger/
│ │ │ │ │ ├── OrderControllerDocs.java
│ │ │ │ │ └── UserOrderControllerDocs.java
│ │ │ │ ├── controller/
│ │ │ │ │ ├── OrderController.java
│ │ │ │ │ └── UserOrderController.java
│ │ │ │ ├── dto/
│ │ │ │ │ ├── request/
│ │ │ │ │ │ ├── CreateOrderRequest.java
│ │ │ │ │ │ └── UpdateOrderStatusRequest.java
│ │ │ │ │ └── response/
│ │ │ │ │ ├── OrderResponse.java
│ │ │ │ │ └── UpdateOrderStatusResponse.java
│ │ │ │ ├── entity/
│ │ │ │ │ ├── Order.java
│ │ │ │ │ └── OrderStatus.java (enum)
│ │ │ │ ├── exception/
│ │ │ │ │ ├── custom/
│ │ │ │ │ │ ├── BusinessException.java
│ │ │ │ │ │ ├── OrderNotFoundException.java
│ │ │ │ │ │ └── OrderStatusInvalidException.java
│ │ │ │ │ └── OrdersExceptionHandler.java
│ │ │ │ ├── repository/
│ │ │ │ │ └── OrderRepository.java
│ │ │ │ ├── serializer/
│ │ │ │ │ └── OrderStatusDeserializer.java
│ │ │ │ └── service/
│ │ │ │ ├── OrderService.java
│ │ │ │ ├── OrderServiceOperations.java
│ │ │ │ ├── UsersOrderService.java
│ │ │ │ └── UsersOrderServiceOperations.java
│ │ │ ├── shared/
│ │ │ │ ├── config/
│ │ │ │ │ ├── exception/
│ │ │ │ │ │ └── ExceptionHandlerOrder.java
│ │ │ │ │ └── swagger/
│ │ │ │ │ ├── GlobalExceptionHandlerDocs.java
│ │ │ │ │ └── OpenApiConfig.java
│ │ │ │ ├── dto/
│ │ │ │ │ └── ErrorResponse.java
│ │ │ │ └── exception/
│ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ └── RequestExceptionHandler.java
│ │ │ └── BackendOrderServiceApplication.java
│ │ └── resources/
│ │ ├── db.changelog/
│ │ │ ├── changes/
│ │ │ │ ├── 001-create-app-user-table.yaml
│ │ │ │ └── 001-create-orders-table.yaml
│ │ │ └── db.changelog-master.yaml
│ │ ├── application.yml
│ │ └── application.properties
│ └── test/
│ └── java/
│ └── com.prashant.backendorderservice/
│ ├── auth/
│ │ └── support/
│ │ ├── AuthenticatedE2ETest.java
│ │ └── SecuredControllerTest.java
│ ├── orders/
│ │ ├── controller/
│ │ │ ├── OrderControllerTest.java
│ │ │ └── UsersOrderControllerTest.java
│ │ ├── integration/
│ │ │ ├── OrderE2ETest.java
│ │ │ └── OrderRepositoryIntegrationTest.java
│ │ ├── repository/
│ │ │ └── OrderRepositoryTest.java
│ │ └── service/
│ │ ├── OrderServiceTest.java
│ │ └── UsersOrderServiceTest.java
│ └── BackendOrderServiceApplicationTests.java
│
├── pom.xml
└── README.md
Continuous Integration is implemented using GitHub Actions. The pipeline automatically:
- Builds the application
- Executes unit and integration tests
- Performs static code analysis
- Ensures code quality before merging changes
Swagger UI: http://localhost:8082/swagger-ui/index.html
Instructions to use JWT Token in swagger
- Create user by using
POST http://localhost:8082/auth/signup
- Go to
POST http://localhost:8082/auth/login
then copy this JWT token
- Now click on Authorize Button in the topleft corner
A popup will open. Paste JWT token here.
- Then click on Authorize
- Then this popup will open click on close
-
Now all endpoints can be used.
-
You can also logout. Click on Authorize and a popup opens. Then click here.
All error responses follow a consistent structure for example:
{
"timestamp": "2026-03-09T10:15:30",
"status": 400,
"error": "INVALID_ENUM_VALUE",
"message": "Valid Order Status : CREATED, PROCESSING, SHIPPED, COMPLETED, CANCELLED",
"path": "/orders/15/status"
}| Method | Endpoint | Status Code | Description |
|---|---|---|---|
| POST | /auth/signup | 201 | Create a new user |
| POST | /auth/login | 200 | Login an existing user |
POST http://localhost:8082/auth/signup
Request Body :
{
"username":"praj12345",
"password":"best_password_ever123"
} | Status Code | Reason |
|---|---|
201 |
User created successfully |
400 |
Empty Username/Password field |
409 |
Use Different Username |
500 |
INTERNAL SERVER ERROR |
Response Body :
{
"id": 25,
"username": "praj12345"
} Example Reference:
POST http://localhost:8082/auth/login
Request Body :
{
"username": "praj12345",
"password": "best_password_ever123"
} | Status Code | Reason |
|---|---|
200 |
User login successfully |
400 |
Empty Username/Password field |
401 |
Invalid username or password |
500 |
INTERNAL SERVER ERROR |
Response Body :
{
"token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJwcmFqMTIzNDUiLCJ1c2VySWQiOiIyNSIsImlhdCI6MTc3MzY4MDIzNiwiZXhwIjoxNzczNjgwODM2fQ.8AW4aWZA1TcawmlieHuPdW8qgnjMcTuHJHMCpAvTgFN9SVwGWIo4UdHvJ6H7npkuXjueioksKpAVhHSsJsRulg",
"userId": 25
} Example Reference:
> Note: All the protected endpoints needs access. To get the access authentication with JWT token is requiredSteps to get the access
i) Login and copy the jwt token from response body
ii) Go to Header add JWT token in the given format below
Authorization : Bearer <token>
Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJwcmFqMTIzNDUiLCJ1c2VySWQiOiIyIiwiaWF0IjoxNzc0MTkzNzE3LCJleHAiOjE3NzQxOTQzMTd9.oi1EOu-L7AlMg7xC2pytzAwWai7K_As4BA-XD7QAG74uDKs5Q0oWcqWjEZoC7ZbqnGdsK6kucJyl8XiG66cacw
| Method | Endpoint | Status Code | Description |
|---|---|---|---|
| POST | /user/orders | 201 | Create a new order |
| PATCH | /user/orders/{id}/status | 200 | Update order status |
| GET | /user/orders | 200 | Retrieve all the orders |
| GET | /user/orders/{id} | 200 | Retrieve an order by ID |
| DELETE | /user/orders/{id} | 204 | Delete an order |
| PATCH | /admin/orders/{id}/status | 200 | Update order status |
| GET | /admin/orders | 200 | Retrieve all the orders |
| GET | /admin/orders/{id} | 200 | Retrieve an order by ID |
| DELETE | /admin/orders/{id} | 204 | Delete an order |
POST http://localhost:8082/user/orders
Request Body :
{
"customerId": 14,
"description": "Medicine and Hospital Equipment",
"status": "CREATED"
} | Status Code | Reason |
|---|---|
201 |
Order created successfully |
400 |
Invalid request body / missing required fields |
Response Body :
{
"id": 15,
"customerId": 14,
"description": "Medicine and Hospital Equipment",
"status": "CREATED"
}
statusis optional. Defaults toCREATEDif not provided.
Example Reference:
PATCH http://localhost:8082/admin/orders/{id}/status
Allowed Status Values:
CREATED,
PROCESSING,
SHIPPED,
COMPLETED,
CANCELLED
Request Body :
{
"status": "SHIPPED"
} | Status Code | Reason |
|---|---|
200 |
Status updated successfully |
400 |
Invalid status value |
404 |
Order not found with given ID |
Response Body :
{
"id": 15,
"status": "SHIPPED",
"updatedAt": "2026-02-06T17:28:06.2548106"
} Example Reference:
GET http://localhost:8082/admin/orders
| Status Code | Reason |
|---|---|
200 |
Order retrieved successfully |
Response Body :
[
{
"id": 15,
"customerId": 14,
"description": "Medicine and Hospital Equipment",
"status": "CREATED"
}
] Example Reference:
GET http://localhost:8082/admin/orders/{id}
| Status Code | Reason |
|---|---|
200 |
Order retrieved successfully |
404 |
Order not found with given ID |
Response Body :
{
"id": 15,
"customerId": 14,
"description": "Medicine and Hospital Equipment",
"status": "CREATED"
} Example Reference:
DELETE http://localhost:8082/admin/orders/{id}
Request Body :
| Status Code | Reason |
|---|---|
204 |
Order deleted successfully |
404 |
Order not found with given ID |
Response Body :
Response Bodyis empty if no error.
Example Reference:
- Introduce distributed tracing using OpenTelemetry
- Implement event-driven communication using Kafka
- Deploy using Kubernetes for scalable container orchestration
- Add caching using Redis for improved performance
MIT © Prashant Raj







