Service Traits — Defining Interfaces
Services can be modeled as cloneable structs that contain concrete clients, trait objects, or function handles. Derive Service on the struct to make it available through ServiceContext.
Define the Interface
trait UserRepositoryImpl: Send + Sync {
fn get_user(&self, id: u64) -> Effect<User, DbError, ()>;
fn save_user(&self, user: User) -> Effect<(), DbError, ()>;
}
#[derive(Clone, Service)]
struct UserRepository {
inner: Arc<dyn UserRepositoryImpl>,
}
impl UserRepository {
fn get_user(&self, id: u64) -> Effect<User, DbError, ()> {
self.inner.get_user(id)
}
}
The service struct is the lookup key. It must be Clone + 'static.
Accessing a Service
Use Effect::service::<S>(), S::use_, or S::use_sync.
fn get_user_profile(id: u64) -> Effect<UserProfile, AppError, ServiceContext> {
UserRepository::use_(move |repo| {
repo.get_user(id)
.map(UserProfile::from)
.map_error(AppError::Db)
})
}
If the service is missing, the lookup fails with MissingService; include From<MissingService> in your application error when using use_ / Effect::service.
Tagged Alternative
The older typed-context style uses service_key!(pub struct Key); plus Tagged<Key, V> / Service<Key, V>.
service_key!(pub struct UserRepositoryKey);
type UserRepositoryService = Service<UserRepositoryKey, Arc<dyn UserRepositoryImpl>>;
Use this when you specifically want HList Context types in function signatures. Prefer derive-service for application service tables.
Keeping Services Focused
Avoid a single god service. Split by capability.
#[derive(Clone, Service)]
struct Users { /* ... */ }
#[derive(Clone, Service)]
struct Mailer { /* ... */ }
#[derive(Clone, Service)]
struct Payments { /* ... */ }
Functions should read exactly the services they need from ServiceContext.