I'm trying to figure out what the appropriate way to deal with this problem is in a functional way. I'm assuming there's a functional data structure or technique that will make this easy to handle.
The problem is that I'm making an API call to get two bits of data, I use one in another API call and if that call works then I use the other bit of data in a second API call. I'm having trouble getting that all to flow together. The API calls can fail of course, so they return Either, and I'd like it to be all one long chain, but I can't figure out how to do that.
In an imperative language I'd do this:
let response = apiCall1();
// error handling, return failure
// do some processing on the response
let json = getJsonBody(response);
let bit1 = json['bit1'];
let bit2 = json['bit2'];
let response2 = apiCall2(bit1);
// error handling, return failure
let response3 = apiCall3(bit2);
// error handling, return failure
return response3 // return success
If the result from each API call was the input to the next function I'd do something like this:
return apiCall1()
.map(jsonBody)
.map(apiCall2)
.map(jsonBody)
.map(apiCall3)
So what's the functional way to accomplish this?
apiCall1 ----
| |
| |
v |
apiCall2 |
| |
| |
v |
apiCall3 <---|
1 Answer 1
This is where the monadic properties of Eithers
come in handy (although you don't have to understand monads to take advantage of them). Most functional programming languages have a way to easily write this sort of chaining. In Haskell it's do notation. In Scala it's for comprehensions. Since I'm more familiar with Scala, I'll demonstrate that below.
val response = for {
response <- apiCall1().right
json = getJsonBody(response)
response2 <- apiCall2(json("bit1")).right
response3 <- apiCall3(json("bit2")).right
} yield response3
Here, you use the right-projection of each of the Eithers
. It basically performs a flatMap
on the Right
values of the results of the api calls. As soon as it hits a Left
however, it will short-circuit the remainder of the calls and yield the Left
as the final result.
Note this is just syntactic sugar. The compiler translates it to a series of flatMaps
, and you can write it that way if you prefer, but this way is much easier to read.
-
Karl, technically this is called API Chaining(TM 2013) and requires following the new API pattern of abstracting communication logic/data from business logic to allow for a internal redirect. github.com/orubel/grails-api-toolkit-docs/wiki/API-ChainingOrubel– Orubel2016年10月05日 12:43:00 +00:00Commented Oct 5, 2016 at 12:43
-
3@Orubel, that technique requires server-side support. What I'm talking about is all client-side and is decades older.Karl Bielefeldt– Karl Bielefeldt2016年10月06日 18:04:11 +00:00Commented Oct 6, 2016 at 18:04
-
Karl, you cannot handle an API chain without a redirect which is handled server side by passing the response to the next endpoint and validating access to the next endpoint; one cannot assume that just because user has access to one endpoint that they have access to ALL ENDPOINTS. So yes, naturally the only way this CAN be done is through the backend. Anything else would be invalid in that you cannot validate and that I/O overhead is the same as making every single request from the client side. The point of an api chain is to reduce those consecutive api calls down to one request/response....Orubel– Orubel2016年10月07日 03:06:56 +00:00Commented Oct 7, 2016 at 3:06
-
And for the record Karl, monads predate this but they are NOT API Chaining(tm). I have given talks about this since 2012 since I'm the creator and maintainer of the spec.Orubel– Orubel2016年10月07日 03:11:06 +00:00Commented Oct 7, 2016 at 3:11
-
1Karl, you may be happy to know in Scala 2.12,
Either
is right-biased (meaning right projections are deprecated) like it should always have been. One step closer to Haskell... bwahahaha!Andres F.– Andres F.2016年10月07日 04:05:55 +00:00Commented Oct 7, 2016 at 4:05
Explore related questions
See similar questions with these tags.
zip
is a function that takes two sequences and returns a sequence of pairs.flatMap
method. If instead of single values you have asynchronous streams of data, you should look into observables.