diff --git a/src/application/mod.rs b/src/application/mod.rs new file mode 100644 index 0000000..fc52d9b --- /dev/null +++ b/src/application/mod.rs @@ -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, +//! repository: Arc, +//! } +//! +//! impl ToggleRelay { +//! pub async fn execute(&self, relay_id: RelayId) -> Result { +//! // 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 diff --git a/src/domain/mod.rs b/src/domain/mod.rs new file mode 100644 index 0000000..91189cc --- /dev/null +++ b/src/domain/mod.rs @@ -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` diff --git a/src/infrastructure/mod.rs b/src/infrastructure/mod.rs new file mode 100644 index 0000000..18449f5 --- /dev/null +++ b/src/infrastructure/mod.rs @@ -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>, +//! config: ModbusConfig, +//! } +//! +//! #[async_trait] +//! impl RelayController for ModbusRelayController { +//! async fn read_relay_state(&self, id: RelayId) -> Result { +//! 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 diff --git a/src/lib.rs b/src/lib.rs index c3472e2..6eababc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,47 @@ //! Backend API server for STA //! //! 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 //! - 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::pedantic)] @@ -21,6 +60,32 @@ pub mod startup; /// Logging and tracing setup 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>; fn prepare(listener: MaybeListener) -> startup::Application { diff --git a/src/presentation/mod.rs b/src/presentation/mod.rs new file mode 100644 index 0000000..f6d467f --- /dev/null +++ b/src/presentation/mod.rs @@ -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, +//! } +//! +//! impl From 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 for Relay { +//! type Error = ValidationError; +//! +//! fn try_from(dto: RelayResponse) -> Result { +//! 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 � 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