I started to use Play 2.5 recently, and I was wondering what the purpose of doing the following was:
@Inject() (implicit val mat: Materializer)
<
A materializer makes actors execute a graph to produce those results.
A graph, in its simplest form, consists of a source that provides elements, and a sink that consumes elements.
Here's a source that provides a range of integers (in this example, integers are our elements):
val source = Source(1 to 10)
And here's a sink that sums all the integers it gets from a source:
val sink = Sink.fold[Int, Int](0)(_ + _)
We connect source and sink to get a graph:
val graph = source.toMat(sink)(Keep.right)
Mind that no computation, addition in our case, is performed when creating a graph. The code is declarative, the graphs describe how we want to transform our data but it is someone else's job to actually perform the computation: Graphs are blueprints.
Now, what about the materializer? The materializer takes action when we run the graph:
implicit val materializer = ActorMaterializer()
val futureResult = graph.run()
When we run()
the graph, the materializer takes the graph and makes actors execute the data transformations specified in the graph; in this example, that's adding integers.
It might be helpful to imagine the graph as a blueprint to build a house, the materializer as the foreman looking at the blueprint, telling the builders how to actually build the house according to the blueprint. The builders in this analogy correspond to actors in Akka.
The reason why several pieces of your code now work, is that with the materializer you provide a way to execute graphs. Graphs are heavily used in Akka HTTP which Play uses for HTTP requests and responses.
The WSClient you mentioned in your comment uses graphs to perform its requests and thus needs a materializer.
Here's a full example of creating and running a graph:
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Keep, Sink, Source}
object Graphs extends App {
// The start of our simple graph. It provides elements, integers in our case
val source = Source(1 to 10)
// The end of our graph. It processes the source's elements
val sink = Sink.fold[Int, Int](0)(_ + _)
/*
* Connect source and sink.
* Keep only the output values (i.e., the graph's right side).
* Note that this is declarative: no result is computed until we run the graph.
*/
val graph = source.toMat(sink)(Keep.right)
// The system coordinates actors and provides threads for them
implicit val actorSystem = ActorSystem()
// The materializer makes actors execute graphs
implicit val materializer = ActorMaterializer()
// Running the graph means that the materializer assigns actors to execute
// the graph from start (source) to end (sink)
val futureResult = graph.run()
// Use the actor system's execution context, which provides threads,
// to print the result of running the graph
implicit val executionContext = actorSystem.dispatcher
futureResult.foreach(res => println(s"Result of running the graph: $res"))
actorSystem.terminate().foreach(_ => println("System is shut down"))
}
Put this in your build.sbt
to make Akka's stream library available in your code:
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.19"
Here's more on sources and sinks.