A functional, declarative Java framework for building Hexagonal Architecture apps with serious swagg.
Built for JDK 17+ with a clean API that puts your use cases front and center. Forget the ceremony. Focus on composing behavior.
HexaFun brings Hexagonal Architecture into the functional age with minimal boilerplate and maximum clarity.
- Define use cases with clear port interfaces
- Pure business logic, no frameworks leaking in
- Plug in real adapters (HTTP, DB, Messaging) when you want
- Composable, testable, functional-by-design
Add HexaFun to your Maven project:
<dependency>
<groupId>com.guinetik</groupId>
<artifactId>hexafun-core</artifactId>
<version>1.0.1</version>
</dependency>Or with Gradle:
implementation 'com.guinetik:hexafun-core:1.0.1'Requirements: Java 17+
| Example | Description |
|---|---|
| Counter | Simple increment/add with validation |
| Tasks | Kanban board TUI (TODO → DOING → DONE) |
| Sysmon | System monitor with 4 output adapters (TUI, CLI, JSON, Prometheus) |
Run the interactive launcher:
mvn exec:java -pl hexafun-examplesOr launch a specific example:
mvn exec:java -pl hexafun-examples -Dexec.args="sysmon" ┌────────────────────────┐
│ Driving Adapter │ (REST / CLI, Events)
└────────▲───────────────┘
│
Input Port (UseCase<I, O>)
│
┌─────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ ┌───────────────────┐ │
│ │ Use Cases │ ◄──► │ Domain Model │ │
│ └──────────────┘ └───────────────────┘ │
│ │
└────────────────────▼──────────────▼─────────────────────────┘
│ │
Output Port Output Port
(e.g. DB) (e.g. Email)
│ │
┌───▼────┐ ┌───▼──────┐
│ Adapter│ │ Adapter │
└────────┘ └──────────┘
Hexagonal Architecture (also known as Ports and Adapters) is a software design pattern that emphasizes organizing application logic around a core domain, isolated from external concerns. HexaFun is a Java framework that implements this architectural pattern with a functional programming approach.
- Domain-Centric Design: Business logic is at the center, protected from external dependencies
- Ports: Interfaces that define how the application interacts with the outside world
- Input/Primary Ports: How external actors communicate with the application
- Output/Secondary Ports: How the application communicates with external systems
- Adapters: Implementations of ports that connect the application to specific technologies
- Primary Adapters: Drive the application (REST controllers, CLI interfaces)
- Secondary Adapters: Provide infrastructure services (databases, external APIs)
HexaFun modernizes Hexagonal Architecture with functional programming principles:
-
Functional Core Primitives:
UseCase<I, O>: Core business logic operations with clean input/output contractsUseCaseKey<I, O>: Type-safe keys for compile-time dispatch safetyValidationPort<I>: Input validation with clear error boundaries- Port Registry: Type-safe dependency injection for external dependencies
-
Functional Error Handling:
- Uses the
Result<T>monad to handle errors without exceptions - Enables functional composition through
mapandflatMapmethods
- Uses the
-
Simplified API:
- Minimal interfaces designed for Java's functional style
- All interfaces are
@FunctionalInterfacefor lambda support - Method references enable concise code:
this::validateInput
-
Fluent DSL for use case composition:
// Define type-safe keys UseCaseKey<Input, Result<Output>> CREATE = UseCaseKey.of("create"); HexaApp app = HexaFun.dsl() .useCase(CREATE) .validate(this::validateInput) .handle(this::executeUseCase) .build();
-
Application Container:
HexaAppmanages and orchestrates use cases and adapters- Registry pattern for looking up use cases by name
- Clear separation between definition and execution
-
Testing Support:
- Declarative testing DSL for use cases
- Mock adapters for clean unit testing
- In-memory repositories for integration testing
HexaFun removes much of the boilerplate traditionally associated with Hexagonal Architecture while preserving its core benefits: separation of concerns, testability, and flexibility to change external implementations.
HexaFun is built around a few simple interfaces:
// Core business logic (use case)
public interface UseCase<I, O> {
O apply(I input);
}
// Input validation
public interface ValidationPort<I> {
Result<I> validate(I input);
}
// External dependencies - define your own interfaces
public interface TaskRepository {
Task save(Task task);
Optional<Task> findById(String id);
}// Type-safe keys carry input/output types at compile time
UseCaseKey<CreateInput, Result<Entity>> CREATE = UseCaseKey.of("create");
UseCaseKey<ListInput, List<Entity>> LIST = UseCaseKey.of("list");HexaApp app = HexaFun.dsl()
.useCase(CREATE)
.validate(this::validateInput) // validation port
.handle(this::createEntity) // use case implementation
.useCase(LIST)
.handle(this::listAllItems) // no validation needed
.build();// Compile-time type checking - wrong input type won't compile
Result<Entity> result = app.invoke(CREATE, new CreateInput("test"));| Concept | How HexaFun Supports It |
|---|---|
| Use Cases | UseCase<I, O> interface |
| Type-Safe Keys | UseCaseKey<I, O> for safe dispatch |
| Input Validation | ValidationPort<I> interface |
| Validator Chaining | Multiple .validate() calls |
| External Systems | Port registry with port(Class, impl) |
| Functional Pipelines | Chain .validate(...).handle(...) |
| Clean Architecture | Strict separation of concerns |
| Error Handling | Result<T> monadic error handling |
| Testing | Declarative testing DSL |
Easily test your use cases with a fluent API:
app.test(CREATE)
.with(new CreateInput("test"))
.expectOk(entity -> assertNotNull(entity.getId()));
app.test(VALIDATE)
.with(invalidInput)
.expectFailure(error -> assertEquals("Invalid input", error));Check out the CounterAppTest in the examples for a working demonstration of the testing API.
- Adapter integrations (REST, CLI, Events)
- Repository interface and built-in in-memory repo helpers
- Test DSL
- Type-safe use case keys
- Validator chaining
- Output port registry (
port(TaskRepo.class, impl)) - Query registered use cases (
registeredUseCases()) - Query registered ports (
registeredPorts()) - Modular grouping support
- Functional by default
- Framework-agnostic
- Clean and declarative
- Test-friendly
- Type-safe dispatch
- Pure Hexagonal vibes
com.guinetik.hexafun.hexa– Hexagonal ports (UseCase,UseCaseKey,ValidationPort)com.guinetik.hexafun.fun– Functional primitives (Result)com.guinetik.hexafun– Core application container (HexaApp,HexaFun)com.guinetik.hexafun.testing– Testing frameworkcom.guinetik.hexafun.examples– Example applications