Jacob Schmidt 1d54cc70c3 Move hot state into transient extension-backed stores
- Remove in-SQF registry mirroring for actor, bank, CAD, org, and task state
- Add validation harness and persistence warnings for hot-state flows
- Treat CAD and task operational state as restart-scoped
2026-04-05 10:05:48 -05:00
..

Forge Library

This directory contains the core business logic and data layers for the Forge framework, organized into modular, reusable crates that follow clean architecture principles.

Architecture Overview

The library follows a layered architecture pattern, ensuring separation of concerns and maintainability:

graph TD
    Extension[Extension Layer<br/>#40;ArmA 3 Interface#41;]
    Services[Services Layer<br/>#40;Business Logic#41;]
    Repositories[Repositories Layer<br/>#40;Data Persistence#41;]
    Models[Models Layer<br/>#40;Data Structures#41;]

    Extension --> Services
    Services --> Repositories
    Repositories --> Models

Modules

Models (lib/models)

Purpose: Defines strict data structures and validation rules for domain entities.

Responsibilities:

  • Define entity structures (Actor, Org)
  • Implement validation logic
  • Handle serialization/deserialization (JSON, Arma)
  • Enforce business rules at the data level

Key Features:

  • Strong typing with Rust structs
  • Built-in validation on creation and updates
  • Automatic email generation for actors
  • Arma-specific type conversions

Documentation: models/README.md

Repositories (lib/repositories)

Purpose: Manages data persistence and retrieval using Redis.

Responsibilities:

  • Abstract database operations
  • Implement CRUD operations
  • Handle data serialization to Redis formats
  • Manage Redis keys and data structures

Key Features:

  • Generic over Redis client implementations
  • Hash-based storage for structured data
  • Set-based storage for collections (e.g., org members)
  • Thread-safe operations (Send + Sync)

Documentation: repositories/README.md

Services (lib/services)

Purpose: Implements business logic, validation, and orchestration of operations.

Responsibilities:

  • Coordinate between repositories
  • Enforce business rules
  • Handle complex workflows
  • Provide high-level APIs for the extension layer

Key Features:

  • Generic over repository implementations
  • Stateless service design
  • Get-or-create patterns for entities
  • Comprehensive error handling

Documentation: services/README.md

Shared (lib/shared)

Purpose: Provides common utilities, traits, and helper functions used across all layers.

Responsibilities:

  • Define shared traits (RedisClient)
  • Provide utility functions
  • Common type definitions
  • Cross-cutting concerns

Key Features:

  • RedisClient trait for repository abstraction
  • JSON/Redis value parsing utilities
  • Arma value conversion helpers
  • Reusable helper functions

How It All Works Together

Example: Creating an Actor

Here's how the layers interact when creating a new actor:

  1. Extension Layer receives SQF command:

    // arma/server/extension/src/actor.rs
    pub fn create_actor(key: String, data: String) -> String {
        // Parse JSON and call service
        ACTOR_SERVICE.create_actor(uid, json_data)
    }
    
  2. Service Layer validates and orchestrates:

    // lib/services/src/actor.rs
    impl<R: ActorRepository> ActorService<R> {
        pub fn create_actor(&self, uid: String, data: String) -> Result<Actor, String> {
            // Create actor model (validates data)
            let actor = Actor::new(uid, data)?;
    
            // Persist via repository
            self.repository.create(&actor)?;
    
            Ok(actor)
        }
    }
    
  3. Repository Layer persists to Redis:

    // lib/repositories/src/actor.rs
    impl<C: RedisClient> ActorRepository for RedisActorRepository<C> {
        fn create(&self, actor: &Actor) -> Result<(), String> {
            // Convert actor to Redis hash
            let fields = actor.to_redis_fields();
    
            // Store in Redis
            self.client.hash_mset(format!("actor:{}", actor.uid), fields)
        }
    }
    
  4. Model Layer ensures data integrity:

    // lib/models/src/actor.rs
    impl Actor {
        pub fn new(uid: String, data: String) -> Result<Self, String> {
            // Validate all fields
            Self::validate(&uid, &data)?;
    
            // Create actor with validated data
            Ok(Actor { uid, /* ... */ })
        }
    }
    

Contributing

We welcome contributions to the Forge library! Follow these guidelines to maintain consistency and quality.

Adding a New Model

See models/README.md - Contributing

Summary:

  1. Define struct with validation rules
  2. Implement new and validate methods
  3. Add serialization traits (Serialize, Deserialize)
  4. Implement Arma conversions (FromArma, IntoArma)

Adding a New Repository

See repositories/README.md - Contributing

Summary:

  1. Define repository trait with Send + Sync
  2. Implement trait for RedisXRepository<C: RedisClient>
  3. Use forge_shared::RedisClient for operations
  4. Register module in lib.rs

Adding a New Service

See services/README.md - Contributing

Summary:

  1. Create service struct generic over repository
  2. Implement constructor and business logic methods
  3. Delegate data operations to repository
  4. Register module in lib.rs

Best Practices

Separation of Concerns

  • Models: Only data structures and validation
  • Repositories: Only data persistence logic
  • Services: Only business logic and orchestration
  • Shared: Only common utilities and traits

Error Handling

  • Use Result<T, String> for all fallible operations
  • Provide descriptive error messages
  • Propagate errors up the stack with ?

Testing

  • Models: Test validation rules
  • Repositories: Test with mock Redis clients
  • Services: Test with mock repositories
  • Integration: Test full stack in extension layer

Dependencies

  • Models should have minimal dependencies
  • Repositories depend on models and shared
  • Services depend on repositories and models
  • Avoid circular dependencies

Thread Safety

  • All repository traits require Send + Sync
  • Services are stateless and thread-safe
  • Use appropriate synchronization primitives when needed

Module Dependencies

graph TD
    Shared[Shared<br/>#40;No Dependencies#41;]
    Models[Models<br/>#40;Depends on Shared#41;]
    Repositories[Repositories<br/>#40;Depends on Models, Shared#41;]
    Services[Services<br/>#40;Depends on Repositories, Models#41;]
    Extension[Extension<br/>#40;Depends on Services, Repositories, Models, Shared#41;]

    Shared --> Services
    Services --> Repositories
    Repositories --> Models
    Models --> Extension

Development Workflow

  1. Define Model: Start with data structure and validation
  2. Create Repository: Implement persistence layer
  3. Build Service: Add business logic
  4. Expose in Extension: Create SQF-callable commands
  5. Test: Verify each layer independently and together

Additional Resources