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
A canonical Ada 2022 application demonstrating a hybrid DDD/Clean/Hexagonal architecture with functional error handling.
| Status | |
| Scope | N/A - Architecture supports SPARK, implementation pending |
| Mode | N/A |
| Results | See CHANGELOG for updates when available |
Note: This application is not yet SPARK-friendly. SPARK compatibility is planned for future releases when embedded deployment is required.
A professional Ada 2022 application starter demonstrating a hybrid DDD/Clean/Hexagonal architecture with functional programming principles using the functional crate for Result monads.
This is a desktop/enterprise application template showcasing:
- 5-Layer Hexagonal Architecture (Domain, Application, Infrastructure, Presentation, Bootstrap)
- Static Dispatch Dependency Injection via generics (zero runtime overhead)
- Railway-Oriented Programming with Result monads (no exceptions across boundaries)
- Presentation Isolation pattern (only the Domain is shareable across apps)
- Single-Project Structure (easy Alire deployment)
This repository uses git submodules for shared tooling. Clone with:
git clone --recurse-submodules https://github.com/abitofhelp/hybrid_app_ada.gitOr if already cloned without submodules:
git submodule update --init --recursive
# Or: make submodule-init- ✅ Single-project structure (easy Alire deployment)
- ✅ Result monad error handling (Domain.Error.Result)
- ✅ Static dependency injection via generics
- ✅ Application.Error re-export pattern
- ✅ Architecture boundary validation (arch_guard.py)
- ✅ Comprehensive documentation with UML diagrams
- ✅ Test framework (see CHANGELOG)
- ✅ Windows CI with GitHub Actions
- ✅ Aspect syntax (not pragmas)
- ✅ Makefile automation
| Platform | Status | Notes |
|---|---|---|
| Linux | ✅ Full | CI tested, console I/O |
| macOS | ✅ Full | CI tested, console I/O |
| Windows | ✅ Full | CI tested (v2.0.0+), console I/O |
| Embedded | 🔧 Untested | Architecture supports it, not yet validated |
5 Layers (Dependency Rule: All dependencies point INWARD):
hybrid_app_ada/
├── src/
│ ├── domain/ # Pure Business Logic (ZERO dependencies)
│ │ ├── error/ # Error types & Result monad
│ │ └── value_object/ # Immutable value objects
│ │
│ ├── application/ # Use Cases & Ports (Depends on: Domain)
│ │ ├── command/ # Input DTOs
│ │ ├── error/ # Re-exports Domain.Error for Presentation
│ │ ├── port/ # Port interfaces (in/out)
│ │ └── usecase/ # Use case orchestration
│ │
│ ├── infrastructure/ # Driven Adapters (Depends on: Application + Domain)
│ │ └── adapter/ # Concrete implementations
│ │
│ ├── presentation/ # Driving Adapters (Depends on: Application ONLY)
│ │ └── cli/ # CLI interface
│ │
│ ├── bootstrap/ # Composition Root (Depends on: ALL)
│ │ └── cli/ # CLI wiring
│ │
│ └── greeter.adb # Main (3 lines - delegates to Bootstrap)
│
├── test/ # Test Suite
│ ├── unit/ # Domain & Application unit tests
│ ├── integration/ # Cross-layer integration tests
│ ├── e2e/ # End-to-end CLI tests
│ └── common/ # Shared test framework
│
├── docs/ # Documentation
│ ├── diagrams/ # UML diagrams (PlantUML)
│ └── formal/ # SDS, SRS, Test Guide
│
├── scripts/ # Automation
│ └── arch_guard.py # Architecture boundary validation
│
├── hybrid_app_ada.gpr # Main project file (single-project)
├── alire.toml # Alire manifest
└── Makefile # Build automation
Critical Boundary Rule:
Presentation is the ONLY outer layer prevented from direct Domain access
- ✅ Infrastructure CAN access
Domain.*(implements repositories, uses entities) - ✅ Application depends on
Domain.*(orchestrates domain logic) - ❌ Presentation CANNOT access
Domain.*(must useApplication.Error,Application.Command, etc.)
Why This Matters:
- Domain is the only shareable layer across multiple applications
- Each app has its own Application/Infrastructure/Presentation/Bootstrap
- Prevents tight coupling between UI and business logic
- Allows multiple UIs (CLI, REST, GUI) to share the same Domain
The Solution: Application.Error re-exports Domain.Error types:
-- Application.Error (facade for Presentation)
with Domain.Error;
package Application.Error is
-- Re-export Domain error types (zero overhead)
subtype Error_Type is Domain.Error.Error_Type;
subtype Error_Kind is Domain.Error.Error_Kind;
package Error_Strings renames Domain.Error.Error_Strings;
-- Convenience constants
Validation_Error : constant Error_Kind := Domain.Error.Validation_Error;
IO_Error : constant Error_Kind := Domain.Error.IO_Error;
end Application.Error;This project uses two distinct Result types:
-
Domain.Error.Result- Self-contained Result monad in the Domain layer- Used throughout Domain and Application layers
- Zero external dependencies
- Owns the
Error_Typedefinition
-
Functional.Result(fromfunctionalcrate) - Used only in Infrastructure- Bridges exception-throwing I/O to Result-based error handling
- Infrastructure catches exceptions, converts to
Functional.Result - Then converts to
Domain.Error.Resultbefore returning to Application
Why two Result types?
- Domain must have zero external dependencies (architecture rule)
- Infrastructure needs exception-to-Result conversion at I/O boundaries
- The conversion happens once at the boundary, not throughout the code
-- Compile-time polymorphism (USED in this project)
generic
with function Writer (Message : String) return Result;
package Application.Usecase.Greet is
function Execute (...) return Result;
end Application.Usecase.Greet;
-- Implementation
function Execute (...) return Result is
begin
return Writer("Hello, " & Name & "!"); -- Direct call (or inlined!)
end Execute;Benefits:
- ✅ Zero runtime overhead (compile-time resolution)
- ✅ Full inlining (compiler can optimize across boundaries)
- ✅ Stack allocation (no heap required)
- ✅ Type-safe (verified at compile time)
- GNAT FSF 13+ or GNAT Pro (Ada 2022 support)
- Alire 2.0+ package manager
- Java 11+ (for PlantUML diagram generation, optional)
# Build the project
make build
# or
alr build
# Clean artifacts
make clean
# Rebuild from scratch
make rebuild# Run the application
./bin/greeter Alice# Greet a person
./bin/greeter Alice
# Output: Hello, Alice!
# Name with spaces
./bin/greeter "Bob Smith"
# Output: Hello, Bob Smith!
# No arguments (shows usage)
./bin/greeter
# Output: Usage: greeter <name>
# Exit code: 1
# Empty name (validation error)
./bin/greeter ""
# Output: Error: Name cannot be empty
# Exit code: 1- 0: Success
- 1: Failure (validation error, infrastructure error, or missing arguments)
Tests use a custom lightweight test framework (no AUnit dependency):
| Test Type | Count | Location | Purpose |
|---|---|---|---|
| Unit | 85 | test/unit/ |
Domain & Application logic |
| Integration | 16 | test/integration/ |
Cross-layer interactions |
| E2E | 8 | test/e2e/ |
Full system via CLI (black-box) |
| Total | 109 | 100% passing |
# Run all tests
make test-all
# Run specific test level
make test-unit
make test-integration
make test-e2e
# Code quality
make check-arch # Validate architecture boundaries
make diagrams # Regenerate UML diagrams
make stats # Code statistics- 📚 Documentation Index - Complete documentation overview
- 🚀 Quick Start Guide - Get started in minutes
- 📖 Software Requirements Specification
- 🏗️ Software Design Specification
- 🧪 Software Test Guide
- 🗺️ Roadmap - Future plans
- 📝 CHANGELOG - Release history
docs/diagrams/application_architecture.svg- 5-layer architecture overviewdocs/diagrams/ada/application_error_pattern_ada.svg- Re-export patterndocs/diagrams/ada/package_structure_ada.svg- Actual packagesdocs/diagrams/ada/error_handling_flow_ada.svg- Error propagationdocs/diagrams/ada/static_dispatch_ada.svg- Static DI with genericsdocs/diagrams/ada/dynamic_static_dispatch_ada.svg- Static vs dynamic comparison
This project follows:
- Ada Agent (
~/.claude/agents/ada.md) - Architecture Agent (
~/.claude/agents/architecture.md) - Functional Agent (
~/.claude/agents/functional.md) - Testing Agent (
~/.claude/agents/testing.md)
- Aspects over Pragmas:
with Purenotpragma Pure - Contracts: Pre/Post conditions on all public operations
- No Heap: Domain uses bounded strings
- Immutability: Value objects immutable after creation
- Pure Functions: Domain logic has no side effects
- Result Monads: No exceptions across boundaries
- Static Dispatch: Generics for dependency injection
This project uses git submodules for shared tooling:
docs- Shared documentation templates and guidesscripts/python- Build, release, and architecture scriptstest/python- Shared test fixtures and configuration
hybrid_python_scripts (source repo)
│
│ git push (manual)
▼
GitHub
│
│ make submodule-update (in each consuming repo)
▼
┌─────────────────────────────────┐
│ 1. Pull new submodule commit │
│ 2. Stage reference change │
│ 3. Commit locally │
│ 4. Push to remote │
└─────────────────────────────────┘
# After fresh clone
make submodule-init
# Pull latest from submodule repos
make submodule-update
# Check current submodule commits
make submodule-statuspython3 ~/Python/src/github.com/abitofhelp/git/update_submodules.py
# Options:
# --dry-run Show what would happen without changes
# --no-push Update locally but do not push to remoteThis project is not open to external contributions at this time.
This project — including its source code, tests, documentation, and other deliverables — is designed, implemented, and maintained by human developers, with Michael Gardner as the Principal Software Engineer and project lead.
We use AI coding assistants (such as OpenAI GPT models and Anthropic Claude Code) as part of the development workflow to help with:
- drafting and refactoring code and tests,
- exploring design and implementation alternatives,
- generating or refining documentation and examples,
- and performing tedious and error-prone chores.
AI systems are treated as tools, not authors. All changes are reviewed, adapted, and integrated by the human maintainers, who remain fully responsible for the architecture, correctness, and licensing of this project.
Copyright © 2025 Michael Gardner, A Bit of Help, Inc.
Licensed under the BSD-3-Clause License. See LICENSE for details.
Michael Gardner A Bit of Help, Inc. https://github.com/abitofhelp
Status: Production Ready (v2.0.0)
- ✅ Single-project structure (easy Alire deployment)
- ✅ Result monad error handling (Domain.Error.Result)
- ✅ Static dependency injection via generics
- ✅ Application.Error re-export pattern
- ✅ Architecture boundary validation (arch_guard.py)
- ✅ Comprehensive documentation with UML diagrams
- ✅ Test framework (see CHANGELOG)
- ✅ Windows CI with GitHub Actions
- ✅ Aspect syntax (not pragmas)
- ✅ Makefile automation
- ✅ Alire publication