Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.