Using Akka and Scala for CQRS and Event Sourcing
In the past, we covered using Lagom to implement Java microservices using the event sourcing and CQRS patterns that framework relies on. Today, we’ll be revisiting our blog microservice example using the Scala programming language and Akka, one of the main components underlying Lagom.
Event Sourcing
First, let’s take a quick review of what event sourcing and CQRS are along with the general design patterns to use to implement these ideas. Event sourcing is an architectural pattern where all activity in a system is captured by immutable event objects, and these events are stored and published in a sequential log. The state of a system at a given time can therefore be derived from the sequence of events prior to the state. For example, most SQL databases are implemented using this sort of architecture where all data mutation operations are stored as events in a write-ahead log. This pattern works very well with the physical storage underlying these devices as they tend to be based on magnetic disks which can work very fast in sequential access but become a lot slower in random access.
Using the concept of event sourcing, Command Query Responsibility Segregation is an architectural pattern that is rather self-describing: the responsibility of a system to handle commands and queries are separated rather than intertwined as in typical applications. Commands are used to update state while queries are used to inspect state. The separation of these two concerns allows for much greater scalability in distributed systems than traditional approaches such as a single backing database for both reading and writing data. While CQRS is not required in order to implement a write-side and read-side pair of databases, it does offer a strong pattern to follow to do so. For example, an application like Twitter may wish to store all tweets in a distributed database such as Cassandra, but in order to effectively search those tweets, it would be useful to copy written data into Elasticsearch for later querying. Provided that we follow an event sourcing pattern, then the source streams of data are well defined and make it much simpler to implement this responsibility segregation.
In CQRS, we can issue a command to a system to update its state. This command, after validation, will create an immutable event which is appended to the event log. After persisting this event, the state of the system can be updated in response. By doing this, the system can always be brought back to any given state by replaying the events in the event log. For performance reasons, the state of a system is periodically snapshotted and persisted so that less events from the main event log need to be replayed when restarting the system. The state obtained from the event log can be as simple as the denormalized representation of entities, or it could be more complex views of the event stream which can be further combined down the line. It’s important to note that following this pattern means we need to manually reimplement some things we take for granted in a normal RDBMS such as transactions, indexing, joins, constraints, and triggers. Ideally, these sorts of things can be abstracted well enough to be provided mostly through libraries or frameworks, but some things such as transactions are still application-specific in how to make compensating transactions to roll back erroneous data.
Actor Model and Akka
The actor model is essentially a model of distributed systems where processes are modeled as “actors”, things that have an inbox for receiving messages that are processed asynchronously. Actors can only communicate with other actors via messages, and this allows actors to be scaled outward into fully distributed systems. A simple way to think of an actor is as a thread that cannot directly access the state of any other thread and must pass immutable message objects to other threads to coordinate anything. This avoids the use of synchronization, locks, and all the extremely difficult to debug concurrency issues that plague typical concurrent code.
Actors are lightweight and can be spawned and restarted many times with very little overhead (far less than an actual thread). Thus, following the ideals of programming languages like Erlang, actors are small enough to allow for a more robust form of error handling known as “let it crash”. Forming a suitable hierarchy of actors, parent actors can automatically restart child actors when errors occur or perform other fallback strategies. This is particularly useful for long-running systems where either a full restart is infeasible or the cost of resources to double up the system for blue green deployments is prohibitive. It may also be the case that recovering the full state of the system would take too long, so being able to selectively restart only single components without affecting the rest of the system is very useful.
Akka is an implementation of this actor model inspired by Erlang. Akka is written in Scala and provides both a Scala and Java API for actors, clustering, persistence, distributed data, reactive streams, HTTP, and integration with various external systems.
All code samples in this post are from my GitHub repository. We’ll be using Scala 2.12 which requires Java 8. Note that if you’re still stuck on Java 6 or 7, Scala 2.10 and 2.11 both only require Java 6. Code samples may be adaptable to previous releases of Scala with little or no modifications. Anyways, let’s jump in to using Scala!
Blog API
Our API to implement today will be the same as in my Lagom tutorial.
As such, we’ll have a blog data type consisting of three fields: title
,
author
, and body
. Blog posts are identified by UUIDs. The REST API will
consist of a GET
, POST
, and PUT
endpoint for looking up, creating, and
updating blog posts respectively. The first thing to implement, then will be
our blog data type.
final case class PostContent(title: String, author: String, body: String)
You may note that there isn’t much to it, and you’re right! Let’s review the
syntax here. First of all, we don’t need to define things as public
in Scala
as they’re all assumed to be public
by default. In fact, public
is not even
a keyword in Scala. After a class name comes its parameters which can be thought
of as the parameters to its constructor. A class can still have multiple
constructors by defining methods named this
within the class, but we have no
need for that here. Parameters and variables are declared in the opposite order
as Java; in Java, we would say String foo
, whereas in Scala we would say
foo: String
. The other keywords used here are final
which makes the class
final as in Java, and case
which makes this a “case class”.
Recall that in Java, we can use Lombok to automatically create an all-args
constructor, getters for all fields, equals
, toString
, and hashCode
,
builders, withers, and other boilerplate code. In Scala, we can simply add
case
to a class to get a lot of that for free. A case class makes all its
class parameters (fields) available with something similar to getters, adds
sensible equals
, hashCode
, and toString
method implementations, and
handles some other Scala-specific features we’ll cover later. Essentially, a
case class can be thought of as a data value class, and we’ll be using it as
such.
Next, we’ll define a post id class instead of directly using UUID
.
class PostId(val id: UUID) extends AnyVal {
override def toString: String = id.toString
}
object PostId {
def apply(): PostId = new PostId(UUID.randomUUID())
def apply(id: UUID): PostId = new PostId(id)
implicit val postIdDecoder: Decoder[PostId] =
Decoder.decodeUUID.map(PostId(_))
implicit val postIdEncoder: Encoder[PostId] =
Encoder.encodeUUID.contramap(_.id)
}
We have several new keywords to discuss here. First, by adding val
to the id
class parameter, this will add a Scala-style getter for id
by providing a way
to access the id
field directly. Classes use the extends
keyword to extend
other classes or implement traits (interfaces). This AnyVal
class is a special
class in Scala that works essentially as a boxed value type. This value class can
only contain a single public field, and at compile time, the compiler will attempt
to remove all indirect access of that field and replace it with direct access.
Thus, we can create a wrapper type for UUID
without sacrificing performance
at runtime.
The override
keyword is used just like the @Override
annotation in Java:
while not required, if the keyword is present but the method doesn’t actually
override anything, this will cause a compiler error. This can be useful for
catching typos or unknown API changes. The def
keyword is used for defining
methods. When a method has zero arguments, the parenthesis are optional. The
conventional style here is that parentheses are omitted in pure functions
while they remain in side-effecting functions. As with variables, the types of
parameters and method return values come afterwards.
The object
keyword here defines what is essentially a singleton instance of a
class of the same name. Scala does not have static
, but these objects provide
an equivalent feature. When an object is named the same as a class, it is
called the class’s “companion object”. A class and its companion object have
equal access to internals of each other similar to how static and non-static
members work in Java.
The apply
methods here are used as factory methods to create PostId
instances. The apply
method has special meaning in Scala: we can omit the
name of the method when calling it. For example, PostId.apply()
is the same
as calling just PostId()
. As can be seen here, the return
keyword is optional
when the last line of executing code of a method is the return value.
The implicit
keyword is used for a lot of things in Scala, and in this case,
an implicit variable is one that is available for implicit parameters to methods
that take them. We’ll be using it here to fill in custom encoder and decoder
implementations for our PostId
class to ensure that the JSON marshaller and
unmarshaller do not think that this is a complex object.
The square bracket syntax, Decoder[PostId]
, is Scala’s generic type parameter
syntax. The equivalent in Java would be Decoder<PostId>
. Finally, these codecs
are derived from existing UUID
codecs, so we map and contramap them using
some lambda functions. Both of these lambda functions use anonymous parameter
syntax, both of which can be expanded to:
Decoder.decodeUUID.map(id => PostId(id))
Encoder.encodeUUID.contramap(postId => postId.id)
Next, let’s stub out a service interface. We’ll fill in the details later.
trait BlogService {
def getPost(id: PostId)
def addPost(content: PostContent)
def updatePost(id: PostId, content: PostContent)
}
The trait
keyword is very similar to an interface
in Java. In fact, as
written, this trait will compile directly into an interface. Traits are far
more powerful than Java interfaces, so this isn’t always possible, but Java
has started making interfaces more powerful starting with default methods in
Java 8, so these two concepts may converge one day. The main difference between
a trait and a class in Scala is that a trait cannot have its own parameters,
but a class can extend (or mix in) multiple traits. So far, this is still
very similar to Java. However, traits can contain fields, private and
protected members, default implementations, and even constraints on what the
concrete implementing class must conform to. We’ll omit the return types of the
methods for now, but we’ll return to add them later once we’ve defined them.
Blog Entity
Next, let’s dive down to the entity level. Recall that in Lagom, a persistence
entity is associated with three related things: commands, events, and state.
Following a similar pattern here, we’ll implement a BlogEntity
class by
first defining our commands, events, and state classes.
object BlogEntity {
sealed trait BlogCommand
final case class GetPost(id: PostId)
extends BlogCommand
final case class AddPost(content: PostContent)
extends BlogCommand
final case class UpdatePost(id: PostId, content: PostContent)
extends BlogCommand
sealed trait BlogEvent {
val id: PostId
val content: PostContent
}
final case class PostAdded(id: PostId, content: PostContent)
extends BlogEvent
final case class PostUpdated(id: PostId, content: PostContent)
extends BlogEvent
final case class PostNotFound(id: PostId)
extends RuntimeException(s"Blog post not found with id $id")
type MaybePost[+A] = Either[PostNotFound, A]
final case class BlogState(posts: Map[PostId, PostContent]) {
def apply(id: PostId): MaybePost[PostContent] =
posts.get(id).toRight(PostNotFound(id))
def +(event: BlogEvent): BlogState =
BlogState(posts.updated(event.id, event.content))
}
object BlogState {
def apply(): BlogState =
BlogState(Map.empty)
}
}
The first new thing of note here is the sealed
keyword. A trait or class
marked sealed
indicates that all subclasses of that trait or class must be
contained in the same file. This makes pattern matching on these types of
classes easier as it aids the compiler in detecting missing patterns checked
by the programmer and can help prevent certain classes of bugs.
We have three types of commands: GetPost
, AddPost
, and UpdatePost
. These
are all rather trivial and mirror the same commands from the Lagom version of
this microservice.
Next, we defined a sealed trait BlogEvent
which contains two public values.
The val
keyword is similar to marking a variable as final
in Java, while
the var
keyword is similar to a normal variable in Java. When defining a
val
or var
on a class, this is technically creating a field in the class
along with a getter-style method (named the same as the field) and a setter-style
method if using var
. What this effectively does is allows the variable to be
accessed as if it were a public field of the class. Note that by not giving a
val
or var
an initial value, this makes them abstract. It may be worth
noting here that semicolons are optional in Scala and tend to be omitted;
otherwise, this would have been our first usage of them.
For our events, we only care about commands, not queries (in the CQRS sense of
the words, not a BlogCommand
type of command), so we defined two events:
PostAdded
and PostUpdated
.
Next, we abuse the case class feature to implement our own exception class,
PostNotFound
, to obtain some handy features for free. This demonstrates
the syntax to call the constructor of a superclass as well. There is one
other nifty feature in use here: interpolated strings. Scala provides an
extensible feature to make interpolated strings which are expanded out and
filled in at compile time. The syntax s"Hello, $foo!"
would be
essentially the same as "Hello, " + foo + "!"
at compile time. We could use
more advanced expressions inside the string by wrapping the variable name
in curly braces, so for instance, we could say s"Hello, ${foo.capitalize}!"
.
After that, we define a type
alias named MaybePost[+A]
. There are a few
things going on in this, so let’s break it down. First of all, Scala allows us
to define type aliases which can be used to alias a larger type into something
more readable, or it can even be used simply to rename types. In the generic
type parameter, the +A
bit indicates that, if B
is a subclass of A
, then
MaybePost[B]
is a subclass of MaybePost[A]
. This is called a covariant type
parameter. If we omitted the plus, we’d have an invariant type parameter which
means that regardless of how A
and B
are related, Foo[A]
and Foo[B]
would not be related. There is a third type of variance available called
contravariant type parameters which use -A
and imply the opposite subclass
relationship between Foo[A]
and Foo[B]
from A
and B
. We alias this
MaybePost
from the Either[A, B]
type from the Scala standard library which
is a type that can be either a Left
or Right
value containing a type A
or
B
respectively. This class is normally used as a more powerful form of
Option
(Optional
in Java) where the left side type is an exception type and
the right side type is the success value type.
Finally, we come to the BlogState
class and companion object. We have some
strange names chosen here for methods: apply
and +
. Wait, +
? Yup! In
Scala, you can name a method pretty much anything you like. Scala will translate
the names into valid Java identifiers at compile time. As explained above, the
apply
method is treated specially by allowing the word apply
to be omitted
when calling the method. There are several other special method names that allow
for syntax features in Scala such as update
, unapply
, map
, flatMap
,
filter
, withFilter
, foreach
, and methods named after mathematical
operators such as +
, -
, and *
. We will not be covering most of them, but
it’s worth noting their names. One other thing to note here is that the default
Map[K, V]
type used here is an immutable hash map, and in keeping with the
spirit of immutability, we will be updating our BlogState
by returning a new
BlogState
with a new Map
which contains the old map plus an additional item.
Thus, the use of posts.updated(key, value)
returns a new map with the addition
or modification of the provided key value mapping.
With all that out of the way, let’s move on to the entity implementation.
class BlogEntity extends PersistentActor {
import BlogEntity._
import context._
private var state = BlogState()
override def persistenceId: String = "blog"
override def receiveCommand: Receive = {
case GetPost(id) =>
sender() ! state(id)
case AddPost(content) =>
handleEvent(PostAdded(PostId(), content)) pipeTo sender()
()
case UpdatePost(id, content) =>
state(id) match {
case response @ Left(_) => sender() ! response
case Right(_) =>
handleEvent(PostUpdated(id, content)) pipeTo sender()
()
}
}
private def handleEvent[E <: BlogEvent](e: => E): Future[E] = {
val p = Promise[E]
persist(e) { event =>
p.success(event)
state += event
system.eventStream.publish(event)
if (lastSequenceNr != 0 && lastSequenceNr % 1000 == 0)
saveSnapshot(state)
}
p.future
}
override def receiveRecover: Receive = {
case event: BlogEvent =>
state += event
case SnapshotOffer(_, snapshot: BlogState) =>
state = snapshot
}
}
Note that we need to extend PersistentActor
to use akka-persistence, the
mechanism we’ll be using to save blog events to disk. This class is dense with
syntax we haven’t covered yet, so let’s go over it all. First of all, note how
we imported all the members of the BlogEntity
object. The _
in the import is
equivalent to using *
in Java (Scala doesn’t use *
here because *
is a
valid class and method name in Scala). Note that we don’t ever use an import
static
like in Java as Scala does not have static things (though it can import
static things from Java without having to specify it’s a static import). Since
BlogEntity
is already in scope, we did not have to specify the full package
name preceding it in the import. We also import the members of the context
variable which is defined in a superclass. We do this to gain access to some
implicitly available variables used in the asynchronous code below.
Next, note that we were able to construct a BlogState
object by omitting
the keyword new
because we defined a method named apply
. Thus, we are
actually calling BlogState.apply()
here. In the next line, we override an
abstract method to identify this aggregate root for persistence purposes. An
alternative name here may be the fully qualified class name.
Another neat syntax to note here is that in Scala, a zero-arg method can omit
its parenthesis. This feature allows for a lot of neat things such as allowing a
val
to override an arg-less def
in the parent class. Our next method,
receiveCommand
, returns an Akka type called Receive
which is a type alias
for a lambda function that takes a single parameter of type Any
and returns
nothing. This warrants a quick overview of some of the standard Scala types.
The Any
class is the root class, and from there are AnyVal
and AnyRef
.
The AnyVal
class is for types like Int
, Boolean
, Double
, etc., along
with user-defined value classes as explained in the PostId
description, while
AnyRef
is equivalent to Object
in Java. There is also the Unit
class which
is equivalent to void
in Java. The difference here is that technically, all
methods must return a value in Scala, so for an empty return type, Unit
can be
used which has only one possible value: ()
. There are also two bottom-most
types in Scala: Nothing
and Null
. The Nothing
type is generally used as a
return value for a method that does not actually return (e.g., it always throws
an exception). The Null
type is the type of the null
reference which is a
subtype of everything. Try not to confuse these with the None
type of an empty
Option
or the Nil
instance of an empty List
.
The receive method of an actor tends to be implemented as a pattern match expression.
A pattern match takes the form of foo match { case a => ...; case b => ...; ... }
,
and this is a very powerful feature used pervasively in Scala. In our use case
here, we can omit the foo match
part to create a lambda function that matches
an anonymous value. In our cases, we’ll be looking for messages that match our
command types. Since our commands are all case classes, Scala has generated an
unapply
method for each which makes the classes destructurable so to say within
pattern match expressions. For example, the expression case GetPost(id) =>
will match when the matched object is an instance of GetPost
, and it will
subsequently bind its one field to the new variable named id
. We can use
nested patterns here if we wanted, but that is a more advanced feature.
In the GetPost
example, we already have a handy BlogState.apply(id)
method
available as a lookup to find the content for a given id. This returns our
MaybePost[PostContent]
type which can be used to determine whether or not we
got the content. Using the sender()
method, we can send a message back to the
actor that sent the initial message. The !
method used here is an alias for
the tell
method. Note that we omitted the dot and parenthesis here by using
the infix notatation syntax of calling methods. This syntax allows, for example,
the method call 1.+(1)
to be written as 1 + 1
. This is particularly useful
for methods that have an operator syntax like +
, but it can also be useful in
functional programming contexts as well. Thus, our line of code is equivalent to
sender().!(state.apply(id))
when expanded out.
Next, to handle the AddPost
command, we create a PostAdded
event with a new
id and its content. The result of handling the event is a Future[PostAdded]
,
so we use the pipeTo
pattern in Akka to send the result of that future back to
the sender. As mentioned for the infix method call syntax, this line can be
equivalently written as handleEvent(PostAdded(PostId(), content)).pipeTo(sender())
.
We’ll be reusing the event handling logic in UpdatePost
, so we’ll come back to
that. The next line contains a Unit
to return explicitly. We do this because
we are using a set of strict compiler flags which would make discarding a return
value an error otherwise. Note that this is not required in the default Scala
compiler settings, but we’re trying to stick to high quality Scala code.
The UpdatePost
command requires a bit more work than AddPost
did. This is
to validate that the id provided already exists. Thus, first we look up in our
state for an existing post. If it does not exist, we’ll get a Left(error)
value with some exception error
; if it does exist, we’ll get a Right(content)
value with content
being the PostContent
value. Thus, we combine this with
a pattern match to check for both types. The syntax, case response @ Left(_)
,
uses two pattern types concurrently: binding the matched expression to the new
variable named response
, and matching that it is a Left
type with any
content (the _
is a wildcard match in this context as a throwaway variable).
If the post doesn’t exist, we send back the error. Otherwise, we create a
PostUpdated
event, handle it, and send it back.
Next, we look at handling the event. Let’s break down the new syntax. The
[E <: BlogEvent]
bit is a generic method type parameter, where <:
is
equivalent to extends
in a Java generic method. Conversely, the >:
generic
syntax would be equivalent to super
in Java. For example, this method
may be written in Java as
private <E extends BlogEvent> Future<E> handleEvent(E event)
. The other
syntax of note here is the e: => E
parameter. This is similar to a zero-arg
lambda function, but when called as such, does not require being wrapped in a
lambda. Had we used e: () => E
as the parameter instead, then we would have
had to call the method as handleEvent(() => PostAdded(...))
instead.
In order to create a Future
here, we’re using the Promise
class. A Scala
Promise
works rather similarly to promises in JavaScript and other languages.
To handle the event, we call the persist
method defined in the
PersistentActor
superclass. We use another new syntax here where a method
with a single lambda function parameter can be replaced with curly braces.
A lambda function can always be surrounded in curly braces, so this syntax
is taking advantage of some infix method call syntax features.
This syntax is rather similar to how Closure
s work in Groovy. The lambda
provided to persist
here is called after the event has been successfully
persisted. In this, we complete our promise which is returned as a Future
to the caller. After that, we call state += event
. Since we never defined a
method called +=
on BlogState
, Scala sees that state
is a var
, so it
can rewrite that expression into state = state + event
. Since we do in fact
have such a method available, the whole expression is equivalent to calling
state = state.+(event)
. After that, we publish the event to the system
event stream which can be used as an application-wide event bus. Finally, we
add in a periodic call to saveSnapshot
every 1000 events which will allow
us to restart and recover the state a lot faster than rereading the entire
event log.
The last bit of code we needed to implement in this actor was the
receiveRecover
method which is used to recover the latest snapshot on
startup if available. We can populate this actor’s state directly from the
snapshot, and then subsequently handle all the events that weren’t included
in that snapshot. The only new syntax used here is the case event: BlogEvent
pattern match syntax which matches when the object is an instance of BlogEvent
and binds it to the new variable event
.
Blog Service
Now that we’ve written our entity actor, we can fill in the stub BlogService
trait defined earlier.
trait BlogService extends AkkaConfiguration {
import BlogEntity._
private val blogEntity = actorRefFactory.actorOf(Props[BlogEntity])
def getPost(id: PostId): Future[MaybePost[PostContent]] =
(blogEntity ? GetPost(id)).mapTo[MaybePost[PostContent]]
def addPost(content: PostContent): Future[PostAdded] =
(blogEntity ? AddPost(content)).mapTo[PostAdded]
def updatePost(id: PostId, content: PostContent): Future[MaybePost[PostUpdated]] =
(blogEntity ? UpdatePost(id, content)).mapTo[MaybePost[PostUpdated]]
}
The AkkaConfiguration
trait we mix in here is a trait we defined for easy
access to Akka-related objects such as an ActorRefFactory
to create actors,
a Materializer
which is used to run Akka Streams, an ExecutionContext
which
is used for coordinating Future
s and other asynchronous functionality, and a
Timeout
which is used for a default timeout value when making request/reply-style
ask
calls to actors.
As noted before, we can import all the members of the BlogEntity
companion
object here. It’s also worth noting that Scala allows you to import things to
whatever scope you want, so we can limit our imports to the closest spot it is
actually used.
To use our BlogEntity
actor, we need to spawn it first. Using our ActorRefFactory
,
we can spawn a BlogEntity
using the default Props
of a BlogEntity
. Spawning
the actor will create it and start it asynchronously, returning an ActorRef
instance which can be used to interact with the actor. Since actors can be restarted,
replaced, or even located on completely different processes or machines, we never
directly access an actor’s instance and instead use its ActorRef
to send messages,
watch it, etc. Since actors are written in a rather type-unsafe fashion, we’re wrapping
the actor’s messaging API into a typesafe trait.
In order to receive responses from our actor when sending a message, we must do
so asynchronously. Thus, our API returns futures. We utilize the ask
pattern
to send a message to the actor and wait for a response message. Since actors
are not typed, the message response comes back as a Future[Any]
, so we use the
mapTo[A]
method on Future
to verify it matches our expected type and cast it.
Other than that, this trait is rather self-explanatory based on our BlogEntity
.
Blog REST API
Next up is defining our REST API. We’ll be using the high level Akka HTTP
route DSL for this. Using our BlogService
trait, we’ll mix that in to
another trait to define our API.
trait BlogRestApi extends RestApi with BlogService {
override def route: Route =
pathPrefix("api" / "blog") {
(pathEndOrSingleSlash & post) {
// POST /api/blog/
entity(as[PostContent]) { content =>
onSuccess(addPost(content)) { added =>
complete((StatusCodes.Created, added))
}
}
} ~
pathPrefix(JavaUUID.map(PostId(_))) { id =>
pathEndOrSingleSlash {
get {
// GET /api/blog/:id
onSuccess(getPost(id)) {
case Right(content) => complete((StatusCodes.OK, content))
case Left(error) => complete((StatusCodes.NotFound, error))
}
} ~
put {
// PUT /api/blog/:id
entity(as[PostContent]) { content =>
onSuccess(updatePost(id, content)) {
case Right(updated) => complete((StatusCodes.OK, updated))
case Left(error) => complete((StatusCodes.NotFound, error))
}
}
}
}
}
}
}
There are a couple new syntax features here along with many DSL-specific
things going on. First of all, this introduces the keyword with
which is used
when extending multiple traits. The RestApi
trait is one we made that mixes
in the Akka HTTP route DSL along with support for marshalling and unmarshalling
our case classes and primitive types into JSON. The only other new syntax here
is the tuple syntax. A tuple is an abstraction of an ordered pair. A tuple can
have two or more values that do not have to be the same type. They are contained
in parenthesis and separated by commas. Due to syntax ambiguity, when sending an
inline tuple to a method, we need to double up on the parenthesis to avoid it
being interpreted as a call to a method with multiple parameters. All other
syntax in this trait are features of the route DSL. For example, URI paths can
be matched using implicit conversions from strings into URI patterns, and those
patterns can be composed with /
which matches a slash in the URI. Segments
of the path can be extracted into parameters in the lambda function provided
after. We can easily unmarshal request bodies using the entity(as[A]) { a: A => ... }
DSL. Using our service mixin, we can forward these requests and get response
which can be chained back into the HTTP response. Finally, the ~
function is
used to chain two routes together into a single route. Far more comprehensive
information about the routing DSL can be found in the Akka documentation.
In order to run this code, we still need to create our HTTP server and set up Akka in general. That code is not very interesting in itself and is included in the code samples. While this post only scratches the surface of Scala, it provides a broad overview of various Scala-specific syntax features that really differentiate it from Java. There is one other feature that has only been mentioned by name so far, and that is implicits. Implicits are a powerful feature specific to Scala that helps reduce boilerplate typing in various scenarios. Implicits can generally be used for a few different things:
- Implicit values can be used to provide parameters to functions or class
constructors without being explicitly written as long as it’s in scope. This
is useful for passing around parameters that are needed by tons of methods
such as the
ExecutionContext
object mentioned above. - Implicit parameters can be used to automatically be filled in by an implicit value that is in scope so that the parameter doesn’t have to be typed out over and over again.
- Implicit methods can be used to convert from one type to another when the
original type is not compatible where it is used. For example, this feature
is used to automatically convert an
Int
to aLong
when passed to a method parameter that takes aLong
. This can be a very dangerous feature when misused. - Implicit classes can be used to provide extension methods to an existing API.
This is used to add methods to
java.lang.String
, for example, which cannot normally be done asString
is a final class that cannot be extended. Extension methods provide a compositional way to extend APIs in a type safe fashion without having to explicitly wrap the API everywhere an extension method is desired. - Implicits can be used to provide type classes to Scala, a feature common to
functional programming languages. Type classes are used very seldomly in
object oriented programming languages. An example of a simple type class in
Java is the
Comparable<T>
orEnum<E>
interface.
Overall, Scala is a fantastic programming language with a simple core set of features and tons of extensibility. Staying true to the “scalable language” concept behind its name, Scala works well in small scripts all the way up to large distributed applications. The Akka framework provides the building blocks for writing distributed systems using event sourcing and CQRS, while Scala itself enables much more concise, readable DSLs and removal of verbose boilerplate common to many Java frameworks. Akka is far more flexible than its opinionated cousin, Lagom, and it works well with various technologies common to event sourced systems like Kafka and Cassandra.