Akka Streams is a powerful implementation on top of the Reactive Streams SPI for non-blocking asynchronous communication with back-pressure on the JVM. This post is not about explaining what this means, nor what Akka Streams does. The purpose of this post is to explain what on earth the
NotUsed type in type signatures of Akka Streams is all about which — incidentally — also means explaining a fundamental design aspect of Akka Streams.
To understand the meaning of
NotUsed we need to understand that Akka Streams are hybrid beasts that have two different concepts of value: the value of what “flows” in the stream, and the value of what is produced and visible outside of the stream. See, an Akka Stream is only going to run if it is closed, which is to say that it has a beginning and and end (or a
Source and a
Sink), and therefore nothing outside the Flow can peek into it. That’s fine if all you do is to carry data from point A to point B, but if by any chance you’d like a stream to produce a value that you’d like to look at, you won’t be able to do so since you can’t touch this.
Let’s look at an example (behold my awesome drawing skills):
val source = Source(List("a", "b", "c"))
val sink = Sink.fold[String, String]("")(_ + _)
val runnable: RunnableGraph[Future[String]] = source.toMat(sink)(Keep.right)
val result: Future[String] = runnable.run()
Now unless you already know Akka Streams, I’m pretty sure I’ve lost you, which is exactly what I wanted so that you can understand what
NotUsed means. So we have a
Source that will emit “a”, “b” and then “c”. Next we have a
Sink that consumes strings and combines them. We combine both of these, creating a
RunnableGraph – it is called like this because there’s both a beginning and an ending, the graph is closed, and hence we can run it. What’s probably very confusing at first is the following line:
Here’s the catch: not only do we want our letters to flow from the source to the sink, but we also would like to retrieve the result of what the sink produces (the concatenation of “a”, “b” and “c”). In Akka slang, that’s materialization. And in order to materialize something we need a materializer. So when we say
source.toMat(sink) what we really say is “connect source to sink which is going to produce a value out of it that can be accessed from the outside by mere mortals”. “Okay, great!” you say, “but what’s up with this
Keep.right ?”. And you will be right to do so (pun intended) because that’s not obvious either.
Here’s a piece of information that will help, especially if you’re native in a Right-To-Left language: in Akka Streams, graphs are flowing from the left to the right.
Akka Streams allows to materialize (or, to keep) either the left or the right value of a stream. In this case, we want to keep the right one (what comes out of the sink), which is why we specify
Keep.Right. You could also want to keep what’s on the left-hand side (in this example, it wouldn’t make very much sense, but in other, more complex cases, it totally does). In fact, you should head over here and read this bit of the Akka documentation.
In our example, the materialized value is going to be a
String. But what if we didn’t care about the materialized value? What if we just wanted our flow to run, pushing elements from one place to another? Yes, you guessed it, that’s when we’re using NotUsed as a way of saying “I don’t care about the materialized value”.