HexaFun
A functional, declarative Java framework for building Hexagonal Architecture apps.
Built for JDK 17+ with a clean API that puts your use cases front and center. Forget the ceremony. Focus on composing behavior.
What is HexaFun?
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
Architecture Overview
┌────────────────────────┐
│ 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) organizes application logic around a core domain, isolated from external concerns.
Fluent DSL Example
The DSL provides a concise, declarative way to compose use cases:
HexaApp app = HexaFun.dsl()
// Create a use case with the name "increment"
.<IncrementInput>useCase("increment")
.from(CounterOperations::validateCounter)
.to(input -> Result.ok(input.getCounter().increment()))
.and()
// Create a use case with the name "decrement"
.<DecrementInput>useCase("decrement")
.from(CounterOperations::validateCounter)
.to(input -> Result.ok(input.getCounter().decrement()))
.and()
// Create a use case with the name "add"
.<AddInput>useCase("add")
.from(input -> {
Result<AddInput> counterResult = CounterOperations.validateCounter(input);
if (counterResult.isFailure()) {
return counterResult;
}
return CounterOperations.validateAmount(input);
})
.to(input -> Result.ok(input.getCounter().add(input.getAmount())))
.and()
.build();
// Invoke use cases by name
Counter initial = Counter.of(10);
Result<Counter> result = app.invoke("increment", new IncrementInput(initial));
Core Concepts
| Concept | How HexaFun Supports It |
|---|---|
| Use Cases | UseCase<I, O> interface |
| Input Validation | ValidationPort<I> interface |
| External Systems | OutputPort<I, O> interface |
| Functional Pipelines | Chain .from(...).to(...) |
| Clean Architecture | Strict separation of concerns |
| Error Handling | Result<T> monadic error handling |
| Testing | Declarative testing DSL |
Testing
Easily test your use cases with a fluent API:
app.test("createTask")
.with(new CreateTaskInput("Study HexaFun", "Important"))
.expectOk(task -> assertFalse(task.isCompleted()));
Packages
com.guinetik.hexafun.hexa– Hexagonal ports (UseCase,InputPort, etc)com.guinetik.hexafun.fun– Functional primitives (Result, etc)com.guinetik.hexafun– Core application container (HexaApp)com.guinetik.hexafun.testing– Testing frameworkcom.guinetik.hexafun.examples– Example applications