lightbend lagom: microservices just right
TRANSCRIPT
![Page 1: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/1.jpg)
Lightbend LagomMicroservices “Just Right”
Mirco Dotta
@mircodotta
Scala Days NYC - May 10, 2016
![Page 2: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/2.jpg)
Lagom - [lah-gome]
Adequate, sufficient, just right
![Page 3: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/3.jpg)
Agenda
● Why Lagom?● Lagom dive in
○ Development Environment○ Service API○ Persistence API
● Running in Production
![Page 4: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/4.jpg)
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
![Page 5: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/5.jpg)
● 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
![Page 6: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/6.jpg)
Enough slides,
Demo time
![Page 7: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/7.jpg)
Anatomy of a Lagom project
● sbt build● Scala 2.11 and JDK8● Each service definition is split into two sbt projects:
○ api○ Implementation
![Page 8: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/8.jpg)
Service API
![Page 9: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/9.jpg)
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
![Page 10: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/10.jpg)
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]
}
![Page 11: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/11.jpg)
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]
![Page 12: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/12.jpg)
Streamed Messagesoverride 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)
![Page 13: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/13.jpg)
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
![Page 14: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/14.jpg)
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
![Page 15: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/15.jpg)
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")
}
}
![Page 16: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/16.jpg)
Persistence API
![Page 17: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/17.jpg)
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
![Page 18: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/18.jpg)
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
![Page 19: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/19.jpg)
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)
![Page 20: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/20.jpg)
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
![Page 21: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/21.jpg)
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
![Page 22: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/22.jpg)
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
![Page 23: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/23.jpg)
Event Sourcing: Example cont’dval 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))
})
![Page 24: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/24.jpg)
b.setEventHandler(classOf[FriendAdded],
(evt: FriendAdded) => state.addFriend(evt.friendId))
Event Sourcing: Example cont’d
No side-effects in the event handler!
![Page 25: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/25.jpg)
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)))
}
}
![Page 26: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/26.jpg)
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
![Page 27: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/27.jpg)
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
![Page 28: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/28.jpg)
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)
![Page 29: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/29.jpg)
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
![Page 30: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/30.jpg)
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!
![Page 31: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/31.jpg)
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
![Page 32: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/32.jpg)
Thank you for listening!@mircodotta
@lagom
![Page 33: Lightbend Lagom: Microservices Just Right](https://reader030.vdocuments.site/reader030/viewer/2022020203/587331c11a28ab596c8b6bf3/html5/thumbnails/33.jpg)
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