Skip to main content
chaptertechnical-review

Effect: typed failures, services and resource cleanup

Prerequisites and adoption decision​

Complete the plain TypeScript DI exercise first. Effect is useful when dependency graphs, expected failures, concurrency and cleanup repeat across operations. It imposes a vocabulary and integration cost; it is not necessary to wrap every React click handler.

Mental model​

An Effect value describes work with success, expected error and required-environment types. Constructing the description does not itself run the operation. Keep expected domain failures distinct from unexpected defects; converting every exception into a generic success value erases useful diagnostic information.

The lab pins Effect 3.22.2. Accounts and Actor are context tags; layers construct their implementations. ManagedRuntime hosts the selected graph. The account service checks ownership in both the Promise and Effect versions, so the comparison changes composition rather than business policy. Official v3 layer documentation describes construction and dependency management; examples for other major versions may differ.

Resource ownership in practice​

The adapter layer acquires a resource with acquireRelease. Disposing the managed runtime releases it. The UI's comparison reports released resources, and a test asserts cleanup after running the operation. In a BFF, create request identity in request scope while sharing only safe infrastructure; never turn the current actor layer into mutable process-global state.

Cross into React through an explicit boundary. A component starts an operation and owns its cancellation signal; render declarative view state rather than launching work during render. Dispose resources on the lifetime boundary, not after each arbitrary nested helper call. One runtime per tiny operation can defeat pooling; one runtime for every user's identity can defeat isolation.

Failure, concurrency and streams​

Use retries only for eligible transient reads or operations with a proven idempotency protocol. A typed error channel makes the classification visible; it does not make retries safe automatically. Bound attempts and total elapsed time, propagate cancellation and keep a request's latency budget across nested calls.

For independent work, bounded concurrency limits resource pressure. Unbounded parallelism against a partner can increase failure rate and tail latency. Structured concurrency is valuable when child work must not outlive its owner. Resourceful streams should scope acquisition around consumption so early termination also releases resources. Test interruption while awaiting, not only success completion.

The current executable comparison covers services/layers, domain failures, runtime disposal and abort cleanup. It is intentionally small enough to compare with plain TypeScript. A full database pool or distributed worker is not simulated by its release counter.

Exercise, expected answer and migration​

Replace the live adapter with a failure fixture, then cancel a pending effect and assert its release action. Add a bounded parallel read of three independent resources, inject one failure and record what happens to siblings. A strong answer preserves the same tenant rules, classifies failures without swallowing defects, and can explain runtime ownership without referring to React context.

Migrate one boundary at a time behind the existing port. Compare error handling and test setup costs. Roll back by restoring the Promise adapter while preserving the domain interface; avoid exposing library-specific types across every public package before the team has evaluated them.

Executable reference and sources​

Implementation · Behavioral evidence · Primary source.

Run the projects and assessment. Human review is pending.