Service Composition

Service composition helps you create results by combining dependent information from underlying services. You can compose well or poorly, depending on how you manage the underlying services and calls to those services.

A few libraries have been created to help you compose calls to services and combine the results. Generally, the more recent service composition libraries are based on simple ideas such as managing the creation of the request separate from executing the requests. Of course, this is the basic idea behind the IO monad found in haskell and a variety of other languages that allow you to control evaluation. We are also concerned about how to catch and manage error information once errors do occur.

First we can address service composition using fetch. It is interesting to note that the scala async macro helps you compose Future's in simple scenarios that match what fetch does when you use Future's with fetch.

fetch

Fetch composes service calls and combines the results together. Here's the main documentation site. It handles batching, request combining and caching. While there may be better ways to handle caching, it allows you to address caching at the fetch library level versus an infrastructure level.

First, we need to enhance our example-server to serve up some data. You can easily create a contrived customer micro-service with akka-http:

~ pathPrefix("customers") {
        path(IntNumber) { n =>
          get {
            onSuccess(customers.find(n)) {
              case Some(customer) => complete(customer)
              case None => complete(StatusCodes.NotFound)
            }
          }
        } ~ path("posts-from" / IntNumber) { n =>
          get {
            onSuccess(msgs.getMsgsFrom(n)) { m =>
              complete(m)
            }
          }
        } ~ path("posts-to" / IntNumber) { n =>
          get {
            onSuccess(msgs.getMsgsTo(n)) { m =>
              complete(m)
            }
          }
        }
      }
    }

which is powered by some new modules:

This allows us to call the service, say, using curl:

and we add the modules to the main program file. Notice the use of spray json, which makes it easy to convert a string to a JSON object to a scala JVM object.

The last import brings in implicit converts that take the json reader/writers/formats and changes them into marshallers and unmarshallers suitable for use in akka routes. akka has a robust marshaller and unmarshaller framework.

Now we can use fetch to fetch results and combine them together. Since dispatch returns a scala Future, we need to use the Future monad composition in fetch. fetch's user documentation is here.

Since dispatch returns scala Futures, we need to hook this into fetch's asynchronous support:

This allows us to get a customer object using the Fetch API. Personally, I think the fetch API will evolve so as to not be so heavy on objects and traits, but that's another day.

We can now create a fetch object, re-use as much as we want, and get a customer object.

Which prints out:

The result prints after the prompt because we artificially inserted a small delay into the server response on the server side.

Now we can compose our customer fetches together and use a slightly different syntax to run the Fetch object (e.g. .runA[Future] vs Fetch.run[Future]):

to get

So now we can sequence our requests, which we could have done with scala async of course, by using:

which gives us:

To retrieve a list back from the http server, we had to ensure that our result signature in the DataSource reflected a list of objects:

The key is to ensure that you can marshal the result from dispatch to the proper case class type.

Last updated

Was this helpful?