Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Events-First Microservices with Lagom - DDDeXchange

Events-First Microservices with Lagom - DDDeXchange

Digital transformations are the new hype. By switching to a more IT-oriented organization, businesses hope to achieve a new level of flexibility to answer better to the fast changing demands of customers. Companies often start their new customer-driven strategy with a big-bang agile transformation in the hope of a quicker time-to-market. But can companies have a significant improvement from this transformation with their (still) rigid software architectures?

Domain Driven Design and Microservices are ways of designing systems in which Conway's law can be used to a business' advantage. With the goal of instant improvement of the business and its flexibility, a big-bang strategy for an (agile) architectural transformation isn't a good fit.

Kenny and Gideon will share with you a strategy for the disentanglement of monoliths during a hands-on session; using event-first DDD they'll start modelling a business and will use Lagom to incrementally transform the senders and receivers of those events. Not only does this technology and strategy ensure a less-entangled system which will improve the autonomy of software development teams. The level of disentanglement also ensures that old and new technology can co-exist with ease during the migration and full life-cycle of your architecture.

Kenny Baas-Schwegler

April 27, 2018
Tweet

More Decks by Kenny Baas-Schwegler

Other Decks in Technology

Transcript

  1. 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
  2. “Any organization that designs a system will produce a design

    whose structure is a copy of the organization's communication structure.”
 
 — Melvin Conway, 1967
  3. Design is not just what it looks like and feels

    like. Design is how it works.
  4. It is not the things that matter (in early stages

    of design). It is the things that happen — Russ Miles
  5. 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]) }
  6. 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]) }
  7. Lagom provides an opinionated way of building microservices that intentionally

    constrains what a developer can do and how they should do it.
  8. 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) ) } }
  9. 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) ) } }
  10. Order Order Picked Whenever an Order is Paid Pick Order

    Order Order Paid Pay Order Order Shipping
  11. 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] }
  12. 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 }
  13. 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) }
  14. 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
  15. Order Order Picked Whenever an Order is Paid Pick Order

    Order Order Paid Pay Order Order Shipping
  16. 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) } }
  17. 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) } }
  18. 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) } }
  19. 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) } }
  20. Event Processor OrderConfirmed OrderPaid OrderChanged OrderCreated Repository (Payment => Order)

    ID: A ID: B ID: C OrderConfirmed OrderPaid OrderChanged OrderCreated OrderConfirmed OrderPaid OrderChanged OrderCreated
  21. 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)
  22. 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)
  23. 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) ) } }
  24. 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) } } } } }
  25. Order Order Picked Whenever an Order is Paid Pick Order

    Order Order Paid Pay Order Order Shipping
  26. 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) }
  27. As long as events are readable on a topic, the

    origin is of less importance
  28. Message Broker Basket Shipping Order Product Stock Legacy Service Gateway

    Domain Events Product UpdateProduct ProductUpdated / StockChanged Anti Corruption Layer / Data Pumps
  29. Message Broker Domain Events Anti Corruption Layer / Data Pumps

    API Gateway CORE SHOPPING Product Event Command Basket Command Event
  30. 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
  31. Events-first delivers on a better way of modelling businesses in

    technology, while getting the rest for “free”
  32. Q&A