queen-of-idleness
07/28/2020, 9:33 AMGiven
Two external services should be called successively.
Calling service B depends on the response of service A.
The responses are wrapped in this monad result type.
There are two places where an error response can happen. In that case the flow should be aborted immediately and the error should be converted to a http response with a human readable body.
We want to stay with the standard library, http4k, result4k and coroutines.
So we don't want to use a library like arrow or similar.
Issues
1. Is there a better mechanism to chain the flow?
2. Should calling external services be wrapped in some structure like Future
or Deferred
? We might have to wait for their result but can't do anything in parallel in the meantime.
This is the simplified, compiling pseudo code:
data class HttpResponse(val status: Int, val body: String)
typealias RequestA = String
typealias ResponseA = String
typealias ErrorA = String
typealias RequestB = String
typealias ResponseB = String
typealias ErrorB = String
fun someFunction(
callServiceA: (RequestA) -> Result<ResponseA, ErrorA>,
callServiceB: (RequestB) -> Result<ResponseB, ErrorB>,
input: String
): HttpResponse =
callServiceA(input)
.mapFailure { HttpResponse(409, "please provide a valid id") }
.flatMap { responseA ->
callServiceB(responseA)
.mapFailure { HttpResponse(401, "you don't exist") }
.map { responseB -> HttpResponse(200, responseB.capitalize()) }
}.get()
Paul Martin
07/30/2020, 5:02 PMError
sealed class in our `Result`s which has a toErrorResponse()
method which converts the error to an http response. something like this:
sealed class Error(open val message: String) {
fun toErrorResponse(): HttpResponse = when (this) {
is ValidationFailure -> HttpResponse(409, this.message)
is Unauthorised -> HttpResponse(401, this.message)
}
}
data class ValidationFailure(override val message: String): Error (message)
data class Unauthorised(override val message: String): Error (message)
data class HttpResponse(val status: Int, val body: String)
typealias RequestA = String
typealias ResponseA = String
typealias RequestB = String
typealias ResponseB = String
fun someFunction(
callServiceA: (RequestA) -> Result<ResponseA, Error>,
callServiceB: (RequestB) -> Result<ResponseB, Error>,
input: String
): HttpResponse =
callServiceA(input)
.flatMap { responseA -> callServiceB(responseA) }
.map { responseB -> HttpResponse(200, responseB.capitalize()) }
.mapFailure { it.toErrorResponse() }
.get()
so your callServiceA
might fail with an Error of ValidationFailure("please provide a valid id")
and callServiceB
might fail with an Error of Unauthorised("you don't exist")
, which then get converted to the appropriate http response in the mapFailure
queen-of-idleness
09/09/2020, 6:39 AM