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 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
9. 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
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").`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 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
14. 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
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")
}
}
17. 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
18. 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
19. 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)
20. 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
21. 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
22. 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
23. 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))
})
25. 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)))
}
}
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: 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
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 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
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]
● 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