Version: 2.0.1
Date: December 10, 2025
SPDX-License-Identifier: BSD-3-Clause
License File: See the LICENSE file in the project root
Copyright: © 2025 Michael Gardner, A Bit of Help, Inc.
Status: Released
- Installation
- First Build
- Running the Application
- Understanding the Architecture
- Making Your First Change
- Error Handling
- Running Tests
- Build Targets
- Common Issues
- Next Steps
# Clone the repository
git clone --recurse-submodules https://github.com/abitofhelp/hybrid_app_ada.git
cd hybrid_app_ada
# Build with Alire (automatically fetches dependencies)
alr build
# Or use make
make build- Alire 2.0+ (Ada package manager)
- GNAT 14+ (via Alire toolchain)
- Make (for convenience targets)
- Python 3 (for architecture validation)
- Java 11+ (optional, for PlantUML diagram generation)
# macOS (Homebrew)
brew install alire
# Linux - download from https://alire.ada.dev/docs/#installation
# Visit: https://github.com/alire-project/alire/releases
# Verify installation
alr --version# Check that the executable was built
ls -lh bin/greeter
# Run the application
./bin/greeter World
# Output: Hello, World!Success! You've built your first hexagonal architecture application in Ada 2022.
The project uses both Alire and Make for building:
# Development build (with debug symbols)
make build
# Or explicit development mode
make build-dev
# Optimized build (O2)
make build-opt
# Release build
make build-release# Development build
alr build --development
# Release build
alr build --releaseBuild Output:
- Executable:
bin/greeter - Object files:
obj/ - Library artifacts:
lib/
The Hybrid_App_Ada starter includes a simple greeter application demonstrating all architectural layers:
# Greet a person
./bin/greeter Alice
# Output: Hello, Alice!
# Name with spaces (use quotes)
./bin/greeter "Bob Smith"
# Output: Hello, Bob Smith!
# Show usage
./bin/greeter
# Output: Usage: greeter <name>
# Exit code: 1# Empty name triggers validation error
./bin/greeter ""
# Output: Error: Name cannot be empty
# Exit code: 1Key Points:
- All errors return via Result monad (no exceptions)
- Exit code 0 = success, 1 = error
- Validation happens in Domain layer
- Greeting format happens in Application layer
- Errors propagate through Application to Presentation
Hybrid_App_Ada demonstrates 5-layer hexagonal architecture:
+-------------------------------------------------+
| Bootstrap (Composition Root) | <- Wires everything together
+-------------------------------------------------+
| Presentation (CLI) | <- User interface (Application ONLY)
+-------------------------------------------------+
| Application (Use Cases + Ports) | <- Orchestration layer
+-------------------------------------------------+
| Infrastructure (Adapters) | <- Technical implementations
+-------------------------------------------------+
| Domain (Business Logic) | <- Pure business rules (ZERO dependencies)
+-------------------------------------------------+
- Domain has zero dependencies - Pure business logic (validation, value objects)
- Presentation cannot access Domain - Must use Application layer (Application.Error, Application.Command)
- Static dependency injection - Via generics (compile-time wiring)
- Railway-oriented programming - Result monads for error handling
- Single-project structure - Easy to deploy via Alire
User Input ("Alice")
|
v
Presentation.Adapter.CLI.Command.Greet (parses input)
|
v
Application.UseCase.Greet (creates Person, formats greeting)
|
v
Domain.Value_Object.Person (validates name)
|
v
Infrastructure.Adapter.Console_Writer (output)
|
v
Result[Unit] (success or error)
|
v
Exit Code (0 or 1)
| Layer | Responsibilities | Dependencies |
|---|---|---|
| Domain | Business logic, validation | NONE |
| Application | Use cases, ports, formatting | Domain |
| Infrastructure | Adapters (driven) | App + Domain |
| Presentation | UI (driving) | Application ONLY |
| Bootstrap | DI wiring | ALL |
- Domain: Provides data (Get_Name) and validates business rules
- Application: Formats output (Format_Greeting), orchestrates use cases
- Infrastructure: Implements technical concerns (console I/O)
- Presentation: Handles user interaction, maps errors to messages
Let's modify the greeting format:
The greeting format is in the Application layer:
# File: src/application/usecase/application-usecase-greet.adbFind the Format_Greeting function:
function Format_Greeting (Name : String) return String is
begin
return "Hello, " & Name & "!";
end Format_Greeting;Change the format, for example:
function Format_Greeting (Name : String) return String is
begin
return "Greetings, " & Name & "!";
end Format_Greeting;# Rebuild
make rebuild
# Run tests to ensure nothing broke
make test-all
# Test manually
./bin/greeter Alice
# Output: Greetings, Alice!Best Practice: Always run tests after making changes. This project has 109 tests ensuring correctness.
Hybrid_App_Ada uses the Result monad pattern - no exceptions are raised.
Result : constant Unit_Result.Result := Greet (Cmd);
if Unit_Result.Is_Ok (Result) then
-- Success path
Put_Line ("Operation succeeded");
else
-- Error path
Put_Line ("Operation failed");
end if;Result : constant Person_Result.Result := Person.Create ("");
if Result.Is_Error then
declare
Info : constant Error_Type := Person_Result.Error_Info (Result);
begin
Put_Line ("Error kind: " & Info.Kind'Image);
Put_Line ("Message: " & Error_Strings.To_String (Info.Message));
end;
end if;| Kind | Description |
|---|---|
Validation_Error |
Input validation failed (empty name, too long) |
Parse_Error |
Malformed data or parsing failure |
Not_Found_Error |
Requested resource not found |
IO_Error |
Infrastructure I/O operation failed |
Internal_Error |
Unexpected internal error (bug) |
The functional ^3.0.0 upgrade brings powerful new combinators:
-- Bimap: Transform both Ok and Error values
Result.Bimap (Ok_Fn => To_Upper, Error_Fn => Add_Context);
-- Ensure: Add validation postconditions
Result.Ensure (Is_Valid'Access, "Validation failed");
-- With_Context: Add error context
Result.With_Context ("While processing user input");
-- Fallback: Provide default on error
Result.Fallback (Default_Value);
-- Recover: Convert errors to Ok values
Result.Recover (Error_To_Value'Access);
-- Tap: Side effects without changing Result
Result.Tap (Log_Success'Access);Why No Exceptions?
- Explicit error paths enforced by compiler
- SPARK compatible for formal verification
- Deterministic timing (no stack unwinding)
- Errors are values that can be passed and transformed
| Test Type | Count | Location |
|---|---|---|
| Unit Tests | 85 | test/unit/ |
| Integration Tests | 16 | test/integration/ |
| E2E Tests | 8 | test/e2e/ |
| Total | 109 |
make test-all# Unit tests only
make test-unit
# Integration tests only
make test-integration
# E2E tests only
make test-e2e########################################
### ###
### ALL TEST SUITES: SUCCESS ###
### All 109 tests passed! ###
### ###
########################################
# Run unit test runner (all unit tests)
./test/bin/unit_runner
# Run individual test
./test/bin/test_domain_personHybrid_App_Ada supports code coverage analysis using GNATcoverage.
Before running coverage analysis for the first time, you need to build the GNATcoverage runtime library:
# Build the coverage runtime (one-time setup)
make build-coverage-runtime
# This will:
# - Locate your GNATcoverage installation
# - Build the runtime library from sources
# - Install it to external/gnatcov_rts/# Run tests with GNATcoverage analysis
make test-coverage
# View coverage report
# Coverage reports generated in coverage/ directory
# - coverage/index.html - HTML coverage report
# - coverage/*.xcov - Detailed coverage filesTest Framework: Custom lightweight framework (no AUnit dependency) located in test/common/test_framework.{ads,adb}
make build # Development build (default)
make build-dev # Explicit development mode
make build-opt # Optimized build (O2)
make build-release # Release build
make rebuild # Clean and rebuildmake test # Run all tests (alias)
make test-all # Run entire test suite
make test-unit # Unit tests only
make test-integration # Integration tests only
make test-e2e # E2E tests only
make test-coverage # Tests with coverage analysismake check # Run static analysis
make check-arch # Validate architecture boundaries
make stats # Show project statistics
make diagrams # Regenerate UML diagramsmake clean # Clean build artifacts
make clean-deep # Deep clean (slow rebuild)
make clean-coverage # Clean coverage datamake deps # Show dependency information
make prereqs # Verify prerequisites
make help # Show all available targetsA: You need GNAT FSF 13+ or GNAT Pro:
gnatls -vA: Alire should fetch dependencies automatically:
alr update
alr buildA: The make check-arch validates layer boundaries. These are warnings, not errors:
make check-archA: Test executables are in test/bin/:
ls -lh test/bin/
# Output:
# unit_runner
# test_domain_error_result
# test_domain_person
# test_application_command_greet
# test_application_usecase_greet
# test_greet_full_flow
# test_infrastructure_console_writer
# test_greeter_cliA: Execute the test directly:
./test/bin/test_domain_person- Software Design Specification - Deep dive into architecture
- Architecture Diagrams - Visual documentation
- Application Architecture - See dependency flow
Start with the wiring in Bootstrap:
# See how all layers are wired together
cat src/bootstrap/cli/bootstrap-cli.adbThen explore each layer:
# Domain (pure business logic)
ls src/domain/
# Application (use cases and ports)
ls src/application/
# Infrastructure (adapters)
ls src/infrastructure/
# Presentation (CLI)
ls src/presentation/
# Bootstrap (composition root)
ls src/bootstrap/# See how tests are organized
ls -R test/
# Read test framework
cat test/common/test_framework.ads- See how Result monads replace exceptions
- Study error propagation patterns
- Learn the Application.Error re-export pattern
- Experiment with new v2.0.0 combinators
- Static Dispatch (Ada) - Generic-based DI pattern
- Static vs Dynamic (Ada) - Pattern comparison
- See Bootstrap wiring examples
- Understand compile-time polymorphism
Follow the pattern:
- Domain: Create value objects/entities (
src/domain/) - Application: Define command, use case, ports (
src/application/) - Infrastructure: Implement adapters (
src/infrastructure/) - Presentation: Create CLI command (
src/presentation/) - Bootstrap: Wire everything together (
src/bootstrap/) - Tests: Add unit/integration/e2e tests (
test/)
- Main Documentation Hub - All documentation links
- Software Requirements Specification - Requirements
- Software Design Specification - Architecture
- Software Test Guide - Testing guide
For questions, issues, or contributions:
- Issues: GitHub Issues
- Documentation: See
docs/directory
License: BSD-3-Clause
Copyright: © 2025 Michael Gardner, A Bit of Help, Inc.
See LICENSE for full license text.