A Gradle multi‑module scaffold that demonstrates how to organize a Spring Boot project using:
- Hexagonal (Ports & Adapters) architecture
- Domain‑Driven Design (DDD)
- API‑First development with OpenAPI Generator
This repository currently focuses on structure and documentation to serve as a template for real implementations.
- common
- Holds OpenAPI schemas and is configured to generate DTOs and interfaces using OpenAPI Generator (WebFlux/Spring Boot 3).
- Intentionally domain‑neutral and infrastructure‑agnostic.
- intake
- A feature/bounded‑context module organized per Hexagonal + DDD.
- Contains application, domain, and infrastructure packages (no business implementation yet).
A high‑level view of the repository layout:
hexagonal-ddd-api-first-spring-boot-sample/
├─ settings.gradle
├─ build.gradle
├─ gradlew, gradlew.bat
├─ gradle/
│ └─ wrapper/
├─ LICENSE
├─ README.md
├─ common/
│ ├─ build.gradle
│ └─ src/
│ └─ main/
│ ├─ java/
│ │ └─ com/spectrayan/common/
│ │ └─ package-info.java
│ └─ resources/
│ └─ openapi/
│ └─ openapi.yaml
└─ intake/
├─ build.gradle
└─ src/
└─ main/
└─ java/
└─ com/spectrayan/intake/
├─ package-info.java
├─ application/
│ ├─ package-info.java
│ ├─ port/
│ │ ├─ in/
│ │ │ └─ package-info.java
│ │ └─ out/
│ │ └─ package-info.java
│ └─ usecase/
│ └─ package-info.java
├─ domain/
│ ├─ package-info.java
│ ├─ model/
│ │ └─ package-info.java
│ ├─ repository/
│ │ └─ package-info.java
│ └─ service/
│ └─ package-info.java
└─ infrastructure/
├─ package-info.java
├─ adapter/
│ ├─ in/
│ │ └─ web/
│ │ └─ package-info.java
│ └─ out/
│ └─ persistence/
│ └─ package-info.java
├─ config/
│ └─ package-info.java
└─ persistence/
├─ entity/
│ └─ package-info.java
├─ mapper/
│ └─ package-info.java
└─ repository/
└─ package-info.java
- application
- port.in: input (driving) ports invoked by inbound adapters (e.g., REST controllers, messaging consumers)
- port.out: output (driven) ports used by application/use cases to reach external systems (e.g., DB, HTTP, messaging)
- usecase: orchestration of business behavior; coordinates domain operations and transactions
- domain
- model: aggregates, entities, value objects, domain events
- service: domain services when logic doesn’t fit naturally in an aggregate
- repository: domain‑level repository abstractions (ubiquitous language, no tech details)
- infrastructure
- adapter.in.web: inbound web adapter (controllers) translating transport → input ports
- adapter.out.persistence: outbound adapter implementing output ports via persistence technologies
- persistence.entity/repository/mapper: technical mapping for DB; not exposed to domain or application
- web.dto/web.mapper: transport DTOs and mappers for the web adapter
- config: infrastructure configuration (WebFlux, beans, properties)
Below is a high-level Hexagonal (Ports & Adapters) view showing the core hexagon, ports, adapters, and layers.
flowchart LR
%% Core hexagon (conceptual)
subgraph Core[Hexagon Core]
direction TB
subgraph Application[Application Layer]
IP[Input Ports - port.in]
UC[Use Cases]
OP[Output Ports - port.out]
IP --> UC
UC --> OP
end
subgraph Domain[Domain Layer]
DM[Aggregates, Entities, Value Objects]
DS[Domain Services]
DR[Domain Repository Abstractions]
UC --> DM
UC --> DS
end
end
%% Inbound/outbound adapters and external world
Client[Client] --> InAdapter[Inbound Adapter - Web or Controller]
InAdapter --> IP
OP --> OutAdapter[Outbound Adapter - Persistence/HTTP/Messaging]
OutAdapter --> Ext[Database and Other Services]
%% Styling to highlight layers and dependency rule
classDef adapter fill:#e8f0ff,stroke:#2f4b8f,stroke-width:1px;
classDef core fill:#e9f9ef,stroke:#2f8f4b,stroke-width:1px;
classDef app fill:#ffffff,stroke:#2f8f4b,stroke-dasharray: 3 3;
classDef domain fill:#ffffff,stroke:#2f8f4b,stroke-dasharray: 3 3;
class InAdapter,OutAdapter adapter;
class Core core;
class Application app;
class Domain domain;
Key: adapters depend inward on ports; the core never depends on adapters.
A request travels from the outside world through inbound adapters into the core (application + domain), and results travel back out the same path. Ports (interfaces) sit at the core boundary; adapters implement or call those ports.
Outside World (Client)
│ HTTP request (JSON over HTTP, etc.)
▼
[Inbound Adapter] REST Controller (WebFlux)
│ invokes
▼
[Application] Input Port (port.in, interface)
│ implemented by
▼
[Application] Use Case (orchestration/transactions)
│ delegates to
▼
[Domain] Aggregates · Entities · Value Objects · Domain Services
│ needs external I/O via
▼
[Application] Output Port (port.out, interface/contract)
│ implemented by
▼
[Outbound Adapter] Persistence/HTTP/Messaging
│ DB/Remote call
▼
External Systems (DB/Other Services)
<–––––––––––––––––––––––––––––––––––– response/result propagates back ––––––––––––––––––––––––––––––––––––>
External Systems → Outbound Adapter → Output Port → Domain → Use Case → Input Port → Inbound Adapter → Client
Narrative
- A client sends a request (e.g., HTTP POST).
- The inbound adapter (REST controller) validates/parses transport DTOs and calls an application input port.
- A use case implementation handles orchestration and transaction boundaries; it invokes domain operations.
- Domain model enforces business rules (aggregates/services). No framework or transport dependencies here.
- For external I/O, the use case calls output ports (defined as interfaces in the application layer).
- Outbound adapters implement those output ports using technical details (DB, HTTP, messaging) and return results.
- The inbound adapter maps domain results to transport DTOs and returns the HTTP response to the client.
Notes
- Dependency rule: domain ← application ← adapters (infrastructure). Adapters depend inward, never the reverse.
- Ports are interfaces owned by the application layer; adapters either call (input) or implement (output) them.
- Keep mapping at the edges: transport ↔ domain in adapters; core remains technology‑agnostic and testable.
- The common module contains openapi/openapi.yaml.
- openapi-generator (configured in common/build.gradle) generates WebFlux/Spring Boot 3 interfaces and DTOs into build/generated sources and wires them into compilation.
- Advantages:
- Schemas drive consistent DTOs across modules.
- Contracts are explicit and reviewable before implementation.
- Enables client code generation if desired.
Basic generation flow
- Edit common/src/main/resources/openapi/openapi.yaml
- Run: ./gradlew :common:openApiGenerate
- Generated sources are added to the main source set of the common module automatically.
- Define or update API contracts (if applicable) in common/openapi.yaml and regenerate DTOs.
- In intake (or another bounded‑context module):
- Add an input port interface under application/port/in (method names reflecting ubiquitous language).
- Implement the use case under application/usecase, depending on domain model and services.
- Define output port(s) under application/port/out for any external I/O needs.
- Implement outbound adapters under infrastructure/adapter/out (e.g., persistence) to satisfy output ports.
- Implement inbound adapters under infrastructure/adapter/in/web (controllers) to expose the input ports over HTTP.
- Map between transport DTOs and domain model using web/persistence mappers as needed.
- Keep business rules in domain; keep technical details in infrastructure.
- Java 21, Spring Boot 3.5 (WebFlux). Uses Gradle Wrapper.
- Initial build may fail if you try to start the intake module since there is no main application class yet; this scaffold is intentionally implementation‑light.
Useful commands
- Windows: gradlew.bat clean build
- macOS/Linux: ./gradlew clean build
- Generate DTOs only: ./gradlew :common:openApiGenerate
This project is licensed under the terms of the LICENSE file included in the repository.