| title | Model Dependencies as Services | ||||||
|---|---|---|---|---|---|---|---|
| id | model-dependencies-as-services | ||||||
| skillLevel | intermediate | ||||||
| applicationPatternId | making-http-requests | ||||||
| summary | Abstract external dependencies and capabilities into swappable, testable services using Effect's dependency injection system. | ||||||
| tags |
|
||||||
| rule |
|
||||||
| related |
|
||||||
| author | Sandro Maglione | ||||||
| lessonOrder | 6 |
Represent any external dependency or distinct capability—from a database client to a simple UUID generator—as a service.
This pattern is the key to testability. It allows you to provide a Live implementation in production and a Test implementation (returning mock data) in your tests, making your code decoupled and reliable.
import { Effect } from "effect";
// Define Random service with production implementation as default
export class Random extends Effect.Service<Random>()("Random", {
// Default production implementation
sync: () => ({
next: Effect.sync(() => Math.random()),
}),
}) {}
// Example usage
const program = Effect.gen(function* () {
const random = yield* Random;
const value = yield* random.next;
return value;
});
// Run with default implementation
const programWithLogging = Effect.gen(function* () {
const value = yield* Effect.provide(program, Random.Default);
yield* Effect.log(`Random value: ${value}`);
return value;
});
Effect.runPromise(programWithLogging);Explanation:
By modeling dependencies as services, you can easily substitute mocked or deterministic implementations for testing, leading to more reliable and predictable tests.
Directly calling external APIs like fetch or using impure functions like Math.random() within your business logic. This tightly couples your logic to a specific implementation and makes it difficult to test.