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:
60
src/application/mod.rs
Normal file
60
src/application/mod.rs
Normal 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
36
src/domain/mod.rs
Normal 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
76
src/infrastructure/mod.rs
Normal 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
|
||||||
65
src/lib.rs
65
src/lib.rs
@@ -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
96
src/presentation/mod.rs
Normal 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
|
||||||
Reference in New Issue
Block a user