The Service Handle pattern represents a subsystem as an explicit value whose fields are operations. Client code receives that handle as a dependency and calls its small, high-level interface instead of reaching into implementation modules. The boundary makes dependencies visible, keeps implementation details behind the interface, and permits replacement without changing clients. section 4.2.2 section 4.3
Use a handle where a subsystem has accumulated many external relations or where business logic must not depend on infrastructure details. Keep the interface concise, accurate, and minimal; external code should use it rather than bypassing it. This supports low coupling, single responsibility, maintainability, and isolated testing with mocks or fakes. section 4.2.3 section 4.3
A handle is a record of operations
A greetings handle can expose both a pure greeting function and an effectful greeting function. Different handle values implement the same fields differently, while helloUser accepts the handle and selects the operation it needs. Injecting handle1 or handle2 changes behavior at composition time, not inside helloUser. section 4.2.2
Listing 13.6 Service Handle interface
data AstroServiceHandle = AstroServiceHandle
{ meteorReporter
:: MeteorTemplate
-> AppL (Either ReportingError MeteorId) #1
, asteroidReporter
:: AsteroidTemplate
-> AppL (Either ReportingError AsteroidId) #2
}
An API-like handle groups reporting operations into one algebraic data type; each field returns either a domain reporting error or an identifier. section 13.2.2
A hardware handle can hide device construction and lookup behind operations such as makeDevice, getBlankDevice, and getDevicePart. An implementation can be assembled from vendor components, and a test can call the same handle interface. Handles often abstract effects through impure methods, though they can also contain pure operations. section 4.2.3 section 4.2.3
For dependency inversion, construct the handle at the application boundary and pass it to business logic. A reporting handle can select TCP or HTTP implementations from configuration, while the application depends only on reporting operations and its ReportingError type. Networking and resource management remain outside the handle consumer. section 13.2.2
Choose this pattern when the service is primarily an API-like collection of distinct operations:
- Inject a handle directly when clients need explicit dependencies and independently callable service methods. section 13.2.2
- Consider ReaderT when a handle must be carried through a Reader environment as an advanced variation. section 13.3
- Use scenario-like or other functional interfaces when sequential scripts or different interface shapes are required. section 13.2
