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

Reactive Systems with Lagom Framework

Reactive Systems with Lagom Framework

Eduardo Costa

November 07, 2017
Tweet

More Decks by Eduardo Costa

Other Decks in Programming

Transcript

  1. CQRS Event Sourcing Streams Circuit Breaker Service Locator Message Broker

    Domain Driven Design Kafka Cassandra Back Pressure API Gateway Embedded concepts and technologies
  2. Service Descriptor trait ItemService extends Service { def createItem: ServiceCall[Item,

    Item] def startAuction(id: UUID): ServiceCall[NotUsed, Done] def getItem(id: UUID): ServiceCall[NotUsed, Item] def getItemsForUser(id: UUID, status: ItemStatus.Status, limit: Option[Int], offset: Option[Int]): ServiceCall[NotUsed, PaginatedSequence[ItemSummary]] final override def descriptor = { import Service._ named("item").withCalls( pathCall("/api/item", createItem), restCall(Method.POST, "/api/item/:id/start", startAuction _), pathCall("/api/item/:id", getItem _), pathCall("/api/item?userId&status&limit&offset", getItemsForUser _) )
  3. Implementing services class ItemServiceImpl(registry: PersistentEntityRegistry, itemRepository: ItemRepository) (implicit ec: ExecutionContext)

    extends ItemService { override def startAuction(id: UUID) = authenticated(userId => ServerServiceCall { _ => entityRef(id).ask(StartAuction(userId)) }) override def getItem(id: UUID) = ServerServiceCall { _ => entityRef(id).ask(GetItem).map { case Some(item) => convertItem(item) case None => throw NotFound(s"Item $id not found") } } private def entityRef(itemId: UUID) = registry.refFor[ItemEntity](itemId.toString)
  4. Circuit-Breaker named("item").withCalls( pathCall("/api/item", createItem), restCall(Method.POST, "/api/item/:id/start", startAuction _), pathCall("/api/item/:id", getItem

    _), pathCall("/api/item?userId&status&pageNo&pageSize", getItemsForUser _) ).withCircuitBreaker(CircuitBreaker.identifiedBy("item-resources"))
  5. Circuit-Breaker lagom.circuit-breaker { # Default configuration that is used if

    a configuration section # with the circuit breaker identifier is not defined. default { # Possibility to disable a given circuit breaker. enabled = on # Number of failures before opening the circuit. max-failures = 10 # Duration of time after which to consider a call a failure. call-timeout = 10s # Duration of time in open state after which to attempt to close # the circuit, by first entering the half-open state. reset-timeout = 15s } }
  6. Persistence Entity class AuctionEntity extends PersistentEntity { import AuctionStatus._ override

    type State = AuctionState override type Command = AuctionCommand override type Event = AuctionEvent override def initialState: AuctionState = AuctionState.notStarted override def behavior: Behavior = { case AuctionState(_, NotStarted, _) => notStarted case AuctionState(_, UnderAuction, _) => underAuction case AuctionState(_, Complete, _) => complete case AuctionState(_, Cancelled, _) => cancelled
  7. Persistence Entity trait AuctionCommand case class AuctionState(auction: Option[Auction], status: AuctionStatus.Status,

    biddingHistory: Seq[Bid]) object AuctionState { implicit val format: Format[AuctionState] = Json.format val notStarted = AuctionState(None, AuctionStatus.NotStarted, Nil) def start(auction: Auction) = ??? } trait AuctionEvent extends AggregateEvent[AuctionEvent] { override def aggregateTag: AggregateEventTagger[AuctionEvent] = AuctionEvent.Tag } object AuctionEvent { val Tag = AggregateEventTag.sharded[AuctionEvent](numShards = 4) }
  8. Persistence Entity private val underAuction = { getAuction orElse {

    Actions().onReadOnlyCommand[StartAuction, Done] { case (StartAuction(_), ctx, _) => ctx.reply(Done) }.onCommand[PlaceBid, PlaceBidResult] { case (placeBid: PlaceBid, ctx, state) => handlePlaceBidWhileUnderAuction(placeBid, ctx, state) }.onCommand[FinishBidding.type, Done] { case (FinishBidding, ctx, state) => ctx.thenPersist(BiddingFinished)(_ => ctx.reply(Done)) }.onEvent { case (BidPlaced(bid), state) => state.bid(bid) case (BiddingFinished, state) => state.withStatus(Complete) } } orElse cancelActions }
  9. Persistence Entity private def getAuction = { Actions().onReadOnlyCommand[GetAuction.type, AuctionState] {

    case (GetAuction, ctx, state) => ctx.reply(state) } } private def cancelActions = { Actions() .onCommand[CancelAuction.type, Done] { case (CancelAuction, ctx, _) => ctx.thenPersist(AuctionCancelled)(_ => ctx.reply(Done)) }.onEvent { case (AuctionCancelled, state) => state.withStatus(Cancelled) } }
  10. Read-Side Repository class TransactionRepository(session: CassandraSession) private def selectByStatus(userId: UUID, status:

    Status, offset: Int, limit: Int) = { session.selectAll(""" SELECT * FROM transactionSummaryByUserAndStatus WHERE userId = ? AND status = ? ORDER BY status ASC, itemId DESC LIMIT ? """, userId, status.toString, Integer.valueOf(limit)) map { rows => rows.drop(offset).map(convertTransactionSummary) } } private def convertTransactionSummary(row: Row) = { TransactionSummary( row.getUUID("itemId"), row.getUUID("creatorId"), ///
  11. Read-Side EventProcessor class TransactionEventProcessor(session: CassandraSession, readSide: CassandraReadSide) (implicit ec: ExecutionContext)

    extends ReadSideProcessor[TransactionEvent] { override def buildHandler() = readSide .builder[TransactionEvent]("transactionEventOffset") .setGlobalPrepare(createTables) .setPrepare(_ => prepareStatements()) .setEventHandler[TransactionStarted](e => insertTransaction)) .setEventHandler[DeliveryDetailsApproved](_ => updateStatus(PaymenyPending)) .setEventHandler[PaymentDetailsSubmitted](_ => updateStatus(PaymentSubmitted)) .setEventHandler[PaymentApproved](_ => updateStatus(PaymentConfirmed)) .setEventHandler[PaymentRejected](_ => updateStatus(PaymentPending)) .build
  12. Topics in the service descriptors def itemEvents: Topic[ItemEvent] final override

    def descriptor = { import Service._ named("item").withCalls( pathCall("/api/item/:id", getItem _), ).withTopics(topic("item-ItemEvent", this.itemEvents) .addProperty(KafkaProperties.partitionKeyStrategy, PartitionKeyStrategy[ItemEvent](_.itemId.toString))
  13. Subscribe topics class BrokerEventConsumer(indexedStore: IndexedStore[SearchResult], itemService: ItemService) { itemService .itemEvents

    .subscribe .withGroupId("search-service") .atLeastOnce(Flow[ItemEvent] .map(toDocument) .collect { case Some(x) => x } .mapAsync(parallelism = 4)(indexedStore.store))
  14. Topic producers override def itemEvents = TopicProducer.taggedStreamWithOffset(ItemEvent.Tag.allTags.toList) { (tag, offset)

    => registry .eventStream(tag, offset) .filter { _.event match { case x@(_: ItemCreated | _: AuctionStarted | _: AuctionFinished) => true case _ => false } }.mapAsync(parallelism = 4)(convertEvent)