A comprehensive example demonstrating Hexagonal Architecture (also known as Ports and Adapters) implementation using Spring Boot and MongoDB. This project showcases clean architecture principles, domain-driven design, and separation of concerns.
- Overview
- Architecture
- Project Structure
- Technologies
- Getting Started
- API Endpoints
- Running Tests
- Design Patterns
- Contributing
This project implements an Order Management System following hexagonal architecture principles. The architecture ensures:
- ✅ Domain Independence: Business logic is isolated from external concerns
- ✅ Testability: Easy to test components in isolation
- ✅ Flexibility: Easy to swap adapters (e.g., switch from MongoDB to PostgreSQL)
- ✅ Maintainability: Clear separation of concerns and responsibilities
┌─────────────────────────────────────────────────────────────┐
│ External World (Adapters) │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ REST API │ │ MongoDB Adapter │ │
│ │ (Controller) │ │ (Persistence) │ │
│ └────────┬────────┘ └──────────┬───────────┘ │
│ │ │ │
│ │ Input Port Output Port│ │
├───────────▼───────────────────────────────────▼─────────────┤
│ Application Layer │
│ (Use Cases / Business Logic) │
│ ShipOrderService │
├────────────────────────��─────────────────────────────────────┤
│ Domain Layer │
│ (Entities, Value Objects, Domain Logic) │
│ Order | OrderId | Money | OrderStatus │
└──────────────────────────────────────────────────────────────┘
- Entities:
Order- Core business entity with state and behavior - Value Objects:
OrderId,Money,OrderStatus- Immutable objects representing domain concepts - Business Rules: Enforced within entities (e.g., only paid orders can be shipped)
- Input Ports:
ShipOrderUseCase- Define application entry points - Output Ports:
LoadOrder,SaveOrder- Define external dependencies
- Input Adapters:
OrderController- REST API endpoints - Output Adapters:
MongoLoadOrderAdapter,MongoSaveOrderAdapter- MongoDB persistence
src/
├── main/
│ ├── java/
│ │ └── it/matteoroxis/hexagonal_architecture/
│ │ ├── domain/ # Domain Layer (Core)
│ │ │ ├── Order.java
│ │ │ ├── OrderId.java
│ │ │ ├── Money.java
│ │ │ └── OrderStatus.java
│ │ │
│ │ ├── ports/ # Ports (Interfaces)
│ │ │ ├── in/
│ │ │ │ └── ShipOrderUseCase.java
│ │ │ └── out/
│ │ │ ├── LoadOrder.java
│ │ │ └── SaveOrder.java
│ │ │
│ │ ├── adapters/ # Adapters
│ │ │ ├── controller/ # Input Adapter (REST)
│ │ │ │ ├── OrderController.java
│ │ │ │ └── GlobalExceptionHandler.java
│ │ │ │
│ │ │ ├── service/ # Application Services
│ │ │ │ └── ShipOrderService.java
│ │ │ │
│ │ │ ├── repository/ # Output Adapter (Persistence)
│ │ │ │ └── MongoOrderRepository.java
│ │ │ │
│ │ │ ├── documents/ # MongoDB Documents
│ │ │ │ └── OrderDocument.java
│ │ │ │
│ │ │ ├── mapper/ # Domain ↔ Document Mapping
│ │ │ │ └── OrderMapper.java
│ │ │ │
│ │ │ ├── MongoLoadOrderAdapter.java
│ │ │ └── MongoSaveOrderAdapter.java
│ │ │
│ │ ├── config/ # Configuration
│ │ │ └── OrderAdapterConfiguration.java
│ │ │
│ │ └── exception/ # Custom Exceptions
│ │ └── OrderNotFoundException.java
│ │
│ └── resources/
│ └── application.properties
│
└── test/
��── java/
└── it/matteoroxis/hexagonal_architecture/
├── domain/ # Domain Tests
│ ├── OrderTest.java
│ ├── MoneyTest.java
│ └── OrderIdTest.java
│
├── adapters/
│ ├── controller/
│ │ └── OrderControllerTest.java
│ ├── service/
│ │ └── ShipOrderServiceTest.java
│ ├── mapper/
│ │ └── OrderMapperTest.java
│ ├── MongoLoadOrderAdapterTest.java
│ └── MongoSaveOrderAdapterTest.java
│
└── integration/
└── OrderIntegrationTest.java
- Java 25 - Programming language
- Spring Boot 3.5.10 - Application framework
- Spring Data MongoDB - MongoDB integration
- MongoDB 6.0+ - NoSQL database
- JUnit 5 - Testing framework
- Mockito - Mocking framework
- Testcontainers - Integration testing with containers
- Maven - Build tool
- Java 25 or higher
- Maven 3.8+
- MongoDB 6.0+ (or Docker)
-
Clone the repository
git clone https://github.com/matteoroxis/exagonal-architecture.git cd exagonal-architecture -
Start MongoDB (using Docker)
docker run -d -p 27017:27017 --name mongodb mongo:6.0
-
Configure MongoDB (Optional)
Edit
src/main/resources/application.properties:spring.data.mongodb.uri=mongodb://localhost:27017/hexagonal-db spring.data.mongodb.database=hexagonal-db
-
Build the project
./mvnw clean install
-
Run the application
./mvnw spring-boot:run
The application will start on http://localhost:8080
Endpoint: POST /api/orders/{orderId}/ship
Description: Ships a paid order. Only orders with status PAID can be shipped.
Example Request:
curl -X POST http://localhost:8080/api/orders/123/shipSuccess Response:
- Code:
200 OK
Error Responses:
- Code:
500 Internal Server Error- Order not found
- Order is not in PAID status
Example Error Response:
{
"status": 500,
"message": "Only paid orders can be shipped"
}./mvnw test# Domain tests
./mvnw test -Dtest=OrderTest
# Service tests
./mvnw test -Dtest=ShipOrderServiceTest
# Controller tests
./mvnw test -Dtest=OrderControllerTest
# Integration tests
./mvnw test -Dtest=OrderIntegrationTestThe project includes comprehensive tests at multiple levels:
- Unit Tests: Domain entities, value objects, and services
- Integration Tests: Adapters and repositories
- End-to-End Tests: Full application flow with Testcontainers
Separates business logic from external concerns through well-defined interfaces.
Domain layer defines interfaces (ports); adapters implement them.
Abstracts data persistence logic through LoadOrder and SaveOrder ports.
Converts between domain entities and persistence models (OrderMapper).
Immutable objects like OrderId and Money ensure domain integrity.
Application services (ShipOrderService) orchestrate domain operations.
┌──────────┐ markAsPaid() ┌──────────┐
│ PENDING │ ───────────────────► │ PAID │
└──────────┘ └────┬─────┘
│
│ markAsShipped()
▼
┌──────────┐
│ SHIPPED │
└────┬─────┘
│
│ markAsDelivered()
▼
┌──────────┐
│DELIVERED │
└──────────���
- Business logic is isolated in the domain layer
- No framework dependencies in core domain
- Easy to understand and maintain
- Domain can be tested without any infrastructure
- Adapters can be easily mocked
- Fast unit tests execution
- Easy to change persistence mechanism (MongoDB → PostgreSQL)
- Easy to add new adapters (REST → gRPC)
- No vendor lock-in
- Each layer has a clear purpose
- Components are focused and cohesive
- Easy to navigate codebase
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License.
Matteo Roxis
- Hexagonal Architecture
- Ports and Adapters Pattern
- Domain-Driven Design
- Clean Architecture by Robert C. Martin
⭐ If you find this project helpful, please consider giving it a star!