Providing Services via Layers
You have a trait. You have an implementation. Now you need a Layer that wires them together.
The Minimal Service Layer
#![allow(unused)] fn main() { use id_effect::{LayerFn, tagged, effect}; // The production implementation struct PostgresUserRepository { pool: Pool, } impl UserRepository for PostgresUserRepository { fn get_user(&self, id: u64) -> Effect<User, DbError, ()> { effect! { let row = ~ query(&self.pool, "SELECT * FROM users WHERE id = $1", id); User::from_row(row) } } // ... } // The layer that builds the implementation from the database pool let user_repo_layer = LayerFn::new(|env: &Tagged<DatabaseKey>| { effect! { let repo = PostgresUserRepository { pool: env.value().clone() }; tagged::<UserRepositoryTag>(Arc::new(repo) as Arc<dyn UserRepository>) } }); }
The layer takes Tagged<DatabaseKey> (the database pool) and produces Tagged<UserRepositoryTag> (the repository wrapped as Arc<dyn UserRepository>).
Composition with Other Layers
The repository layer needs the database. Wire them:
#![allow(unused)] fn main() { let app_layer = config_layer .stack(db_layer) .stack(user_repo_layer); // db_layer output feeds into user_repo_layer }
Now app_layer produces an environment containing ConfigKey, DatabaseKey, and UserRepositoryTag — everything get_user_profile needs.
Test Layer with Mock
#![allow(unused)] fn main() { struct MockUserRepository { users: HashMap<u64, User>, } impl UserRepository for MockUserRepository { fn get_user(&self, id: u64) -> Effect<User, DbError, ()> { match self.users.get(&id) { Some(u) => succeed(u.clone()), None => fail(DbError::NotFound), } } // ... } let test_repo_layer = LayerFn::new(|_: &Nil| { let repo = MockUserRepository { users: [(1, alice()), (2, bob())].into(), }; succeed(tagged::<UserRepositoryTag>(Arc::new(repo) as Arc<dyn UserRepository>)) }); }
The test layer needs nothing (no real database), produces the same Tagged<UserRepositoryTag>, and can be stacked in place of the production layer.
The Swap
#![allow(unused)] fn main() { // Production run_blocking(my_app().provide_layer( config_layer.stack(db_layer).stack(user_repo_layer) )); // Test run_test(my_app().provide_layer( test_repo_layer // no config or db needed )); }
The application code doesn't change. Only the layer stack changes. The type system ensures both stacks satisfy the effect's requirements.