feat(src): create hexagonal architecture module structure

Establish foundational module hierarchy following hexagonal architecture
(clean architecture) patterns: domain, application, infrastructure, and
presentation layers. Each module includes comprehensive documentation
explaining its architectural role and responsibilities.

Ref: T002 (specs/001-modbus-relay-control)
This commit is contained in:
2026-01-01 14:10:13 +01:00
parent 926f8da683
commit d5c70f3e7f
5 changed files with 333 additions and 0 deletions

60
src/application/mod.rs Normal file
View File

@@ -0,0 +1,60 @@
//! Application layer - Use cases and orchestration logic
//!
//! This module implements business use cases by coordinating domain entities and
//! infrastructure services. It contains the application logic that orchestrates
//! domain behavior without implementing domain rules directly.
//!
//! # Architecture Principles
//!
//! - **Depends on Domain layer**: Uses domain entities, value objects, and traits
//! - **Framework-independent**: No dependencies on HTTP, database, or other infrastructure
//! - **Use case driven**: Each module represents a specific business use case
//! - **Testable in isolation**: Can be tested with mock infrastructure implementations
//!
//! # Planned Submodules
//!
//! - `relay`: Relay control use cases
//! - `get_status`: Retrieve current state of one or all relays
//! - `toggle_relay`: Switch relay on/off with validation
//! - `bulk_control`: Control multiple relays (all on, all off, pattern)
//! - `update_label`: Manage relay labels with persistence
//! - `get_health`: Check device health and connectivity
//!
//! # Use Case Pattern
//!
//! Each use case follows this pattern:
//! 1. Accept domain types as input (validated at boundary)
//! 2. Orchestrate domain entities and services
//! 3. Return domain types or application-specific results
//! 4. Depend on traits (RelayController, RelayLabelRepository), not concrete types
//!
//! # Example Use Case Structure
//!
//! ```rust,ignore
//! pub struct ToggleRelay {
//! controller: Arc<dyn RelayController>,
//! repository: Arc<dyn RelayLabelRepository>,
//! }
//!
//! impl ToggleRelay {
//! pub async fn execute(&self, relay_id: RelayId) -> Result<Relay, ApplicationError> {
//! // 1. Read current state
//! let current = self.controller.read_relay_state(relay_id).await?;
//!
//! // 2. Toggle state (domain logic)
//! let new_state = current.toggle();
//!
//! // 3. Write new state
//! self.controller.write_relay_state(relay_id, new_state).await?;
//!
//! // 4. Return updated relay
//! Ok(Relay::new(relay_id, new_state))
//! }
//! }
//! ```
//!
//! # References
//!
//! - Architecture: `specs/constitution.md` - Hexagonal Architecture principles
//! - Use cases: `specs/001-modbus-relay-control/plan.md` - Implementation plan
//! - Domain types: [`crate::domain`] - Domain entities and value objects

36
src/domain/mod.rs Normal file
View File

@@ -0,0 +1,36 @@
//! Domain layer - Pure business logic with no external dependencies
//!
//! This module contains the core business domain for the StA relay control system.
//! It follows **Domain-Driven Design** principles with rich domain models and clear
//! ubiquitous language.
//!
//! # Architecture Principles
//!
//! - **No external dependencies**: Domain layer depends only on Rust standard library
//! - **Inward-pointing dependencies**: Infrastructure/Application depend on Domain, never reverse
//! - **Rich domain models**: Entities and value objects encapsulate business rules
//! - **Ubiquitous language**: Code reflects real-world relay control domain concepts
//!
//! # Planned Submodules
//!
//! - `relay`: Core relay domain (RelayId, RelayState, RelayLabel, Relay entity)
//! - Value objects with validation (newtypes following TyDD principles)
//! - Domain entities (Relay, RelayCollection)
//! - Repository traits (RelayLabelRepository)
//! - Controller traits (RelayController)
//! - Domain errors (DomainError, ValidationError)
//!
//! # Type-Driven Development (TyDD)
//!
//! Domain types follow "make illegal states unrepresentable" principle:
//! - `RelayId`: Newtype wrapping u8, validated to 1..=8 range
//! - `RelayLabel`: String wrapper, validated max 50 chars
//! - `RelayState`: Enum (On, Off) - no invalid states possible
//!
//! See `specs/001-modbus-relay-control/types-design.md` for complete type design.
//!
//! # References
//!
//! - Architecture: `specs/constitution.md` - Hexagonal Architecture principles
//! - Type design: `specs/001-modbus-relay-control/types-design.md`
//! - Domain specification: `specs/001-modbus-relay-control/spec.md`

76
src/infrastructure/mod.rs Normal file
View File

@@ -0,0 +1,76 @@
//! Infrastructure layer - External integrations and adapters
//!
//! This module implements the technical infrastructure required by the application,
//! including external system integrations, persistence, and communication protocols.
//! All infrastructure depends on domain/application layers through trait implementations.
//!
//! # Architecture Principles
//!
//! - **Implements domain traits**: Provides concrete implementations of domain interfaces
//! - **Depends inward**: Depends on domain/application, never the reverse
//! - **Substitutable**: Different implementations can be swapped without changing domain
//! - **Framework-specific**: Contains framework and library dependencies (Modbus, SQLx, etc.)
//!
//! # Planned Submodules
//!
//! ## `modbus` - Modbus RTU over TCP Integration
//!
//! - `client`: ModbusRelayController implementation using tokio-modbus
//! - `mock`: MockRelayController for testing without hardware
//! - `config`: Modbus connection configuration
//! - `connection`: Connection pool and health management
//!
//! Implements: [`domain::relay::controller::RelayController`](crate::domain)
//!
//! ## `persistence` - SQLite Database with SQLx
//!
//! - `sqlite_repository`: SqliteRelayLabelRepository implementation
//! - `schema.sql`: Database schema with relay_labels table
//! - `migrations`: Database migration scripts (if using sqlx-cli)
//!
//! Implements: [`domain::relay::repository::RelayLabelRepository`](crate::domain)
//!
//! # Technology Stack
//!
//! - **Modbus**: `tokio-modbus` 0.17.0 for async Modbus RTU over TCP
//! - **Persistence**: `sqlx` 0.8 for compile-time verified SQLite queries
//! - **Async Runtime**: `tokio` 1.48 (shared with main application)
//! - **Testing**: `mockall` 0.13 for mock implementations
//!
//! # Implementation Pattern
//!
//! Each infrastructure adapter:
//! 1. Implements domain-defined trait (e.g., `RelayController`)
//! 2. Translates domain types to/from external formats
//! 3. Handles connection management and retries
//! 4. Provides error translation (external errors → domain errors)
//!
//! # Example: Modbus Controller
//!
//! ```rust,ignore
//! pub struct ModbusRelayController {
//! client: Arc<Mutex<ModbusClient>>,
//! config: ModbusConfig,
//! }
//!
//! #[async_trait]
//! impl RelayController for ModbusRelayController {
//! async fn read_relay_state(&self, id: RelayId) -> Result<RelayState, ControllerError> {
//! let address = self.config.base_address + id.value() as u16;
//! let mut client = self.client.lock().await;
//!
//! // Read coil from Modbus device
//! let result = client.read_coils(address, 1).await
//! .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
//!
//! // Translate to domain type
//! Ok(if result[0] { RelayState::On } else { RelayState::Off })
//! }
//! }
//! ```
//!
//! # References
//!
//! - Architecture: `specs/constitution.md` - Dependency Inversion Principle
//! - Implementation: `specs/001-modbus-relay-control/plan.md` - Infrastructure tasks
//! - Modbus docs: `docs/Modbus_POE_ETH_Relay.md` - Hardware protocol specification

View File

@@ -1,8 +1,47 @@
//! Backend API server for STA //! Backend API server for STA
//! //!
//! This is a REST API built with the Poem framework that provides: //! This is a REST API built with the Poem framework that provides:
//! Backend API server for `StA` (Smart Temperature & Appliance Control)
//!
//! `StA` is a web-based Modbus relay control system that provides `RESTful` API access
//! to 8-channel relay devices. The system eliminates the need for specialized Modbus
//! software, enabling browser-based relay control for automation and remote management.
//!
//! # Architecture
//!
//! This crate follows **Hexagonal Architecture** (Clean Architecture) with strict
//! layer separation and inward-pointing dependencies:
//!
//! - **[`domain`]**: Pure business logic with no external dependencies (relay entities, value objects)
//! - **[`application`]**: Use cases and orchestration logic (relay control, label management)
//! - **[`infrastructure`]**: External integrations (Modbus TCP, `SQLite` persistence)
//! - **[`presentation`]**: API contracts and DTOs (not yet used - see [`route`] for current API)
//!
//! Traditional modules (will be migrated to hexagonal layers):
//! - **[`route`]**: HTTP API endpoints (will move to `presentation`)
//! - **[`middleware`]**: Custom middleware (rate limiting, CORS)
//! - **[`settings`]**: Configuration management from YAML + env vars
//! - **[`startup`]**: Application builder and server configuration
//! - **[`telemetry`]**: Logging and tracing setup
//!
//! # Current Features
//!
//! - Health check endpoints //! - Health check endpoints
//! - Application metadata endpoints //! - Application metadata endpoints
//! - Rate limiting middleware
//! - CORS support
//! - `OpenAPI` documentation
//!
//! # Planned Features (001-modbus-relay-control)
//!
//! - Modbus RTU over TCP communication with 8-channel relay devices
//! - Real-time relay status monitoring
//! - Individual relay control (on/off toggle)
//! - Bulk relay operations (all on, all off)
//! - Persistent relay labels (`SQLite` with `SQLx`)
//! - Device health monitoring
//!
//! See `specs/001-modbus-relay-control/` for detailed specification.
#![deny(clippy::all)] #![deny(clippy::all)]
#![deny(clippy::pedantic)] #![deny(clippy::pedantic)]
@@ -21,6 +60,32 @@ pub mod startup;
/// Logging and tracing setup /// Logging and tracing setup
pub mod telemetry; pub mod telemetry;
/// Domain layer - Pure business logic with no external dependencies
///
/// Contains domain entities, value objects, and business rules for the relay
/// control system. This layer has no dependencies on frameworks or infrastructure.
///
/// See `specs/constitution.md` for hexagonal architecture principles.
pub mod domain;
/// Application layer - Use cases and orchestration logic
///
/// Coordinates domain entities to implement business use cases such as relay
/// control, label management, and device health monitoring.
pub mod application;
/// Infrastructure layer - External integrations and adapters
///
/// Implements interfaces defined in domain/application layers for external
/// systems: Modbus TCP communication, SQLite persistence, HTTP clients.
pub mod infrastructure;
/// Presentation layer - API contracts and DTOs
///
/// Defines data transfer objects and API request/response types. Currently
/// unused - API handlers are in [`route`] module (legacy structure).
pub mod presentation;
type MaybeListener = Option<poem::listener::TcpListener<String>>; type MaybeListener = Option<poem::listener::TcpListener<String>>;
fn prepare(listener: MaybeListener) -> startup::Application { fn prepare(listener: MaybeListener) -> startup::Application {

96
src/presentation/mod.rs Normal file
View File

@@ -0,0 +1,96 @@
//! Presentation layer - API contracts and DTOs
//!
//! This module defines data transfer objects (DTOs) and API request/response types
//! that form the public API contract. It translates between domain types and wire
//! formats (JSON) for HTTP communication.
//!
//! # Current Status
//!
//! **Currently unused** - API handlers are currently in [`crate::route`] module using
//! legacy structure. This module is prepared for future migration to proper hexagonal
//! architecture with clear presentation layer separation.
//!
//! # Architecture Principles
//!
//! - **API-first design**: Define contracts before implementation
//! - **DTO pattern**: Separate domain entities from API representations
//! - **Validation at boundary**: Parse and validate input before domain layer
//! - **OpenAPI integration**: Generate documentation from code
//!
//! # Planned Submodules
//!
//! ## `dto` - Data Transfer Objects
//!
//! - `relay_dto`: RelayResponse, RelayStatusResponse, BulkControlRequest
//! - `label_dto`: UpdateLabelRequest, LabelResponse
//! - `health_dto`: HealthResponse, DeviceStatusResponse
//! - `error_dto`: ApiError, ValidationError (user-facing errors)
//!
//! ## `mapper` - Domain ↔ DTO Conversions
//!
//! - `relay_mapper`: Convert between Relay entity and RelayResponse
//! - `error_mapper`: Translate domain errors to HTTP status codes
//!
//! ## `validator` - Input Validation
//!
//! - Request validation before domain layer
//! - Parse DTOs into domain types (parse, don't validate principle)
//!
//! # DTO Design Pattern
//!
//! DTOs are separate from domain types to:
//! 1. **Prevent domain leakage**: Domain types stay internal
//! 2. **Enable versioning**: API can evolve without changing domain
//! 3. **Control serialization**: Explicit JSON representation
//! 4. **Validate at boundary**: Convert raw input to validated domain types
//!
//! # Example DTO Structure
//!
//! ```rust,ignore
//! /// API response for relay status
//! #[derive(Serialize, Deserialize, Object)]
//! pub struct RelayResponse {
//! /// Relay ID (1-8)
//! pub id: u8,
//! /// Current state ("on" or "off")
//! pub state: String,
//! /// Optional custom label
//! pub label: Option<String>,
//! }
//!
//! impl From<Relay> for RelayResponse {
//! fn from(relay: Relay) -> Self {
//! Self {
//! id: relay.id().value(),
//! state: relay.state().to_string(),
//! label: relay.label().map(|l| l.to_string()),
//! }
//! }
//! }
//!
//! impl TryFrom<RelayResponse> for Relay {
//! type Error = ValidationError;
//!
//! fn try_from(dto: RelayResponse) -> Result<Self, Self::Error> {
//! let id = RelayId::new(dto.id)?;
//! let state = RelayState::from_str(&dto.state)?;
//! let label = dto.label.map(RelayLabel::new).transpose()?;
//! Ok(Relay::new(id, state).with_label(label))
//! }
//! }
//! ```
//!
//! # Migration Plan
//!
//! Current API in [`crate::route`] will be migrated to this layer:
//! 1. Define DTOs for all API endpoints
//! 2. Implement domain <20> DTO mappers
//! 3. Move API handlers to use DTOs
//! 4. Generate OpenAPI specs from DTOs
//! 5. Remove direct domain type exposure
//!
//! # References
//!
//! - Architecture: `specs/constitution.md` - API-First Design principle
//! - API design: `specs/001-modbus-relay-control/plan.md` - Presentation layer tasks
//! - Domain types: [`crate::domain`] - Types to be wrapped in DTOs