DEV Community

rezahazegh
rezahazegh

Posted on

Hexagonal Architecture Demo - Task Management System

A Spring Boot application demonstrating Hexagonal Architecture (also known as Ports and Adapters) with a Task Management system. This project showcases clean separation of concerns, dependency inversion, and testable code structure.

๐Ÿ“„ Repository

https://github.com/rezahazegh/demo-hexagonal-architecture

๐Ÿ“‹ Table of Contents

๐Ÿ—๏ธ Architecture Overview

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     Infrastructure Layer                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚              Inbound Adapters (Input)                โ”‚   โ”‚
โ”‚  โ”‚         REST API Controllers & DTOs                   โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                     โ”‚                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚              Application Layer                        โ”‚   โ”‚
โ”‚  โ”‚        Use Cases & Business Orchestration             โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                     โ”‚                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚                Domain Layer (Core)                    โ”‚   โ”‚
โ”‚  โ”‚        Business Logic & Domain Models                 โ”‚   โ”‚
โ”‚  โ”‚              No External Dependencies                 โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                     โ”‚                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚             Outbound Adapters (Output)                โ”‚   โ”‚
โ”‚  โ”‚      PostgreSQL Repository & Persistence              โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Enter fullscreen mode Exit fullscreen mode

Layer Interaction Flow

REST Request โ†’ Controller โ†’ Application Service โ†’ Domain Logic โ†’ Repository โ†’ Database

The dependency rule: Dependencies point inward (Infrastructure โ†’ Application โ†’ Domain)

๐ŸŽฏ What is Hexagonal Architecture?

Hexagonal Architecture (coined by Alistair Cockburn) is a software design pattern that creates loosely coupled application components.

Core Concepts

  1. Domain (Core/Hexagon): The business logic lives here, completely isolated from external concerns

    • Pure business rules
    • Domain models
    • Domain exceptions
    • No framework dependencies
  2. Ports: Interfaces that define contracts

    • Input Ports: Define use cases (e.g., TaskService)
    • Output Ports: Define what the domain needs from external systems (e.g., TaskRepository)
  3. Adapters: Implementations that connect to the outside world

    • Inbound/Primary Adapters: Drive the application (e.g., REST controllers, CLI)
    • Outbound/Secondary Adapters: Driven by the application (e.g., database repositories, external APIs)

Why "Hexagonal"?

The hexagon shape symbolizes that the architecture can have multiple adapters on each side - it's not limited to just one input or output method.

๐Ÿ“ Project Structure

dev.hazegh.demo_hexagonal_architecture/
โ”‚
โ”œโ”€โ”€ domain/                                    # ๐Ÿ”ต CORE DOMAIN (No external dependencies)
โ”‚   โ”œโ”€โ”€ model/
โ”‚   โ”‚   โ”œโ”€โ”€ Task.java                         # Domain entity with business logic
โ”‚   โ”‚   โ””โ”€โ”€ TaskStatus.java                   # Enum (TODO, IN_PROGRESS, DONE)
โ”‚   โ”œโ”€โ”€ exception/
โ”‚   โ”‚   โ”œโ”€โ”€ TaskNotFoundException.java
โ”‚   โ”‚   โ””โ”€โ”€ InvalidTaskStateException.java
โ”‚   โ””โ”€โ”€ port/
โ”‚       โ””โ”€โ”€ output/
โ”‚           โ””โ”€โ”€ TaskRepository.java           # Output port interface
โ”‚
โ”œโ”€โ”€ application/                               # ๐ŸŸข APPLICATION LAYER
โ”‚   โ”œโ”€โ”€ port/
โ”‚   โ”‚   โ””โ”€โ”€ input/
โ”‚   โ”‚       โ””โ”€โ”€ TaskService.java              # Input port (use case interface)
โ”‚   โ””โ”€โ”€ service/
โ”‚       โ””โ”€โ”€ TaskServiceImpl.java              # Use case implementation
โ”‚
โ””โ”€โ”€ infrastructure/                            # ๐ŸŸ  INFRASTRUCTURE LAYER
    โ””โ”€โ”€ adapter/
        โ”œโ”€โ”€ input/
        โ”‚   โ””โ”€โ”€ rest/
        โ”‚       โ”œโ”€โ”€ TaskController.java       # REST API adapter
        โ”‚       โ”œโ”€โ”€ dto/
        โ”‚       โ”‚   โ”œโ”€โ”€ CreateTaskRequest.java
        โ”‚       โ”‚   โ”œโ”€โ”€ UpdateTaskRequest.java
        โ”‚       โ”‚   โ”œโ”€โ”€ ChangeStatusRequest.java
        โ”‚       โ”‚   โ””โ”€โ”€ TaskResponse.java
        โ”‚       โ””โ”€โ”€ exception/
        โ”‚           โ”œโ”€โ”€ GlobalExceptionHandler.java
        โ”‚           โ””โ”€โ”€ ErrorResponse.java
        โ””โ”€โ”€ output/
            โ””โ”€โ”€ persistence/
                โ”œโ”€โ”€ entity/
                โ”‚   โ””โ”€โ”€ TaskJpaEntity.java    # JPA entity (infrastructure concern)
                โ”œโ”€โ”€ mapper/
                โ”‚   โ””โ”€โ”€ TaskMapper.java       # Maps between domain and JPA
                โ”œโ”€โ”€ TaskJpaRepository.java    # Spring Data JPA interface
                โ””โ”€โ”€ TaskRepositoryAdapter.java # Implements domain port
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”‘ Key Design Principles

1. Dependency Inversion Principle

  • High-level modules (domain) don't depend on low-level modules (infrastructure)
  • Both depend on abstractions (ports/interfaces)

2. Separation of Concerns

  • Domain: Business rules (e.g., "A completed task cannot go back to TODO")
  • Application: Orchestration (e.g., "Find task, change status, save task")
  • Infrastructure: Technical details (e.g., REST, JPA, PostgreSQL)

3. Testability

  • Domain logic can be tested without any frameworks
  • Application logic can be tested with mocked repositories
  • Each layer can be tested independently

4. Flexibility

  • Easy to swap adapters (e.g., PostgreSQL โ†’ MongoDB, REST โ†’ GraphQL)
  • Domain remains unchanged when infrastructure changes

๐Ÿ› ๏ธ Technologies Used

  • Java 21
  • Spring Boot 4.0.1
    • Spring Web (REST API)
    • Spring Data JPA (Persistence)
    • Spring Validation (Bean validation)
  • PostgreSQL (Production database)
  • H2 (Test database)
  • Lombok (Reduce boilerplate)
  • Maven (Build tool)
  • Docker & Docker Compose (Containerization)
  • JUnit 5 & Mockito (Testing)

๐Ÿš€ Getting Started

Prerequisites

  • Java 21 or higher
  • Docker and Docker Compose
  • Maven 3.9+ (or use the included Maven wrapper)

1. Clone the Repository

git clone https://github.com/rezahazegh/demo-hexagonal-architecture
cd demo-hexagonal-architecture
Enter fullscreen mode Exit fullscreen mode

2. Start PostgreSQL Database

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

This will start PostgreSQL on localhost:5432 with:

  • Database: taskdb
  • Username: taskuser
  • Password: taskpass

3. Run the Application

Using Maven Wrapper (Recommended):

./mvnw spring-boot:run
Enter fullscreen mode Exit fullscreen mode

Or using Maven:

mvn spring-boot:run
Enter fullscreen mode Exit fullscreen mode

The application will start on http://localhost:8080

4. Verify the Application

curl http://localhost:8080/api/tasks
Enter fullscreen mode Exit fullscreen mode

You should receive an empty array [] (no tasks yet).

๐Ÿ“š API Documentation

Base URL

http://localhost:8080/api/tasks
Enter fullscreen mode Exit fullscreen mode

Endpoints

1. Create a Task

POST /api/tasks
Content-Type: application/json

{
  "title": "Learn Hexagonal Architecture",
  "description": "Study the concepts and implement a demo project"
}

Response: 201 Created
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "Learn Hexagonal Architecture",
  "description": "Study the concepts and implement a demo project",
  "status": "TODO",
  "createdAt": "2025-12-26T10:00:00",
  "updatedAt": "2025-12-26T10:00:00"
}
Enter fullscreen mode Exit fullscreen mode

2. Get All Tasks

GET /api/tasks

Response: 200 OK
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "title": "Learn Hexagonal Architecture",
    "description": "Study the concepts and implement a demo project",
    "status": "TODO",
    "createdAt": "2025-12-26T10:00:00",
    "updatedAt": "2025-12-26T10:00:00"
  }
]
Enter fullscreen mode Exit fullscreen mode

3. Get Task by ID

GET /api/tasks/{id}

Response: 200 OK
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "Learn Hexagonal Architecture",
  "description": "Study the concepts and implement a demo project",
  "status": "TODO",
  "createdAt": "2025-12-26T10:00:00",
  "updatedAt": "2025-12-26T10:00:00"
}
Enter fullscreen mode Exit fullscreen mode

4. Update Task

PUT /api/tasks/{id}
Content-Type: application/json

{
  "title": "Master Hexagonal Architecture",
  "description": "Deep dive into advanced concepts"
}

Response: 200 OK
Enter fullscreen mode Exit fullscreen mode

5. Change Task Status

PATCH /api/tasks/{id}/status
Content-Type: application/json

{
  "status": "IN_PROGRESS"
}

Response: 200 OK
Enter fullscreen mode Exit fullscreen mode

Valid status transitions:

  • TODO โ†’ IN_PROGRESS โœ…
  • TODO โ†’ DONE โœ…
  • IN_PROGRESS โ†’ DONE โœ…
  • DONE โ†’ TODO โŒ (Business rule violation)
  • DONE โ†’ IN_PROGRESS โŒ (Business rule violation)

6. Delete Task

DELETE /api/tasks/{id}

Response: 204 No Content
Enter fullscreen mode Exit fullscreen mode

Error Responses

Validation Error

{
  "timestamp": "2025-12-26T10:00:00",
  "status": 400,
  "error": "Validation Failed",
  "message": "Invalid input data",
  "path": "/api/tasks",
  "validationErrors": [
    {
      "field": "title",
      "message": "Title is required"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Not Found

{
  "timestamp": "2025-12-26T10:00:00",
  "status": 404,
  "error": "Not Found",
  "message": "Task not found with id: 550e8400-e29b-41d4-a716-446655440000",
  "path": "/api/tasks/550e8400-e29b-41d4-a716-446655440000"
}
Enter fullscreen mode Exit fullscreen mode

Invalid State Transition

{
  "timestamp": "2025-12-26T10:00:00",
  "status": 400,
  "error": "Bad Request",
  "message": "Failed to change status: Cannot move a completed task back to TODO",
  "path": "/api/tasks/550e8400-e29b-41d4-a716-446655440000/status"
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿงช Testing

Run All Tests

./mvnw test
Enter fullscreen mode Exit fullscreen mode

Test Structure

Unit Tests

  • Domain Tests (TaskTest.java): Test business logic in isolation
  • Application Tests (TaskServiceImplTest.java): Test use cases with mocked dependencies
  • Mapper Tests (TaskMapperTest.java): Test data transformations

Integration Tests

  • REST API Tests (TaskControllerIntegrationTest.java): End-to-end API testing
  • Repository Tests (TaskRepositoryAdapterTest.java): Database integration testing

Test Coverage

The tests demonstrate:

  • โœ… Business rule enforcement (e.g., status transition rules)
  • โœ… Validation handling
  • โœ… Error scenarios (not found, invalid state)
  • โœ… CRUD operations
  • โœ… Mapper bidirectional conversions
  • โœ… Repository persistence

๐Ÿ’ก Benefits of This Architecture

1. Independence from Frameworks

  • The core business logic doesn't depend on Spring, JPA, or any framework
  • Frameworks become implementation details

2. Testability

  • Domain logic can be tested without any infrastructure
  • Easy to write unit tests with minimal setup
  • Clear boundaries make mocking straightforward

3. Flexibility

  • Database change: Swap PostgreSQL for MongoDB without touching domain/application
  • API change: Add GraphQL alongside REST without modifying business logic
  • Add new adapters: CLI, gRPC, message queue - all without changing the core

4. Maintainability

  • Clear separation makes code easier to understand
  • Changes in one layer don't ripple through the entire application
  • Each component has a single responsibility

5. Business Logic Protection

  • Domain rules are explicit and enforced in one place
  • No risk of bypassing business logic through different entry points

6. Team Scalability

  • Different teams can work on different layers independently
  • Clear contracts (ports) reduce integration conflicts

๐Ÿ”„ Example: Swapping Adapters

Current Setup

  • REST API (Inbound)
  • PostgreSQL (Outbound)

Adding a New Adapter (e.g., CLI)

You can add a CLI adapter without changing domain or application:

@Component
public class TaskCliAdapter implements CommandLineRunner {
    private final TaskService taskService;

    @Override
    public void run(String... args) {
        Task task = taskService.createTask("CLI Task", "Created from CLI");
        System.out.println("Created task: " + task.getId());
    }
}
Enter fullscreen mode Exit fullscreen mode

Swapping Database (PostgreSQL โ†’ MongoDB)

Just create a new adapter implementing TaskRepository:

@Repository
public class MongoTaskRepository implements TaskRepository {
    private final MongoTemplate mongoTemplate;
    // Implementation using MongoDB
}
Enter fullscreen mode Exit fullscreen mode

The domain and application layers remain unchanged!

๐Ÿณ Docker Commands

# Start PostgreSQL
docker-compose up -d

# Stop PostgreSQL
docker-compose down

# View logs
docker-compose logs -f postgres

# Clean up (removes volumes)
docker-compose down -v
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ Environment Variables

Copy .env.example to .env and customize if needed:

cp .env.example .env
Enter fullscreen mode Exit fullscreen mode

Default values:

  • POSTGRES_DB=taskdb
  • POSTGRES_USER=taskuser
  • POSTGRES_PASSWORD=taskpass
  • POSTGRES_PORT=5432

๐Ÿ“– Further Reading


Built with โค๏ธ to demonstrate Hexagonal Architecture principles

Top comments (0)