# 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:
```mermaid
graph TD
Extension[Extension Layer]
Services[Services Layer
#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
```rust
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
```rust
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` 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:
1. **Create the Module**: Create a new file in `src/` (e.g., `src/item.rs`).
2. **Define the Struct**: Define your service struct with a generic repository.
```rust
pub struct ItemService {
repository: R,
}
```
3. **Implement `new`**: Provide a constructor that accepts the repository.
```rust
impl ItemService {
pub fn new(repository: R) -> Self {
Self { repository }
}
}
```
4. **Implement Business Logic**: Add methods for your business logic (e.g., `create_item`, `transfer_item`).
```rust
impl ItemService {
pub fn create_item(&self, item_id: String, data: String) -> Result- {
// 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)
}
}
```
5. **Register the Module**: Add your new module to `src/lib.rs` and export the service struct.
```rust
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.
```rust
// 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.