ResponseState.JS
JavaScript version of the ResponseState pattern.
The ResponseState pattern improves structure and control flow of business logic by providing
- a clean representation of the various outcomes that complex operations can yield, and
- an efficient control flow mechanism to react to those outcomes.
These conventions allow for a clean separation of the different layers within an application stack, through fully domain-specific APIs, while avoiding the need to abuse errors or exceptions for fine-grained, algorithmic control flow.
Using the ResponseState pattern
Lets assume our architecture has a
service layer
that contains a transferFunds
service.
This service returns its result as response states.
Here is how readable code that uses it is.
This code could for example live in a controller for a REST API.
(Examples are in CoffeeScript for readability)
servicestransferFunds from: 'checking'to: 'savings'amount: 100 resultis : -> send status: 201message: 'transfer complete' : -> send status: 202message: 'transfer pending user approval' : -> send status: 412message: 'daily transaction limit exceeded' : -> send status: 403message: 'not enough funds' : -> send status: 404message: 'unknown account given' : -> send status: 401message: 'please log in first' : -> send status: 500message: 'please try again later'
In this example,
the result is an instance of the Response
class provided
by this library.
It has an is
method, which calls the matching handler
from the given hash of response state handlers.
This represents the different code paths that deal with the different outcomes
of the attempt to create a transfer in a very readable and maintainable way.
The special other
response state acts as a catch-all,
i.e. it is called if none of the states listed match the current condition.
If no such catch-all handler is provided, unmatched errors cause an exception
in the is
method.
Implementing ResponseState APIs
With the help of this library, the API of the transferFunds
service above
can be implemented super easy like this:
Response = require 'response-state' = if not fromAccount = getAccount from then return done 'unknown_account' if frombalance < amount then return done 'insufficient_funds' if fromlimit < amount then return done 'limit_exceeded' ... done null'transfer_finished'confirmationurl
Data handlers
The is
method returns the result of the respective response state handler function.
If the handler is not a function, it is returned directly.
This allows to refactor the example above into:
servicestransferFunds from: 'checking'to: 'savings'amount: 100 resultis success: 201'transfer complete' pending: 202'transfer pending user approval' limit_exceeded: 412'daily transaction limit exceeded' insufficient_funds: 403'not enough funds' unknown_account: 404'unknown account given' unauthorized: 401'please log in first' other: 500'please try again later' send statusmessage
When to use
Response states are an optional pattern that should be used casually, i.e. only if the value it adds exceeds the complexity it introduces. This is probably mostly the case across larger functional boundaries, when calling into different architectural layers that perform complex operations. The responsibility of verifying the overall correctness of the code's behavior, i.e. that it transfers the right amounts between the right accounts at the right times, for the right reasons, remains with your test suite.
Development
See the developer documentation