175 lines
5.9 KiB
Markdown
175 lines
5.9 KiB
Markdown
# 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<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
|
|
|
|
```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<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:
|
|
|
|
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<R: ItemRepository> {
|
|
repository: R,
|
|
}
|
|
```
|
|
3. **Implement `new`**: Provide a constructor that accepts the repository.
|
|
```rust
|
|
impl<R: ItemRepository> ItemService<R> {
|
|
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<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)
|
|
}
|
|
}
|
|
```
|
|
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.
|