Lightbend Lagom: Microservices Just Right

33
Lightbend Lagom Microservices “Just Right” Mirco Dotta @mircodotta Scala Days NYC - May 10, 2016

Transcript of Lightbend Lagom: Microservices Just Right

Page 1: Lightbend Lagom: Microservices Just Right

Lightbend LagomMicroservices “Just Right”

Mirco Dotta

@mircodotta

Scala Days NYC - May 10, 2016

Page 2: Lightbend Lagom: Microservices Just Right

Lagom - [lah-gome]

Adequate, sufficient, just right

Page 3: Lightbend Lagom: Microservices Just Right

Agenda

● Why Lagom?● Lagom dive in

○ Development Environment○ Service API○ Persistence API

● Running in Production

Page 4: Lightbend Lagom: Microservices Just Right

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

● 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

Enough slides,

Demo time

Page 7: Lightbend Lagom: Microservices Just Right

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

Service API

Page 9: Lightbend Lagom: Microservices Just Right

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

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

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

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

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

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

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

Persistence API

Page 17: Lightbend Lagom: Microservices Just Right

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Thank you for listening!@mircodotta

@lagom

Page 33: Lightbend Lagom: Microservices Just Right

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