Slide 1

Slide 1 text

Events-First Microservices With Lagom Gideon de Kok @gideondk Kenny Baas @kenny_baas

Slide 2

Slide 2 text

2 IT’S YOUR BUSINESS. WE ACCELERATE IT. XEBIA GROUP CONSISTS OF SEVEN SPECIALIZED, INTERLINKED COMPANIES WITH OFFICES IN AMSTERDAM AND HILVERSUM (NETHERLANDS), PARIS, DELHI, BANGALORE AND BOSTON, WE EMPLOY OVER 700 PEOPLE WORLDWIDE

Slide 3

Slide 3 text

Microservices

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

ShipOrder Order Shipping REST

Slide 6

Slide 6 text

ShipOrder Order Shipping REST

Slide 7

Slide 7 text

“Any organization that designs a system will produce a design whose structure is a copy of the organization's communication structure.”
 
 — Melvin Conway, 1967

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

ShipOrder Order Shipping

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Fucking Microservices

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

We’re often forcing our current problem into microservices instead of complimenting it

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

Boundaries aren’t only about form, they’re also about communication patterns and language

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

Structure-First Domain-Driven Design

Slide 18

Slide 18 text

Structure weights you down. Something that has been visualised is hard to be unvisualised.

Slide 19

Slide 19 text

Design is not just what it looks like and feels like. Design is how it works.

Slide 20

Slide 20 text

It is not the things that matter (in early stages of design). It is the things that happen — Russ Miles

Slide 21

Slide 21 text

“Event”
 1. A thing that happens or takes place, especially one of importance.

Slide 22

Slide 22 text

Events-First Domain-Driven Design™,

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

"Software development is a learning process; working code is a side effect." — Alberto Brandolini

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

Order Created

Slide 34

Slide 34 text

Order Confirmed Order Created Order Paid Order Shipped Order Delivered Order Picked

Slide 35

Slide 35 text

Order Shipping Order Confirmed Order Created Order Paid Order Shipped Order Delivered Order Picked

Slide 36

Slide 36 text

package com.xebia.webshop.order.api sealed trait OrderEvent object OrderEvent { case class OrderCreated(orderId: String, orderLines: Seq[OrderLine], shippingInfo: ShippingInfo) extends OrderEvent case class OrderConfirmed(orderId: String) extends OrderEvent case class OrderPaid(orderId: String, orderLines: Seq[OrderLine], shippingInfo: ShippingInfo) extends OrderEvent object OrderCreated { implicit val format: Format[OrderCreated] = Json.format } object OrderConfirmed { implicit val format: Format[OrderConfirmed] = Json.format } object OrderPaid { implicit val format: Format[OrderPaid] = Json.format } implicit val format: Format[OrderEvent] = derived.flat.oformat((__ \ "type").format[String]) }

Slide 37

Slide 37 text

package com.xebia.webshop.shipping.api sealed trait OrderEvent object OrderEvent { case class OrderPicked(orderId: String, pickedBy: Employee) extends OrderEvent case class OrderShipped(orderId: String, shippingReference: String) extends OrderEvent case class OrderDelivered(orderId: String, wasDeliveredAt: LocalDateTime) extends OrderEvent object OrderPicked { implicit val format: Format[OrderPicked] = Json.format } object OrderShipped { implicit val format: Format[OrderShipped] = Json.format } object OrderDelivered { implicit val format: Format[OrderDelivered] = Json.format } implicit val format: Format[OrderEvent] = derived.flat.oformat((__ \ "type").format[String]) }

Slide 38

Slide 38 text

“Lagom” /lah-gome/ 1. (just) right, fitting, neither too much nor too little

Slide 39

Slide 39 text

Lagom provides an opinionated way of building microservices that intentionally constrains what a developer can do and how they should do it.

Slide 40

Slide 40 text

package com.xebia.webshop.order.api import akka.NotUsed import com.lightbend.lagom.scaladsl.api.broker.Topic import com.lightbend.lagom.scaladsl.api.transport._ import com.lightbend.lagom.scaladsl.api.{Descriptor, Service, ServiceCall} trait OrderService extends Service { import Method._ import Service._ def orderEvents: Topic[OrderEvent] final override def descriptor = { named("order").withTopics( topic("order-OrderEvent", orderEvents) ) } }

Slide 41

Slide 41 text

Order Paid Order Picked Order Shipping

Slide 42

Slide 42 text

Order Paid Order Picked Whenever an Order is Paid Order Shipping

Slide 43

Slide 43 text

class OrderServicePolicy(orderService: OrderService) { orderService.orderEvents.subscribe.atLeastOnce(Flow[api.OrderEvent].mapAsync(1) { case x: OrderPaid => ??? // Where we should trigger our side-effect case other => Future.successful(Done) }) }

Slide 44

Slide 44 text

package com.xebia.webshop.order.api import akka.NotUsed import com.lightbend.lagom.scaladsl.api.broker.Topic import com.lightbend.lagom.scaladsl.api.transport._ import com.lightbend.lagom.scaladsl.api.{Descriptor, Service, ServiceCall} trait OrderService extends Service { import Method._ import Service._ def orderEvents: Topic[OrderEvent] final override def descriptor = { named("order").withTopics( topic("order-OrderEvent", orderEvents) ) } }

Slide 45

Slide 45 text

Order Paid Order Picked Whenever an Order is Paid Pick Order Pay Order Order Shipping

Slide 46

Slide 46 text

Order Order Picked Whenever an Order is Paid Pick Order Order Order Paid Pay Order Order Shipping

Slide 47

Slide 47 text

Create Order Order Pay Order Order Confirmed Order Created Order Paid

Slide 48

Slide 48 text

Order OrderLine OrderLine OrderLine CreateOrder Ack OrderCreated Akka Actor

Slide 49

Slide 49 text

Order 1 Order Domain Order 2 Order 3 Akka Singleton (Entity Framework)

Slide 50

Slide 50 text

Hash(Id(Order 1)) Hash(Id(Order 2)) Hash(Id(Order 3)) A B C Akka Cluster (Auto-Sharding)

Slide 51

Slide 51 text

Create Order Order Pay Order Order Confirmed Order Created Order Paid

Slide 52

Slide 52 text

trait POrderCommand object POrderCommand { case class CreateOrder(orderLines: Seq[POrderLine], info: PShippingInfo) extends POrderCommand with ReplyType[Done] case class PayOrder(reference: String) extends POrderCommand with ReplyType[Done] case object GetOrder extends POrderCommand with ReplyType[POrder] }

Slide 53

Slide 53 text

trait POrderEvent extends AggregateEvent[POrderEvent] { override def aggregateTag: AggregateEventTagger[POrderEvent] = POrderEvent.Tag } object POrderEvent { val Tag = AggregateEventTag[POrderEvent] case class OrderCreated( orderLines: Seq[POrderLine], shippingInformation: PShippingInfo) extends POrderEvent case object OrderConfirmed extends POrderEvent case class OrderPaid(reference: String) extends POrderEvent }

Slide 54

Slide 54 text

case class POrder(id: String, orderLines: Seq[POrderLine], shippingInfo: Option[PShippingInfo], paymentReference: Option[String], confirmed: Boolean) object POrder { case class PShippingInfo(address: String, zipcode: String, city: String, country: String) case class PSKU(id: String) case class POrderLine(sku: PSKU, amount: Int) }

Slide 55

Slide 55 text

class OrderEntity extends PersistentEntity { override type Command = POrderCommand override type Event = POrderEvent override type State = POrder override def initialState = POrder(this.entityId, Seq.empty, None, None, false) override def behavior: Behavior = Actions() .onCommand[POrderCommand.CreateOrder, Done] { case (POrderCommand.CreateOrder(orderLines, shippingInfo), ctx, state) => { ctx.thenPersist(POrderEvent.OrderCreated(orderLines, shippingInfo)) { evt => ctx.reply(Done) } } }.onCommand[POrderCommand.PayOrder, Done] { case (POrderCommand.PayOrder(reference), ctx, state) => { ctx.thenPersistAll(POrderEvent.OrderPaid(reference), POrderEvent.OrderConfirmed) { () => ctx.reply(Done) } } }.onReadOnlyCommand[GetOrder.type, POrder] { case (GetOrder, ctx, state) => ctx.reply(state) }.onEvent { case (POrderEvent.OrderCreated(lines, shippingInfo), state) => state.copy(orderLines = lines, shippingInfo = Some(shippingInfo)) case (POrderEvent.OrderPaid(reference), state) => state.copy(paymentReference = Some(reference)) case (POrderEvent.OrderConfirmed, state) => state.copy(confirmed = true) } } Order

Slide 56

Slide 56 text

Order Order Picked Whenever an Order is Paid Pick Order Order Order Paid Pay Order Order Shipping

Slide 57

Slide 57 text

class OrderEntity extends PersistentEntity { override type Command = POrderCommand override type Event = POrderEvent override type State = POrder override def initialState = POrder(this.entityId, Seq.empty, None, None, false) override def behavior: Behavior = Actions().onCommand[POrderCommand.PayOrder, Done] { case (POrderCommand.PayOrder(reference), ctx, state) => { ctx.thenPersistAll(POrderEvent.OrderPaid(reference), POrderEvent.OrderConfirmed) { () => ctx.reply(Done) } } }.onEvent { case (POrderEvent.OrderPaid(reference), state) => state.copy(paymentReference = Some(reference)) case (POrderEvent.OrderConfirmed, state) => state.copy(confirmed = true) } }

Slide 58

Slide 58 text

class OrderEntity extends PersistentEntity { override type Command = POrderCommand override type Event = POrderEvent override type State = POrder override def initialState = POrder(this.entityId, Seq.empty, None, None, false) override def behavior: Behavior = Actions().onCommand[POrderCommand.PayOrder, Done] { case (POrderCommand.PayOrder(reference), ctx, state) => { ctx.thenPersistAll(POrderEvent.OrderPaid(reference), POrderEvent.OrderConfirmed) { () => ctx.reply(Done) } } }.onEvent { case (POrderEvent.OrderPaid(reference), state) => state.copy(paymentReference = Some(reference)) case (POrderEvent.OrderConfirmed, state) => state.copy(confirmed = true) } }

Slide 59

Slide 59 text

class OrderEntity extends PersistentEntity { override type Command = POrderCommand override type Event = POrderEvent override type State = POrder override def initialState = POrder(this.entityId, Seq.empty, None, None, false) override def behavior: Behavior = Actions().onCommand[POrderCommand.PayOrder, Done] { case (POrderCommand.PayOrder(reference), ctx, state) => { ctx.thenPersistAll(POrderEvent.OrderPaid(reference), POrderEvent.OrderConfirmed) { () => ctx.reply(Done) } } }.onEvent { case (POrderEvent.OrderPaid(reference), state) => state.copy(paymentReference = Some(reference)) case (POrderEvent.OrderConfirmed, state) => state.copy(confirmed = true) } }

Slide 60

Slide 60 text

class OrderEntity extends PersistentEntity { override type Command = POrderCommand override type Event = POrderEvent override type State = POrder override def initialState = POrder(this.entityId, Seq.empty, None, None, false) override def behavior: Behavior = Actions().onCommand[POrderCommand.PayOrder, Done] { case (POrderCommand.PayOrder(reference), ctx, state) => { ctx.thenPersistAll(POrderEvent.OrderPaid(reference), POrderEvent.OrderConfirmed) { () => ctx.reply(Done) } } }.onEvent { case (POrderEvent.OrderPaid(reference), state) => state.copy(paymentReference = Some(reference)) case (POrderEvent.OrderConfirmed, state) => state.copy(confirmed = true) } }

Slide 61

Slide 61 text

State OrderPaid OrderConfirmed OrderUpdated Akka Persistence

Slide 62

Slide 62 text

Event Processor OrderConfirmed OrderPaid OrderChanged OrderCreated Repository (Payment => Order) ID: A ID: B ID: C OrderConfirmed OrderPaid OrderChanged OrderCreated OrderConfirmed OrderPaid OrderChanged OrderCreated

Slide 63

Slide 63 text

Event Processor SEQ: 12 #1 #2 #3 #4 #5 #6 #7 #8 #9 #10 #11 #12 SEQ: 0 ID: A ID: B ID: C OrderConfirmed OrderPaid OrderChanged OrderCreated OrderConfirmed OrderPaid OrderChanged OrderCreated OrderConfirmed OrderPaid OrderChanged OrderCreated Repository (Payment => Order)

Slide 64

Slide 64 text

Event Processor SEQ: 12 #1 #2 #3 #4 #5 #6 #7 #8 #9 #10 #11 #12 SEQ: 12 ID: A ID: B ID: C OrderConfirmed OrderPaid OrderChanged OrderCreated OrderConfirmed OrderPaid OrderChanged OrderCreated OrderConfirmed OrderPaid OrderChanged OrderCreated Repository (Payment => Order)

Slide 65

Slide 65 text

package com.xebia.webshop.order.api import akka.NotUsed import com.lightbend.lagom.scaladsl.api.broker.Topic import com.lightbend.lagom.scaladsl.api.transport._ import com.lightbend.lagom.scaladsl.api.{Descriptor, Service, ServiceCall} trait OrderService extends Service { import Method._ import Service._ def orderEvents: Topic[OrderEvent] final override def descriptor = { named("order").withTopics( topic("order-OrderEvent", orderEvents) ) } }

Slide 66

Slide 66 text

class OrderServiceImpl(persistentEntityRegistry: PersistentEntityRegistry)(implicit ec: ExecutionContext) extends OrderService { override def orderEvents = TopicProducer.singleStreamWithOffset { offset => persistentEntityRegistry.eventStream(POrderEvent.Tag, offset).filter(e => /* Filter some of the internal events to isolate some of the entities internal logic */ e.event == POrderEvent.OrderPaid ).mapAsync(1) { event => event.event match { case x: POrderEvent.OrderPaid => persistentEntityRegistry.refFor[OrderEntity](event.entityId).ask(POrderCommand.GetOrder).map { x => val shippingInfo = x.shippingInfo.getOrElse(throw new NoShippingInfoSetException()) val message = api.OrderEvent.OrderPaid( orderId = event.entityId, orderLines = x.orderLines.map(x => api.Order.OrderLine(api.Order.SKU(x.sku.id), x.amount)), shippingInfo = api.Order.ShippingInfo(shippingInfo.address, shippingInfo.zipcode, shippingInfo.country) ) (message, event.offset) } } } } }

Slide 67

Slide 67 text

Order Shipping Message Broker OrderPaid PickOrder OrderPaid

Slide 68

Slide 68 text

Order Order Picked Whenever an Order is Paid Pick Order Order Order Paid Pay Order Order Shipping

Slide 69

Slide 69 text

Order Shipping Message Broker OrderPaid PickOrder OrderPaid Whenever an Order is Paid

Slide 70

Slide 70 text

class OrderServicePolicy(persistentEntityRegistry: PersistentEntityRegistry, orderService: OrderService) { orderService.orderEvents.subscribe.atLeastOnce(Flow[OrderEvent].mapAsync(1) { case x: OrderPaid => entityRef(x.orderId).ask( PickOrder(x.orderId, PAddress(x.shippingInfo.address, x.shippingInfo.city, x.shippingInfo.address, x.shippingInfo.zipcode) )) case other => Future.successful(Done) }) private def entityRef(orderId: UUID) = persistentEntityRegistry.refFor[POrder](orderId.toString) }

Slide 71

Slide 71 text

Purposes are deduced from behaviour, not from rhetoric or stated goals — Donella H. Meadows

Slide 72

Slide 72 text

Autonomy is reached by implementing policies for behaviour instead of commanding behaviour

Slide 73

Slide 73 text

As long as events are readable on a topic, the origin is of less importance

Slide 74

Slide 74 text

Message Broker Basket Shipping Order Product Stock Legacy Service Gateway Domain Events Product UpdateProduct ProductUpdated / StockChanged Anti Corruption Layer / Data Pumps

Slide 75

Slide 75 text

Message Broker Domain Events Anti Corruption Layer / Data Pumps API Gateway CORE SHOPPING Product Event Command Basket Command Event

Slide 76

Slide 76 text

Message Broker Domain Events Anti Corruption Layer / Data Pumps API Gateway CORE SHOPPING CHECK-OUT SELF-SERVICE Product Event Payment Command Event Shipping Command Event Order Command Event Command Basket Command Event

Slide 77

Slide 77 text

Event-first thinking decouples behaviour, communication from structure

Slide 78

Slide 78 text

REST as an anti-pattern for service to service communication

Slide 79

Slide 79 text

Events-first delivers on a better way of modelling businesses in technology, while getting the rest for “free”

Slide 80

Slide 80 text

Q&A