Microservices architecture are becoming a de-facto industry standard, but are you satisfied with the current state of the art? We are not, as we believe that building microservices today is more challenging than it should be. Lagom is here to take on this challenge. First, Lagom is opinionated and it will take some of the hard decisions for you, guiding you to produce microservices that adheres to the Reactive tenents. Second, Lagom was built from the ground up around you, the developer, to push your productivity to the next level. If you are familiar with the Play Framework's development environment, imagine that but tuned for building microservices; we are sure you are going to love it! Third, Lagom comes with batteries included for deploying in production: going from development to production could not be easier. In this session, you will get an introduction to the Lightbend Lagom framework. There will be code and live demos to show you in practice how it works and what you can do with it, making you fully equipped to build your next microservices with Lightbend Lagom!
3. Agenda
● Why Lagom?
● Lagom dive in
○ Development Environment
○ Service API
○ Persistence API
● Running in Production
4. Why Lagom?
● Opinionated
● Developer experience matters!
○ No brittle script to run your services
○ Inter-service communication just works
○ Services are automatically reloaded on code change
● Takes you through to production deployment
5. ● sbt build tool (developer environment)
● Play 2.5
● Akka 2.4 (clustering, streams, persistence)
● Cassandra (default data store)
● Jackson (JSON serialization)
● Guice (DI)
Under the hood
9. Service definition
// this source is placed in your api project
trait HelloService extends Service {
override def descriptor(): Descriptor = {
named("helloservice").withCalls(
namedCall("/hello", sayHello _)
)
}
def sayHello(): ServiceCall[String, String]
}
10. ServiceCall explained
● ServiceCall can be invoked when consuming a service:
○ Request: type of incoming request message (e.g. String)
○ Response: type of outgoing response message (e.g. String)
● JSON is the default serialization format for request/response messages
● There are two kinds of request/response messages:
○ Strict
○ Streamed
trait ServiceCall[Request, Response] {
def invoke(request: Request): Future[Response]
}
12. Streamed Messages
override def descriptor(): Descriptor = {
named("clock").withCalls(
pathCall("/tick/:interval", tick _)
)
}
def tick(interval: Int): ServiceCall[String, Source[String, _]]
● A streamed message is of type Source (an Akka streams API)
● Back-pressured, asynchronous handling of messages
● Lagom will select transport protocol (currently WebSockets)
13. Remember the Service definition?
// this source is placed in your api project
trait HelloService extends Service {
override def descriptor(): Descriptor = {
named("helloservice").withCalls(
namedCall(sayHello _)
)
}
def sayHello(): ServiceCall[String, String]
}
14. Here is the Service implementation
// this source is placed in your implementation project
class HelloServiceImpl extends HelloService {
override def sayHello(): ServiceCall[String, String] = {
name => Future.successful(s"Hello, $name!")
}
}
15. Inter-service communication
class MyServiceImpl @Inject()(helloService: HelloService)
(implicit ec: ExecutionContext) extends MyService {
override def sayHelloLagom(): ServiceCall[NotUsed, String] = unused => {
val response = helloService.sayHello().invoke("Lagom")
response.map(answer => s"Hello service said: $answer")
}
}
16. Inter-service communication
Do you see a problem?
class MyServiceImpl @Inject()(helloService: HelloService)
(implicit ec: ExecutionContext) extends MyService {
override def sayHelloLagom(): ServiceCall[NotUsed, String] = unused => {
val response = helloService.sayHello().invoke("Lagom")
response.map(answer => s"Hello service said: $answer")
}
}
18. Principles
● Each service owns its data
○ Only the service has direct access to the DB
● We advocate the use of Event Sourcing (ES) and CQRS
○ ES: Capture all state’s changes as events
○ CQRS: separate models for write and read
19. Benefits of Event Sourcing/CQRS
● Allows you to time travel
● Audit log
● Future business opportunities
● No need for ORM
● No database migration script, ever!
● Performance & Scalability
● Testability & Debuggability
20. Event Sourcing: Write Side
● Create your own Command and Event classes
● Subclass PersistentEntity
○ Define Command and Event handlers
○ Can be accessed from anywhere in the cluster
○ (corresponds to an Aggregate Root in DDD)
21. Let’s implement the add a friend functionality in chirper
Event Sourcing: Example
Demo
22. Event Sourcing: Example
1. Create a AddFriend command class
2. Create a FriendAdded event class
3. Define a FriendEntity holding the state (i.e., who are the
friends of a specific user)
a. Create a command handler for the AddFriend command
b. Create an event handler for the FriendAdded event
23. Event Sourcing: Example cont’d
1. Create a AddFriend command class
2. Create a FriendAdded event class
3. Define a FriendEntity holding the state of what are friends of
a given user
4. Create a command handler for the AddFriend command
5. Create an event handler for the FriendAdded event
24. Event Sourcing: Example cont’d
Create the Command classes
sealed trait FriendCommand extends Jsonable
case class AddFriend(friendId: String) extends
PersistentEntity.ReplyType[Done] with FriendCommand
// more friend commands
25. Event Sourcing: Example cont’d
1. Create a AddFriend command class
2. Create a FriendAdded event class
3. Define a FriendEntity holding the state of what are friends of
a given user
4. Create a command handler for the AddFriend command
5. Create an event handler for the FriendAdded event
26. Event Sourcing: Example cont’d
Create the Event classes
sealed trait FriendEvent extends Jsonable
case class FriendAdded(userId: String, friendId: String,
timestamp: Instant = Instant.now()) extends FriendEvent
// more friend events
27. Event Sourcing: Example cont’d
1. Create a AddFriend command class
2. Create a FriendAdded event class
3. Define a FriendEntity holding the state of what are friends of
a given user
4. Create a command handler for the AddFriend command
5. Create an event handler for the FriendAdded event
28. Event Sourcing: Example cont’d
class FriendEntity extends
PersistentEntity[FriendCommand, FriendEvent, FriendState] {
def initialBehavior(snapshotState: Optional[FriendState]): Behavior =
// TODO: define command and event handlers
}
Create a subclass of PersistentEntity
29. Event Sourcing: Example cont’d
Create the State class
case class FriendState(user: Option[User]) extends Jsonable {
def addFriend(friendUserId: String): FriendState = user match {
case None => throw new IllegalStateException(
"friend can't be added before user is created")
case Some(user) =>
val newFriends = user.friends :+ friendUserId
FriendState(Some(user.copy(friends = newFriends)))
}
}
30. Event Sourcing: Example cont’d
1. Create a AddFriend command class
2. Create a FriendAdded event class
3. Define a FriendEntity holding the state of what are friends of
a given user
4. Create a command handler for the AddFriend command
5. Create an event handler for the FriendAdded event
31. Event Sourcing: Example cont’d
val b: Behavior = newBehaviorBuilder(/*...*/)
b.setCommandHandler(classOf[AddFriend],
(cmd: AddFriend, ctx: CommandContext[Done]) => state.user match {
case None =>
ctx.invalidCommand(s"User ${entityId} is not created")
ctx.done()
case Some(user) =>
ctx.thenPersist(FriendAdded(user.userId, cmd.friendUserId),
(evt: FriendAdded) => ctx.reply(Done))
})
32. Event Sourcing: Example cont’d
1. Create a AddFriend command class
2. Create a FriendAdded event class
3. Define a FriendEntity holding the state of what are friends of
a given user
4. Create a command handler for the AddFriend command
5. Create an event handler for the FriendAdded event
34. class FriendService extends Service {
override def descriptor(): Descriptor = {
named("friendservice").withCalls(
pathCall("/api/users/:userId/friends", addFriend _),
...
)
}
def addFriend(userId: String): ServiceCall[FriendId, NotUsed]
}
Event Sourcing: Example cont’d
And we can now finally implement the service call
35. class FriendServiceImpl @Inject() (persistentEntities: PersistentEntityRegistry)
(implicit ec: ExecutionContext) extends FriendService {
// at service startup we must register the needed entities
persistentEntities.register(classOf[FriendEntity])
def addFriend(userId: String): ServiceCall[FriendId, NotUsed] = request => {
val ref = persistentEntities.refFor(classOf[FriendEntity], userId)
ref.ask[Done, AddFriend](AddFriend(request.friendId))
}
// ...
}
Event Sourcing: Example cont’d
36. Event Sourcing: Read Side
● Tightly integrated with Cassandra
● Create the query tables:
○ Subclass CassandraReadSideProcessor
○ Consumes events produced by the PersistentEntity and
updates tables in Cassandra optimized for queries
● Retrieving data: Cassandra Query Language
○ e.g., SELECT id, title FROM postsummary
37. Running in Production
● sbt-native packager is used to produce zip, MSI, RPM, Docker
● Lightbend ConductR* (our container orchestration tool)
● Lightbend Reactive Platform*
○ Split Brain Resolver (for Akka cluster)
○ Lightbend Monitoring
*Requires a Lightbend subscription (but it is free to use during development)
38. Current[Lagom]
● Current version is 1.0.0-RC1
● Java API, but no Scala API yet
○ We are working on the Scala API
○ But using Scala with the Java API works quite well! https:
//github.com/dotta/activator-lagom-scala-chirper
39. Future[Lagom]
● Maven support
● Message broker integration
● Scala API
● Support for other cluster orchestration tools
○ Want Kubernetes support? Contribute! https://github.
com/huntc/kubernetes-lib
● Support for writing integration tests
● Swagger integration
40. Next: Seq[Step]
● Try Lagom yourself
○ https://lightbend.com/lagom
● Using Scala with Lagom
○ https://github.com/dotta/activator-lagom-scala-
chirper/releases/tag/v02_scaladays_berlin_2016
● Lagom on Github
○ https://github.com/lagom/lagom
● Read Jonas Bonér's free ebook Reactive Services Architecture
○ https://lightbend.com/reactive-microservices-architecture
● Great presentation by Greg Young on why you should use ES
○ https://www.youtube.com/watch?v=JHGkaShoyNs