Service Traits — Defining Interfaces
The first step in defining a service is the trait — the contract between implementation and callers.
Define the interface
#![allow(unused)] fn main() { use id_effect::Effect; pub trait UserRepository: Send + Sync { fn get_user(&self, id: u64) -> Effect<User, DbError, ()>; fn save_user(&self, user: &User) -> Effect<(), DbError, ()>; } }
Conventions:
- Methods return
Effect<_, _, ()>— the service method itself has no extra environment; callers carryUserRepoincaps!. Send + Syncon the trait so handles likeArc<dyn UserRepository>work across fibers.- Small, verb-oriented methods (
get_user, notusers).
Define the capability service
#![allow(unused)] fn main() { struct UserRepo; }
This generates UserRepo: Capability with Value = Arc<dyn UserRepository>.
Use ~Key in callers
#![allow(unused)] fn main() { use id_effect::{effect, require, caps, succeed}; fn get_user_profile(id: u64) -> Effect<UserProfile, AppError, caps!(UserRepo)> { effect!(|r| { let repo = ~UserRepo; let user = ~ repo.get_user(id).map_error(AppError::Db); UserProfile::from(user) }) } }
Keep traits focused
#![allow(unused)] fn main() { // BAD — one god trait trait AppService { fn get_user(&self, id: u64) -> Effect<User, AppError, ()>; fn send_email(&self, to: &str, body: &str) -> Effect<(), AppError, ()>; } // GOOD — separate capabilities trait UserRepository { /* … */ } trait EmailService { /* … */ } struct UserRepo; struct Email; }
Functions declare exactly what they need: caps!(UserRepo, Email).