对Akka Future的理解(二)

来源:互联网 发布:adobeillustrator mac 编辑:程序博客网 时间:2024/06/14 18:29

《Akka in Action v13》对Future描述如下:

In this chapter

  • Futures
  • Composing Futures
  • Recovering from Errors inside Futures
  • Futures andActors
In this chapter we're going to introduce Futures. The Future type that is explained in this chapter was initially part of the Akka toolkit. Since Scala2.10 it is part of the standard Scala distribution. Like Actors, Futures are animportant asynchronous building block in the Akka toolkit. Both Actors and Futures aregreat tools best used for different use cases. It's simply a case of the right toolfor the right job. We will start with describing the type of use case that Futures are best suited for and work through some examples. Where Actors provide a mechanism to build a system out of concurrent , Futures provide a mechanism to build aobjects system out of concurrent functions.

Futures make it possible to combine the results of functions without ever blocking or waiting. Exactly how you can achieve this will become clear in this chapter. We will focus on showing you examples of how to best use Futures instead of diving into the type system details that make many of the featuresof the Future type possible.

You don't have to choose for Futures or Actors, they can be used together.Akka provides common Actor and Future patterns which make it easy to work with both. You will learn about the pitfalls that you need to be aware of when using them together in the last section of this chapter.
5.1 The use case for Futures
In the chapters so far we've learned a lotabout Actors. To contrast the best use cases for futures we will briefly think about use cases that be implementedcan with Actors, but not without unwanted complexity.
Actors are great for processing many messages, capturing state and reacting with different behavior based on the messages they receive. They are resilientobjects that can live on for a long time even when problems occur, using monitoring and supervision.
Futures are the tool to use when you would rather use and don't really functionsneed objects to do the job. A is a placeholder for a function result that willFuture be available at some point in the future. It is effectively an asynchronous result.It gives us a way to reference the result that will eventually become available.The below figure shows the concept:
Figure 5.1 A placeholder for an asynchronousfunction result
A Future is a read-only placeholder. It cannot be changed from the outside. A Future will contain a successful result or a failure once the asynchronousfunction is completed. After completion the result inside the Future cannot change and can be read many times, it will always give the same result. Having a placeholderfor the result makes it easier to combine many functions that are executed asynchronously. It gives us a way to for instance call a web service without blocking the current thread and process the response at a later time.
NOTEThis is not POJF (Plain Old Java Future) To prevent any confusion,if you are familiar with the java.util.concurrent.Future class in Java 7 youmight think that the scala.concurrent.Future discussed in this chapter is justa Scala wrapper around this Java class. This is not the case. Thejava.util.concurrent.Future class requires polling and only provides a way toget to the result with a blocking get method, while the Scala Future makes itpossible to combine function results without blocking or polling as you willlearn in this chapter.
To make the example more concrete we're goingto look at another use case for the ticket system. We would like to create a web page with extra information about the event and the venue. The ticket would simply link to this web page so that customers can access it from their mobile device for instance. We might want to show a weather forecast for the venue when it is on open air event, route planning to the event around the time of the event (should I take public transport ordrive by car?), where to park, or show suggestions for similar future events that the customer might be interested in. Futures are especially handy for , wherepipelining one function provides the input for a next function. The TicketInfo servicewill find related information for an event based on the ticket number. In all these cases the services that provide a part of the information might be down and we don't want to block on every service request while aggregating the information. If servicesdo not respond in time or fail, their information should just not be shown. To be able to show the route to the event we will first need to find the event using the ticket number, which is shown in the below figure.
Figure 5.2 Chain asynchronous functions
In this case getEvent getTraffic and are bothfunctions that do asynchronous web service calls, executed one after the other. The getTrafficInfo web service call takes an argument. Event getTrafficInfo is called the moment the event becomes available in the Future[Event] result. This is very different from calling the getEvent method and  polling and waiting for the event on the current thread. We simplydefine a flow and the getTrafficInfo functionwill be called ,eventually without polling or waiting on a thread. The functions execute as soon as they can.Limiting the amount of waiting threads is obviouslya good thing because the
should instead be doing something useful.
The below figure shows a simple example where calling services asynchronously is ideal. It shows a mobile device calling the TicketInfo service which in the below case aggregates information from a weather and traffic service:
Figure 5.3 Aggregating results, sync vs async
Not having to wait for the weather servicebefore calling the traffic service decreases the latency of the mobile device request. The more services need to be called the more dramatic the effect on latency will be since the responses can be processed in parallel. The next figure shows another use case. In this case we would like the fastest result of two competing weather services:
Figure 5.4 Respond with the fastest result
Maybe weather service X is malfunctioning andtimes out on the request. In that case you would not want to wait for this timeout but rather use the fast response of weather service Y which is working as expected.
It's not as if these scenarios are impossible to execute with Actors. It's just that we would have to do a lot of work for such a simple use case. Take the example of aggregating weather and traffic information. Actors have to be created,messages defined, receive functions implemented, as part of an ActorSystem. You would have to think about how to handle timeouts, when to stop the actors and how to create new actors for every web page request and combine the responses. The below figure shows how Actors could be used to do this.
Figure 5.5 Combine web service requests with Actors
We need two separate actors for the weatherand traffic web service calls so that they can be called in parallel. How the web service calls are combined will need to be coded in the TicketInfo Actor for every specific case. That's a lot of work for just calling two web services and combining the results. Note however that actors are a better choice when fine-grained control over state is required or when actions need to be monitored or possibly retried.
So although actors are a great tool they are not the 'be all and end all' on our quest to never block again. In this case a tool specifically made for combining function results would be a lot simpler.
There are some variations on the above use case where futures are the best tool for the job. In general the use cases have one or more of the following characteristics:

  • You do not want to block (wait on the current thread) to handlethe result of a function.
  • Call a function once-off and handle the result atsome point in the future.
  • Combine many once-off functions and combine theresults.
  • Call many competing functions and only use some of the results, forinstance only the fastest response.
  • Call a function and return a default resultwhen the function throws an exception so the flow can continue.
  • Pipeline these kind offunctions, where one function depends on or more results of other functions.

In the next sections we're going to look atthe details of implementing TicketInfo service with futures. We will start with just calling one webservice asynchronously, after that we will combine many services in a pipeline of futures and we'll look at error handling.

5.2 In the Future nobody blocks
It's time to build the TicketInfoService and we're not explicitly going to siton any thread waiting idly by. Unit testing is the only case where blocking on a future would be valid in our opinion because it can simplify validating results. And even then delaying any blocking all the way to the end of the test case is preferred.We're going to start with the TicketInfo service and try to execute the two steps in the below figure so that we can provide traffic information about the route to the event.
Figure 5.6 Get traffic information about theevent
The first step is to get the event for theticket number. The big difference between calling a function synchronously and asynchronously is the flow in which you define your program. The below listing shows an example of a synchronous web service call to get the event for the ticket number.

val request = EventRequest(ticketNr)val response:EventResponse = callEventService(request)val event:Event = response.event

  • Create the request
  • Block main thread until the response iscompleted
  • Read the event value

The figure shows 3 lines of code executed onthe main thread. The flow is very simple, a function is called and its return value is immediately accessible on the same thread. The program obviously can'tcontinue on the same thread before the value is accessible. Scala expressions are by default strict and the above code does not use any lazy evaluation. So every line in the above code has to produce a complete value.
Lets see what we need to change to this synchronous web service call into an asynchronous one. In the above case the callEventService is a blocking call to a web service, it needs to wait on a thread for the response. We'll first wrap the callEventService into a code block that is executed on a separate thread.The below figure shows the change in the code:

val request = EventRequest(ticketNr)val futureEvent:Future[Event] = future {val response = callEventService(request)response.event}

  • Call code block asynchronously and return a Future Event result
  • this is run on a separate thread.
  • The event in the response can be accessed onthe separate thread.

This asynchronous flow makes a smalladjustment, it runs the callEventService in a separate thread. The separate thread is blocked by the callEventService, something that will be fixed later. The future { ...} is shorthand for a call to the apply method on the Future object with the code block as its only argument, Future.apply(codeblock). This method is in the scala.concurrent package object so you only need to import scala.concurrent._ to use it. It is a helper function to asynchronously call a 'code block' and get back a Future[T], in this case a Future[Event], because we need the Event for the next call to get traffic information. In case you are new to Scala, the last expression in a block is automatically the return value. The Future.apply method ensures that the last expression is turned into a Future[Event]. The type of the futureEvent value is explicitly defined for this example but can be omitted because of type inference in Scala.
The above code block is actually a . A Closure is a special type of
function that can use values from itsenclosing scope. You don't have to do anything special in Scala to create a closure, but it is important to note that this is a special type of function. The code block refers to the value from the request other thread, which is how we bridge between the main thread and the other thread and pass the request to the web service call.
Great, the web service is now called on a separate thread and we could handle the response right there. Lets see how we can chain the call to callTrafficService to get the traffic information for the event in the listing below. As a first step we will print the route to the event to the console:
Listing5.3 Handling the event result

futureEvent.foreach { event =>val trafficRequest = TrafficRequest(destination = event.location,arrivalTime = event.time)val trafficResponse = callTrafficService(trafficRequest)println(trafficResponse.route)}

  • Asynchronously process the event result whenit becomes available.
  •  Call the traffic service with a request based on theevent.
  •  Print the route to the console.

The above listing uses the method on , whichcalls the code foreach Future block with the event result when it becomes available. The code block is only called when the callEventService is successful.
In this case we are expecting to use the Route later on as well, so it would be better if we could return a Future[Route] foreach . The method returns Unit so we'll have to use something else. The below listing shows how this is done with the method.
Listing5.4 Chaining the event result

val futureRoute:Future[Route] =futureEvent.map { event =>val trafficRequest = TrafficRequest(destination = event.location,arrivalTime = event.time)val trafficResponse = callTrafficService(trafficRequest)trafficResponse.route}

  • handle the event and return a Future[Route]
  • return the value to the map function which turns it into a Future[Route].

Both foreach and map should be familiar to you from using the scala.collectionslibrary and standard types like Option and Either.Conceptually the Future.map method is very similar to for instance Option.map. Where the Option.map method calls a code block if it contains some value and returns a new Option[T] value, the Future.map method eventually calls a code block when it contains a successful result and returns a new Future[T] value. In this case aFuture[Route] because the last line in the code block returns a value. Once again the type of Route futureRoute is explicitly defined which can be omitted. The below code shows how you can chain both web service calls directly.
Listing5.5 getRoute method with Future[Route] result

val request = EventRequest(ticketNr)val futureRoute = future {callEventService(request).event}.map { event =>val trafficRequest = TrafficRequest(destination = event.location,arrivalTime = event.time)callTrafficService(trafficRequest).route}

  • Chain on theFuture[Event]
  • Return the route

If we refactor into a getEventmethod that takes a ticketNr and a getRoute method that takes a event argument the code in the following listing would chain the two calls. The methods getEventand getRoute respectively return a Future[Event]and Future[Route] .
Listing5.6 Refactored version

val futureRoute = getEvent(ticketNr).map {event =>getRoute(event)}
The callEventServiceand callTrafficService methods in the previous examples where blocking calls to show the transition from asynchronous to an asynchronous call. To really benefit from the asynchronous style the above getEventand getRoute should be implemented with a non-blocking I/O API and return futures directly to minimize the amount of blocking threads. An example of such an API for HTTP is the spray-client library which is built on top of Akka actors and the Java NIO library which makes use of low-level OS facilities likeselectors and channels. In the next sections you can assume that the web service calls are implemented with spray-client, which will be covered
in the REST chapter [TODO correct reference].
A detail that has been omitted so far is that you need to provide an implicitExecutionContext to use futures. If you do not provide this your code will not compile. The below snippet shows how you can import an implicit value for the global execution context.
Listing5.7 Handling the event result

import scala.concurrent.Implicits.global

  • Use the global ExecutionContext

The ExecutionContext is an abstraction forexecuting tasks on some thread pooling implementation. If you are familiar with the java.util.concurrent package, it can be compared to a java.util.concurrent.Executor interface with extras. The global ExecutionContext uses a ForkJoinPool if it is allowed according to security policy and otherwise falls back to aThreadPoolExecutor. In the section Futuresand Actors we will see that the dispatcher of an actor system can be used as an ExecutionContext as well.
So far we've only looked at chaining successful function results. The next section is going to show you how you can recover from error results.

5.3 Futuristic Errors
The future results in the previous section were expected to always succeed.Lets look at what happens if an Exception is thrown during the asynchronous execution. Start up a scala REPL session on the command line and follow along with the below listing:
Listing5.8 Throwing an exception from the Future

scala> :paste// Entering paste mode (ctrl-D to finish)import scala.concurrent._import ExecutionContext.Implicits.globalval futureFail = future { throw new Exception("error!")}futureFail.foreach(value=> println(value))// Exiting paste mode, now interpreting.futureFail: scala.concurrent.Future[Nothing] =scala.concurrent.impl.Promise$DefaultPromise@193cd8e1scala>

  • Try to print the valueonce the Future has completed
  • Nothing gets printed since an Exception occurred

The above listing throws an Exception fromanother thread. The first thing you notice is that you do not see a stacktrace in the console which you would have seen if the exception was thrown directly. The block is not executed. This is foreach because the future is not completed with a successful value. One of the ways to get to the failure value is to use the onComplete method. This method also takes afunction like and but in this case itprovides a foreach map scala.util.Try value to the function. The Try can be a or a value. The below Success Failure REPL session shows how it can be used to print the exception.
Listing5.9 Using onComplete to handle Success and Failure

scala> :paste// Entering paste mode (ctrl-D to finish)import scala.util._import scala.util.control.NonFatalimport scala.concurrent._import ExecutionContext.Implicits.globalval futureFail = future { throw new Exception("error!")}futureFail.onComplete {case Success(value) => println(value)case Failure(NonFatal(e)) => println(e)}// Exiting paste mode, now interpreting.java.lang.Exception: error!

  • Import statement for Try,Success and Failure
  • It is good practice to only catch non-fatal errors
  • Print the successful value
  •  Print the non-fatal exception
  • The exception is printed

The onComplete method makes it possible tohandle the success or failure result. Take note in the above example that the onComplete callback is executed even if the future has already finished, which is quite possible in the above case since a exception is directly thrown in the future block. This is true for all functions that are registered on a future.
The onComplete method returns so we cannot chain to a next Unit function. Similarly there is a onFailure method which makes it possible to match exceptions. onFailure also returns so we can't use it for further Unit chaining. The below listing shows the use of onFailure.
Listing5.10 Using onFailure to match on all non-fatal Exceptions

futureFail.onFailure {case NonFatal(e) => println(e)}

  • Called whenthe function has failed
  • Match on all non-fatal exception types

We'll need to be able to continueaccumulating information in the TicketInfo service when exceptions occur. The TicketInfo service aggregates information about the event and should be able to leave out parts of the information if the required service threw an exception. The below figure shows how the information around the event is going to be accumulated in a TicketInfo class for a part of the flow of the TicketInfo service.
Figure 5.7 Accumulate information about theevent in the TicketInfo class
The getEvent getTraffic and methods aremodified to return a TicketInfo value (inside a Future) which will be used to accumulate information further down the chain. The TicketInfo class is a simple case class that contains optional values for the service results. The below listing shows the TicketInfo case class. In the next sections we'll add more information to this class like a weather forecast and suggestions for other events.
Listing5.11 TicketInfo case class

case class TicketInfo(ticketNr:String,event:Option[Event]=None,route:Option[Route]=None)

  • All extra information about the ticketNr is optional and defaultempty

It's important to note that you should alwaysuse immutable data structures when working with futures. Otherwise it would be possible to share mutable state between futures that possibly use the same objects. We're safe here since we're using case classes and Options which are immutable. When a service call fails the chain should continue with the TicketInfo that it had accumulated so far. The below figure shows how a failed GetTraffic call should be handled.
Figure 5.8 Ignore failed service response
The method can be used to achieve this. Thismethod makes it recover possible to define what result should be returned when exceptions occur. The below snippet shows how it can be used to return the input TicketInfo when a TrafficServiceException is thrown.
Listing5.12 Using recover to continue with an alternative Future result

val futureStep1 = getEvent(ticketNr)val futureStep2 = futureStep1.flatMap { ticketInfo =>getTraffic(ticketInfo).recover {case e:TrafficServiceException => ticketInfo}}

  • Get event returns a Future[TicketInfo]
  • flatMap is used so we can directly return a Future[TicketInfo] instead of a TicketInfo value from the code block.
  • getTraffic returns a Future[TicketInfo]. recover with a Future containing the initial TicketInfo value.

The recover method above defines that when a TrafficServiceException occurs that it should return the original ticketInfo that it received as an argument. The get Traffic method normaly creates a copy of the TicketInfo value with the route added to it. In the above example we used instead of on the future returned by getEvent. In the code flatMap map block passed to you would need to return a TicketInfo value which will be map wrapped in a new Future. With you need to return a flatMap Future[TicketInfo]  directly. Since getTraffic already returns a Future[TicketInfo] it is easier to use  flatMap.

Similarly there is a recoverWith method where the code block would need to return a Future[TicketInfo], instead of a TicketInfo value in the case of the method in this example. Be aware that the code block passed to recover the method call is executed recover synchronously after the error hasbeen returned.
In the above code there is still a problem left. What will happen if the first getEvent call fails? The code block in the flatMap call will not be called because futureStep1 is a failed Future, so there is no value to chain the next call on. The futureStep2 value will be exactly the same as futureStep1, a failed future result. If we wanted to return an empty TicketInfo containingonly the ticketNr we could recover for the first step as well which is shown in the below listing.
Listing5.13 Using recover to return an empty TicketInfo if getEvent failed

val futureStep1 = getEvent(ticketNr)val futureStep2 = futureStep1.flatMap { ticketInfo =>getTraffic(ticketInfo).recover {case e:TrafficServiceException => ticketInfo}}.recover {case NonFatal(e) => TicketInfo(ticketNr)}

  • Return an empty TicketInfo which only contains the ticketNr in case getEvent failed.

The code block in the call will not be executed. The flatMap will flatMap simply return a failed future result. The last recover call in the above listing turns this failed Future into a Future[TicketInfo] if the Future contains a non fatal Throwable. Now that you've learned how you can recover from errors in a chain of futures we're going to look at more ways to combine futures for theTicketInfo service.

5.4 Combining Futures
In the previous sections you were introduced to and to chain map flatMap asynchronous functions with futures. In this section we're going to look at more ways to combine asynchronous functions with futures. Both the Future[T] trait and theFuture object provide combinator methods like flatMap and map to combine futures. These combinator methods are very similar to flatMap,map ant others found in the Scala Collections API. They make it possible to create pipelines of transformations from one immutable collection to the next, solving a problem step by step. In this section we will only scratch the surface of the possibilities of combining futures in a functional style. If you would like to know more aboutFunctional Programming in Scala we recommend Functional Programming in Scala by Paul Chiusano and Rúnar Bjarnason.
The TicketInfo service needs to combine several web service calls to provide the additional information. We will use the combinator methods to add information to the TicketInfo step by step using functions which take a TicketInfo and return a Future[TicketInfo]. At every step a copy of the TicketInfo case class is made which is passed on to the next function, eventually building a
complete TicketInfo value. The TicketInfo case class has been updated and is shown in the below listing, including the other case classes that areused in the service.
Listing5.14 Improved TicketInfo class

case class TicketInfo(ticketNr:String,userLocation:Location,event:Option[Event]=None,travelAdvice:Option[TravelAdvice]=None,weather:Option[Weather]=None,suggestions:Seq[Event]=Seq())case class Event(name:String,location:Location,time:DateTime)case class Weather(temperature:Int, precipitation:Boolean)case class RouteByCar(route:String,timeToLeave:DateTime,origin:Location,destination:Location,estimatedDuration:Duration,trafficJamTime:Duration)case class TravelAdvice(routeByCar:Option[RouteByCar]=None,publicTransportAdvice: Option[PublicTransportAdvice]=None)case class PublicTransportAdvice(advice:String,timeToLeave:DateTime,origin:Location, destination:Location,estimatedDuration:Duration)case class Location(lat:Double, lon:Double)case class Artist(name:String, calendarUri:String)

  • The TicketInfo case class collects traveladvice, weather and event suggestions.
  • To keep things simple in this examplethe route is just a string.
  • To keep things simple in this example the advice isjust a string.

All items are optional except the ticketnumber and the location of the user.Every step in the flow will add some information by copying the argument TicketInfo and modifying properties in the new TicketInfo value, passing it to the next function. The associated information will be left empty if a service call can not be completed, as we've shown in the section on futuristic errors. The below figureshows the flow of asynchronous web service calls and the combinators that we will use in this example:
Figure 5.9 TicketInfoService Flow
The combinators are shown as diamonds in theabove figure. We will look at every combinator in more detail. The flow starts with a ticketNr and a GPS location of the user of the ticket info service and eventually completes a TicketInfo future result. The fastest response of the weather services is used.Public transport and car route  information are combined in a TravelAdvice. At the same time similar artists are retrieved and the calendar for each is Artist requested. This results in suggestions for similar events. All futures are eventually combined into a Future[TicketInfo]. Eventually this final Future[TicketInfo] will have an onComplete callback that completes the
HTTP request with a response back to the client, which we will omit in these examples.
We'll start with combining the weather services. The TicketInfo service needs to call out to many weather services in parallel and use the quickest response.The below figure shows the combinators used in the flow.
Figure 5.10 Weather flow
Both weather services return aFuture[Weather], which needs to be turned into a Future[TicketInfo] for the next step. If one of the weather services is not responsive we can still inform the client about the weather with the response of the other service. The below listing shows how the Future.firstCompletedOf method is used in the TicketInfoService flow to respond to the first completed service:
Listing5.15 Using firstCompletedOf to get the fastest response

def getWeather(ticketInfo:TicketInfo):Future[TicketInfo] = {val futureWeatherX = callWeatherXService(ticketInfo).recover(withNone)val futureWeatherY = callWeatherYService(ticketInfo).recover(withNone)val futures = Seq(futureWeatherX, futureWeatherY)val fastestResponse = Future.firstCompletedOf(futures)fastestResponse.map{ weatherResponse =>ticketInfo.copy(weather = weatherResponse)}}

  •  The error recovery is extracted out into a withNone function omitted here. It simplyrecovers with a None value.
  • The first completed Future[Weather].
  •  Copy theweather response into a new ticketInfo. return the copy as the result of themap code block.
  • the map code block transforms the completed Weather value intoTicketInfo, resulting in a Future[TicketInfo].

First two futures are created for the weathe rservice requests. The Future.firstCompletedOf function creates a new Future out of the two provided weather service future results. It is important to note that firstCompletedOf returns the first future. A Future is completed completed with a successful value or a failure. With the above code the ticketInfoservice will not be able to add weather information when for instance the WeatherX service fails faster than the WeatherY service can return a correct result. For nowthis will do since we will assume that a non-responsive service or a worse performing service will respond slower than a correct functioning service. We'll see laterthat a firstSucceededOf method is not too difficult to write ourselves. [We will getback to this once we explain Promises and implement our own firstSucceededOf
method. [TODO this explanation forces us to explain Promises and get deeper,implementing our own firstSucceededOf not sure if this goes too far.]]
The public transport and car route service need to be processed in parallel and combined into a TravelAdvice when both results are available. The below figure shows the combinators used in the flow to add the travel advice.
Figure 5.11 Travel advice flow
getTraffic getPublicTransport and return two different types inside a future, respectively RouteByCarand PublicTransportAdvice . These two values are first put together in a tuple value. The tuple is then mapped into a TravelAdvice TravelAdvice value. The class is shown in the below listing.
Listing5.16 TravelAdvice class

case classTravelAdvice(routeByCar:Option[RouteByCar] = None,publicTransportAdvice: Option[PublicTransportAdvice] = None)

Based on this information the user can decideto travel by car or by public transport. The below listing shows how thezip combinator can be used for this.
Listing5.17 Using zip and map to combine route and public transport advice

def getTravelAdvice(info:TicketInfo,event:Event):Future[TicketInfo] = {val futureR = callTraffic(info.userLocation,event.location,event.time).recover(withNone)val futureP = callPublicTransport(info.userLocation,event.location,event.time).recover(withNone)futureR.zip(futureP).map {case(routeByCar, publicTransportAdvice) =>val travelAdvice = TravelAdvice(routeByCar,publicTransportAdvice)info.copy(travelAdvice = Some(travelAdvice))}}

  • ZipFuture[RouteByCar] and Future[PublicTransportAdvice] into Future[(RouteByCar,PublicTransportAdvice)].
  • Transform the future route and public transport adviceinto a Future[TicketInfo]

The above code first zips the future publictransport and route by car together into a new Future which contains both results inside a tuple value. It then maps over the combined future and turns the result into a Future[TicketInfo] so it can be chained further down the line. You can use a for-comprehension instead of using the method. This can sometimes lead to more readable code. The map below listing shows how it can be used, it does exactly the same thing as the zip and map in the above listing:

Listing5.18 Using zip and map to combine route and public transport advice

for((route, advice) <- futureRoute.zip(futurePublicTransport);travelAdvice = TravelAdvice(route, advice)) yield info.copy(travelAdvice = Some(travelAdvice))

  • The futurecreated by the zip method evaluates at some point into a routeByCar andpublicTransportAdvice tuple.
  • The for-comprehension yields a TicketInfo, whichis returned as a Future[TicketInfo] from the for comprehension, similar to howthe map method does this.

If you are not too familiar to for-comprehensions, you could think of it as iterating over a collection. In the case of a Future we 'iterate' over acollection which eventually contains one value or nothing (in the case of an exception).
The next part of the flow we'll look at is the suggestion of similar events.Two web services are used; a similar artist service which returns information about artists similar to the one performing at the event. The artist information isused to call a specific calendar service per artist to request the next planned event close to the event location which will be suggested to the user. The below listing shows how the suggestions are built up.
Listing5.19 Using for-comprehension and traverse to map

def getSuggestions(event:Event):Future[Seq[Event]] = {val futureArtists = callSimilarArtistsService(event)for(artists <- futureArtistsevents <- getPlannedEvents(event, artists)) yield events}

  • returns a Future[Seq[Events]], a future list of planned events for every artist.
  • returns a Future[Seq[Artist]], a Future to similar artists.
  •  'artists' evaluates at somepoint to a Seq[Artist].
  •  'events' evaluates at some point to a Seq[Events], aplanned event for every called artist.
  •  The for comprehension returns theSeq[Event] as a Future[Seq[Event]].

The above example is a bit more involved. The code is split up over a couple of methods for clarity although this can obviously be in-lined. The getPlannedEvents is only executed once the artists are available. The getPlannedEvents uses the Future.sequence method to build a Future[Seq[Event]] out of a Seq[Future[Event]] . In other words it combines many futures into one single future which contains a list of the results.The code for getPlannedEvents is shown in the below listing.
Listing5.20 Using sequence to combine many Future[[Event]] into one Future[Seq[Event]]

def getPlannedEvents(event:Event,artists:Seq[Artist]) = {val events = artists.map { artist=>callArtistCalendarService(artist, event.location)}Future.sequence(events)}

  • returns a Future[Seq[Event]], a list of planned events, one forevery similar artist.
  • map over the Seq[Artists]. for every artist call thecalendar service. the 'events' value is a Seq[Future[Event]].
  • Turns the Seq[Future[Event]] into a Future[Seq[Event]]. It eventually returns a list of events when the results of all the asynchronous call ArtistCalendarService calls are completed.

The sequence method is a simpler version of thetraverse method. The below example shows how getPlannedEvent looks when we use traverse instead.
Listing5.21 Using traverse to combine many Future[[Event]] into one Future[Seq[Event]]

def getPlannedEventsWithTraverse(event:Event, artists:Seq[Artist]) = {Future.traverse(artists) { artist=>callArtistCalendarService(artist, event.location)}}

  • traverse takes a code block which is required to return a Future.It allows you to traverse a collection and at the same time create the future results.

Using sequence we first had to create a Seq[Future[Event]] so we could transform it into a Future[Seq[Event]]. With traverse we can do the same but without the intermediate step of first creating a Seq[Future[Event]].
It's time for the last step in the TicketInfoService flow. The TicketInfo value which contains the information needs to be combined  Weather  with theTicketInfo containing the TravelAdvice. We're going to use thefold method
to combine to TicketInfo values into one. Thebelow listing shows how it is used:
Listing5.22 Using fold to combine two Future[[Event]] into one Future[Seq[Event]]

val ticketInfos = Seq(infoWithTravelAdvice,infoWithWeather)val infoWithTravelAndWeather = Future.fold(ticketInfos)(info) {(acc, elem) =>val (travelAdvice, weather) = (elem.travelAdvice, elem.weather)acc.copy(travelAdvice = travelAdvice.orElse(acc.travelAdvice),weather = weather.orElse(acc.weather))}

  • create a list of the TicketInfo containing the travel advice andthe TicketInfo containing the weather.
  • fold is called with the list and theaccumulator is initialized with the ticketInfo that only contains the eventinformation.
  • Fold returns the result of the previously executed code block inthe accumulator ('acc') value. it passes every element to the code block, inthis case every TicketInfo value.
  • extract the optional travelAdvice and weatherproperties out of the ticketInfo.
  • copy the travelAdvice or the weather into theaccumulated TicketInfo, whichever is filled. the copy is returned as the nextvalue of 'acc' for the next invocation of the code block.

The fold method works just likefold on data structures like Seq[T] andList[T] which you are probably familiar with. It is often used instead of traditional for loops to build up some data structure through iterating over a collection.foldtakes a collection, an initial value and a code block. The code block is fired for every element in the collection.The block takes two arguments, a value to accumulate state in and the element in the collection that is next. In the above case the initial TicketInfo value is used as the initial value. At every iteration of the code block a copy of the TicketInfo is returned that contains more information, based on the elements in the ticketInfos list.
The complete flow is shown in the below listing:
Listing5.23 Complete TicketInfoService flow

def getTicketInfo(ticketNr:String,location:Location):Future[TicketInfo] = {val emptyTicketInfo = TicketInfo(ticketNr, location)val eventInfo = getEvent(ticketNr, location).recover(withPrevious(emptyTicketInfo))eventInfo.flatMap { info =>val infoWithWeather = getWeather(info)val infoWithTravelAdvice = info.event.map { event =>getTravelAdvice(info, event)}.getOrElse(eventInfo)val suggestedEvents = info.event.map { event =>getSuggestions(event)}.getOrElse(Future.successful(Seq()))val ticketInfos = Seq(infoWithTravelAdvice, infoWithWeather)val infoWithTravelAndWeather = Future.fold(ticketInfos)(info) {(acc, elem) =>val (travelAdvice, weather) = (elem.travelAdvice, elem.weather)acc.copy(travelAdvice = travelAdvice.orElse(acc.travelAdvice),weather = weather.orElse(acc.weather))}for(info <- infoWithTravelAndWeather;suggestions <- suggestedEvents) yield info.copy(suggestions = suggestions)}}// error recovery functions to minimize copy/pastetype Recovery[T] = PartialFunction[Throwable,T]// recover with Nonedef withNone[T]:Recovery[Option[T]] = { case NonFatal(e) => None }// recover with empty sequencedef withEmptySeq[T]:Recovery[Seq[T]] = { case NonFatal(e) => Seq() }// recover with the ticketInfo that was built in the previous stepdef withPrevious(previous:TicketInfo):Recovery[TicketInfo] = {case NonFatal(e) => previous}

  • First call getEvent whichreturns a Future[TicketInfo]
  • create a TicketInfo with Weather information
  • create a TicketInfo with TravelAdvice information
  • get a future list ofsuggested events
  • combine weather and travel into one TicketInfo
  • Eventually addthe suggestions as well.
  • Error recovery methods that are used in theTicketInfoService flow.

That concludes the TicketInfoService exampleusing futures. As you have seen, futures can be combined in many ways and the combinator methods make it very easy to transform and sequence asynchronous function results. The entire TicketInfoService flow does not make one blocking call. If the calls to the hypothetical web services would be implemented with an asynchronous HTTP client like the library the amount of blocking threads would be kept tospray-client a minimum for I/O as well. At the time of writing this book more and more asynchronous client libraries in Scala for I/O but also for database access are written that provide Future results.
In the next section we're going to look at how futures can be combined with Actors.

5.5 Futures and Actors
In the Up and Running chapter we used for our first REST service whichSpray uses Actors for HTTP request handling. This chapter already showed that the ask method returns a Future. The below example was given:
Listing5.24 Collecting event information

import akka.pattern.askimport context._implicit val timeout = Timeout(5 seconds)val capturedSender = senderdef askEvent(ticketSeller:ActorRef): Future[Event] = {val futureInt = ticketSeller.ask(GetEvents).mapTo[Int]futureInt.map { nrOfTickets =>Event(ticketSeller.actorRef.path.name, nrOfTickets)}}val futures = context.children.map {ticketSeller => askEvent(ticketSeller)}Future.sequence(futures).map {events => capturedSender ! Events(events.toList)}

  • Import the ask pattern, which adds the askmethod to ActorRef.
  • The context contains an implicit def of the dispatcher ofthe actor. Importing the context imports the dispatcher of this actor as theexecution context in implicit scope which will be used for the futures.
  • A timeout needs to be defined for ask. If the ask does not complete within the timeout the future will contain a timeout exception.
  • Capture the contextual senderreference into a value.
  • A local method definition for asking GetEvents to aTicketSeller.
  • The ask method returns a Future result. Because Actors can sendback any message the returned Future is not typed. we use the mapTo method toconvert the Future[Any] to a Future[Int]. If the actor responds with adifferent message than an Int the mapTo will complete with a failed Future.
  • Send back information to the captured sender. The captured sender is the senderof the original GetEvents request which should receive the response.
  • Ask all the children how many tickets they have left for the event.

This example should be more clear now than itwas in chapter two. To reiterate, the example shows how the boxOffice actor can collect the number of tickets that
every ticket seller has left.
The above example shows a couple of important details. First of all we capturethe sender reference into a value. This is necessary because the sender is part of the
actor context, which can differ at every message the actor receives. The sender of the message can obviously be different for every message the actor receives.Since
the future callback is a Closure it closesover  the values it needs to use. The sender could have a completely different value at the time the callback is invoked. This is why the sender is captured into a capturedSender value, which makes it possible to respond to the originator of the GetEvents request.
So be aware when using futures from Actors that the ActorContext provides a current view of the Actor. And since actors are stateful it is important tomake sure that the values that you close over are not mutable from another thread. The easiest way to prevent this problem is to use immutable data structures and capture the reference to the immutable data structure before closing over it from a future,as shown above in the capturedSender example.
Another pattern that is used in the Up and Running example is pipeTo. The below listing shows an example of its use:
Listing5.25

import akka.pattern.pipepath("events") {get { requestContext =>val responder = createResponder(requestContext)boxOffice.ask(GetEvents).pipeTo(responder)}}

  • Imports the pipe pattern
  • Create an actor for every request thatcompletes the HTTP request with a response
  • The Future result of the ask ispiped to the responder actor

The RestInterface uses a per-request actor tohandle the HTTP request and provide a HTTP response. The /events URL is translated to a call to the boxOffice to collect the amount of tickets left per event. The result of the request to the boxOffice is a Future[Events] Events . The value that that future will eventually contain is piped to the responder actor when it becomes available.The responder actor completes the request when it receives this message, as is shown in below listing.
Listing5.26

class Responder(requestContext:RequestContext,ticketMaster:ActorRef)extends Actor with ActorLogging {def receive = {case Events(events) =>requestContext.complete(StatusCodes.OK, events)self ! PoisonPill// other messages omitted..}}
The method is useful when you want an actor to handle the result of a pipeTo Future, or when the result of asking an actor needs to be handled by another actor.

5.6 Summary
This chapter gave an introduction on futures. You have learnt how to use futures to create a flow out of asynchronous functions. The goal has been to minimize
explicitly blocking and waiting on threads, maximize resource usage and minimize unnecessary latency.
A Future is a placeholder for a function result that will eventually be available. It is a great tool for combining functions into asynchronous flows. futures make it possible to define transformations from one result to the next. Since futuresare all about function results it's no surprise that a functional approach needs to be taken to combine these results.
The combinator methods for futures provide a 'transformational style' similar to the combinators found in the scala collections library. Functions are executed in parallel and where needed in sequence, eventually providing a meaningful result. A Future can contain a successful value or a failure. Luckily failures can be recovered with a replacement value to continue the flow.
The value contained in a Future should be immutable to be sure that no accidental mutable state is shared. Futures can be used from Actors, but you need to be careful with what state you close over. The sender reference of an actor needs to be captured into a value before it can besafely used for instance. Futures are used in the Actor API as the response of an method. Future results can also be ask provided to an actor with thepipeTo method. Now that you know about futures we're going back to Actors in the next chapter. This time we'll scale the goticks.com app with Remote Actors.

原创粉丝点击