From REST to algebraic data by Mark Seemann
Mapping RESTful HTTP requests to values of algebraic data types is easy.
In previous articles, you've seen how to easily model a simple domain model with algebraic data types, and how to use RESTful API design to surface such a model at the boundary of an application. In this article, you'll see how trivial it is to map incoming HTTP requests back to values of algebraic data types.
The advantage of REST is that you can make illegal states unrepresentable. Clients follow links, and while clients are supposed to treat links as opaque values, URLs still contain information your API can use.
Routing and dispatching #
Continuing where the previous article left off, clients can issue POST requests against a URL like https://example.com/credit-card
. On the server, a well-known piece of code handles such requests. (In the example code base I've used so far, I've been using ASP.NET Web API, so the code that handles such a request is a Controller.) Since you know that URLs like that are always routed to that particular piece of code, you can create a new PaymentType
value that specifically represents an individual payment with a credit card:
let paymentType = Individual { Name = "credit-card"; Action = "Pay" }
If, on the other hand, the client is using a provided link to POST a representation against the URL https://example.com/recurrent/start/credit-card
, your server-side dispatcher will route the request to a different handler (Controller), in which case you can create a PaymentType
value like this:
let paymentType = Parent { Name = "credit-card"; Action = "Pay" }
Finally, if the client has already created a parent payment and is now using the resulting link to create child payments, it may be POSTing to a URL like https://example.com/recurrent/42
. Your server-side dispatcher will route that request to a third handler. Most web frameworks, including ASP.NET Web API, will be able to pull values out of URLs. In this case, you can configure it so that it pulls the value 42
out of the URL and binds it to a value called transactionKey
. With this, again it's trivial to create a PaymentType
value:
let paymentType = Child (transactionKey, { Name = "credit-card"; Action = "PayRecurrent" })
Notice that, despite containing different data, and being created three different places in the code base, they all have the same type: PaymentType
. This means that you can pass these values to a common pay
function, which handles the actual communication with the third-party payment service.
Code reuse #
Independent of the route the data arrived at, a central, reusable function named pay
handles all such payments. This is still an impure boundary function that takes various other input apart from PaymentType
. Without going into too much detail, it has a type like Config -> PaymentType -> Result<PaymentDtr,BoundaryFailure>
. Don't worry if some of the details look obscure; the important point is that pay
is a function that takes a PaymentType
value as input. You can visualise the transition from HTTP requests to a function call like this:
The pay
function is composed from various smaller functions, some pure and some impure. Ultimately, it transforms all the input data to the format required by the third-party payment service, and forwards the transaction information. Inside that function you'll find the pattern match that you saw in my previous article.
Summary #
By making good use of routing and dispatching, you can easily map incoming HTTP requests to values of algebraic data types. This enables you to close the loop on exposing your domain model at the boundary of your system. Not only can clients request data from your API in terms of your model, but when clients send data to your API, you can translate that data back to your model.