Lightbend Lagom
Microservices “Just Right”
Mirco Dotta
@mircodotta
Scala Days NYC - May 10, 2016
Lagom - [lah-gome]
Adequate, sufficient, just right
Agenda
● Why Lagom?
● Lagom dive in
○ Development Environment
○ Service API
○ Persistence API
● Running in Production
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
● sbt build tool (developer experience)
● Play 2.5
● Akka 2.4 (clustering, streams, persistence)
● Cassandra (default data store)
● Jackson (JSON serialization)
● Guice (DI)
● Architectural Concepts: immutability, Event Sourcing/CQRS,
circuit breakers
Under the hood
Enough slides,
Demo time
Anatomy of a Lagom project
● sbt build
● Scala 2.11 and JDK8
● Each service definition is split into two sbt projects:
○ api
○ Implementation
Service API
Service definition
trait HelloService extends Service {
override def descriptor(): Descriptor = {
named("helloservice").`with`(
namedCall("/hello", sayHello _)
)
}
def sayHello(): ServiceCall[String, String]
}
// this source is placed in your api project
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]
}
Strict Messages
Strict messages are fully buffered into memory
override def descriptor(): Descriptor = {
named("friendservice").`with`(
pathCall("/users/:userId/friends", addFriend _)
)
}
def addFriend(userId: String): ServiceCall[FriendId, NotUsed]
Streamed Messages
override def descriptor(): Descriptor = {
named("clock").`with`(
pathCall("/tick/:interval", tick())
)
}
def tick(): ServiceCall[String, Source[String, _]]
● A streamed message is of type Source (an Akka streams API)
● It allows asynchronous streaming and handling of messages
● Lagom will select transport protocol (currently WebSockets)
Remember the Service definition?
trait HelloService extends Service {
override def descriptor(): Descriptor = {
named("helloservice").`with`(
namedCall(sayHello _)
)
}
def sayHello(): ServiceCall[String, String]
}
// this source is placed in your api project
Here is the Service implementation
class HelloServiceImpl extends HelloService {
override def sayHello(): ServiceCall[String, String] {
name => Future.successful(s"Hello, $name!")
}
}
// this source is placed in your implementation project
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")
}
}
Persistence API
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
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
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)
Event Sourcing: Example
Create the Command classes
sealed trait FriendCommand extends Jsonable
case class AddFriend(friendUserId: String) extends
PersistentEntity.ReplyType[Done] with FriendCommand
// more friend commands
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
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
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))
})
b.setEventHandler(classOf[FriendAdded],
(evt: FriendAdded) => state.addFriend(evt.friendId))
Event Sourcing: Example cont’d
No side-effects in the event handler!
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)))
}
}
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
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
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 (ConductR is free to use during development)
Current[Lagom]
● Current version is 1.0.0-M2
○ 1.0 soon
● Java API, but no Scala API yet
○ We are working on the Scala API
○ But using Scala with the Java API works well! https:
//github.com/dotta/activator-lagom-scala-chirper
Future[Lagom]
● Maven support
● Message broker integration
● Scala API
● Support for other cluster orchestration tools
● Support for writing integration tests
● Swagger integration
○ Which also removes binary coupling!
Next: Seq[Step]
● Try Lagom yourself
○ https://lightbend.com/lagom
● Using Scala with Lagom
○ https://github.com/dotta/activator-lagom-scala-chirper
● Lagom on Github
○ https://github.com/lagom/lagom
● Read Jonas Bonér's free book 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
Thank you for listening!
@mircodotta
@lagom
override def descriptor(): Descriptor = {
named("friendservice").`with`(
namedCall("/users", createUser _),
pathCall("/users/:id", getUser _),
restCall(Method.POST, "/users/:userId/friends", addFriend _)
)
}
def createUser(): ServiceCall[User, NotUsed]
def getUser(id: String): ServiceCall[NotUsed, User]
def addFriend(userId: String): ServiceCall[FriendId, NotUsed]
Different kinds of service call descriptors

Lightbend Lagom: Microservices Just Right

  • 1.
    Lightbend Lagom Microservices “JustRight” Mirco Dotta @mircodotta Scala Days NYC - May 10, 2016
  • 2.
    Lagom - [lah-gome] Adequate,sufficient, just right
  • 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 buildtool (developer experience) ● Play 2.5 ● Akka 2.4 (clustering, streams, persistence) ● Cassandra (default data store) ● Jackson (JSON serialization) ● Guice (DI) ● Architectural Concepts: immutability, Event Sourcing/CQRS, circuit breakers Under the hood
  • 6.
  • 7.
    Anatomy of aLagom project ● sbt build ● Scala 2.11 and JDK8 ● Each service definition is split into two sbt projects: ○ api ○ Implementation
  • 8.
  • 9.
    Service definition trait HelloServiceextends Service { override def descriptor(): Descriptor = { named("helloservice").`with`( namedCall("/hello", sayHello _) ) } def sayHello(): ServiceCall[String, String] } // this source is placed in your api project
  • 10.
    ServiceCall explained ● ServiceCallcan 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] }
  • 11.
    Strict Messages Strict messagesare fully buffered into memory override def descriptor(): Descriptor = { named("friendservice").`with`( pathCall("/users/:userId/friends", addFriend _) ) } def addFriend(userId: String): ServiceCall[FriendId, NotUsed]
  • 12.
    Streamed Messages override defdescriptor(): Descriptor = { named("clock").`with`( pathCall("/tick/:interval", tick()) ) } def tick(): ServiceCall[String, Source[String, _]] ● A streamed message is of type Source (an Akka streams API) ● It allows asynchronous streaming and handling of messages ● Lagom will select transport protocol (currently WebSockets)
  • 13.
    Remember the Servicedefinition? trait HelloService extends Service { override def descriptor(): Descriptor = { named("helloservice").`with`( namedCall(sayHello _) ) } def sayHello(): ServiceCall[String, String] } // this source is placed in your api project
  • 14.
    Here is theService implementation class HelloServiceImpl extends HelloService { override def sayHello(): ServiceCall[String, String] { name => Future.successful(s"Hello, $name!") } } // this source is placed in your implementation project
  • 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.
  • 17.
    Principles ● Each serviceowns 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
  • 18.
    Benefits of EventSourcing/CQRS ● Allows you to time travel ● Audit log ● Future business opportunities ● No need for ORM ● No database migration script, ever ● Performance & Scalability ● Testability & Debuggability
  • 19.
    Event Sourcing: WriteSide ● 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)
  • 20.
    Event Sourcing: Example Createthe Command classes sealed trait FriendCommand extends Jsonable case class AddFriend(friendUserId: String) extends PersistentEntity.ReplyType[Done] with FriendCommand // more friend commands
  • 21.
    Event Sourcing: Examplecont’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
  • 22.
    Event Sourcing: Examplecont’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
  • 23.
    Event Sourcing: Examplecont’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)) })
  • 24.
    b.setEventHandler(classOf[FriendAdded], (evt: FriendAdded) =>state.addFriend(evt.friendId)) Event Sourcing: Example cont’d No side-effects in the event handler!
  • 25.
    Event Sourcing: Examplecont’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))) } }
  • 26.
    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
  • 27.
    Event Sourcing: ReadSide ● 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
  • 28.
    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 (ConductR is free to use during development)
  • 29.
    Current[Lagom] ● Current versionis 1.0.0-M2 ○ 1.0 soon ● Java API, but no Scala API yet ○ We are working on the Scala API ○ But using Scala with the Java API works well! https: //github.com/dotta/activator-lagom-scala-chirper
  • 30.
    Future[Lagom] ● Maven support ●Message broker integration ● Scala API ● Support for other cluster orchestration tools ● Support for writing integration tests ● Swagger integration ○ Which also removes binary coupling!
  • 31.
    Next: Seq[Step] ● TryLagom yourself ○ https://lightbend.com/lagom ● Using Scala with Lagom ○ https://github.com/dotta/activator-lagom-scala-chirper ● Lagom on Github ○ https://github.com/lagom/lagom ● Read Jonas Bonér's free book 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
  • 32.
    Thank you forlistening! @mircodotta @lagom
  • 33.
    override def descriptor():Descriptor = { named("friendservice").`with`( namedCall("/users", createUser _), pathCall("/users/:id", getUser _), restCall(Method.POST, "/users/:userId/friends", addFriend _) ) } def createUser(): ServiceCall[User, NotUsed] def getUser(id: String): ServiceCall[NotUsed, User] def addFriend(userId: String): ServiceCall[FriendId, NotUsed] Different kinds of service call descriptors