- Wire bank client and server for credit line repayment requests - Show credit line balance and repay action in the banking view - Extend org/bank payloads and models with credit line fields
Forge Services
This crate implements the service layer for the Forge application, containing the core business logic and orchestration.
Architecture
The service layer sits between the API/Extension layer and the Repository layer:
graph TD
Extension[Extension Layer]
Services[Services Layer<br/>#40;This Module#41;]
Repositories[Repositories Layer]
Database[Database]
Extension --> Services
Services --> Repositories
Repositories --> Database
Responsibilities
- Business Logic: Enforces game rules and constraints.
- Validation: Validates input data before processing.
- Orchestration: Coordinates operations across multiple repositories.
- Error Handling: Converts technical errors into business-friendly messages.
- Data Transformation: Handles JSON parsing and model conversion.
Actor Service
The ActorService manages player lifecycle and state.
Key Features
- Get-or-Create: Automatically creates new actors with default values if they don't exist.
- JSON Integration: Directly accepts JSON strings for updates and creation.
- Partial Updates: Supports updating specific fields without overwriting the entire actor.
- UID Protection: Prevents modification of immutable fields like UIDs.
Usage Example
use forge_services::ActorService;
use forge_repositories::RedisActorRepository;
// Initialize
let repo = RedisActorRepository::new(client);
let service = ActorService::new(repo);
// 1. Get Actor (creates default if missing)
let actor = service.get_actor("76561198123456789".to_string())?;
// 2. Create/Overwrite Actor
let json_data = r#"{"name": "NewPlayer", "bank": 1000.0}"#;
service.create_actor("76561198123456789".to_string(), json_data.to_string())?;
// 3. Update Actor (Partial)
let update_json = r#"{"bank": 1500.0}"#;
service.update_actor("76561198123456789".to_string(), update_json.to_string())?;
// 4. Check Existence
if service.actor_exists("76561198123456789".to_string())? {
println!("Actor exists");
}
// 5. Delete Actor
// 5. Delete Actor
service.delete_actor("76561198123456789".to_string())?;
Organization Service
The OrgService manages organization (guild/clan) lifecycle and member management.
Key Features
- Get-or-Create: Automatically creates new organizations with default values if they don't exist.
- Member Management: Handles adding and removing members with validation.
- Duplicate Prevention: Ensures unique organization IDs and member UIDs.
- Name Validation: Enforces non-empty organization names.
Usage Example
use forge_services::OrgService;
use forge_repositories::RedisOrgRepository;
// Initialize
let repo = RedisOrgRepository::new(client);
let service = OrgService::new(repo);
// 1. Get Organization (creates default if missing)
let org = service.get_org("elite_squad".to_string())?;
// 2. Create/Overwrite Organization
let json_data = r#"{"name": "Elite Squad", "description": "Best players", "leader": "76561198123456789"}"#;
service.create_org("elite_squad".to_string(), json_data.to_string())?;
// 3. Add Member
let member_json = r#"{"uid": "76561198987654321", "rank": "member"}"#;
service.add_member("elite_squad".to_string(), member_json.to_string())?;
// 4. Update Organization
let update_json = r#"{"description": "New description"}"#;
service.update_org("elite_squad".to_string(), update_json.to_string())?;
// 5. Check Existence
if service.org_exists("elite_squad".to_string())? {
println!("Organization exists");
}
Error Handling
The service layer returns Result<T, String> where the error string is a descriptive message suitable for logging or displaying to administrators. It wraps lower-level repository errors with additional context.
Contributing
We welcome contributions to the Forge Service Layer! This guide will help you understand how to add new services and maintain the existing codebase.
Adding a New Service
To add a new service (e.g., ItemService), follow these steps:
- Create the Module: Create a new file in
src/(e.g.,src/item.rs). - Define the Struct: Define your service struct with a generic repository.
pub struct ItemService<R: ItemRepository> { repository: R, } - Implement
new: Provide a constructor that accepts the repository.impl<R: ItemRepository> ItemService<R> { pub fn new(repository: R) -> Self { Self { repository } } } - Implement Business Logic: Add methods for your business logic (e.g.,
create_item,transfer_item).impl<R: ItemRepository> ItemService<R> { pub fn create_item(&self, item_id: String, data: String) -> Result<Item, String> { // Validation logic... if self.repository.exists(&item_id)? { return Err("Item already exists".to_string()); } // ... logic to create item let item = Item::new(item_id); self.repository.create(&item)?; Ok(item) } } - Register the Module: Add your new module to
src/lib.rsand export the service struct.pub mod item; pub use item::ItemService;
Testing
- Unit Tests: Write unit tests for your business logic.
- Mocking: Since services use generic repositories, you can easily mock them for testing without a real database.
// Example Mock struct MockRepo; impl ItemRepository for MockRepo { ... }
Best Practices
- Validation: Always validate data at the service boundary. Do not rely on the API layer or database constraints alone.
- Error Messages: Return user-friendly error messages. Avoid exposing internal database errors directly.
- Immutability: Respect immutable fields (like UIDs).
- Documentation: Document public methods with doc comments (
///) explaining their purpose, arguments, and return values.