Slide 1

Slide 1 text

Full Stack Reactive Let's see some real-world CQRS and Event Sourcing! — Kevin Webber — Indie Consultant, ex Lightbend/Typesafe w: kevinwebber.ca m: medium.com/@kvnwbbr t: @kvnwbbr

Slide 2

Slide 2 text

Why? — React, Vue, and other front-end frameworks are enabling innovative user experiences

Slide 3

Slide 3 text

Why? — React, Vue, and other front-end frameworks are enabling innovative user experiences — Meanwhile, relational databases continue to dominate the server side

Slide 4

Slide 4 text

Why? — React, Vue, and other front-end frameworks are enabling innovative user experiences — Meanwhile, relational databases continue to dominate the server side — Reactive principles and frameworks help create an end-to-end event-driven, message-driven channel, all the way up and all the way down

Slide 5

Slide 5 text

Traditional architecture — Unbounded in complexity and side effects — Putting React or Vue on top won't help much — Batch-mode, high-latency, high-failure rates — Has an impact — Customer happiness — The ability to hire the best — Visit TheDailyWTF

Slide 6

Slide 6 text

What we will cover 1. Raw ingredients → Commands and Events 2. Structure and modelling → Domain Driven Design 3. Show me the code → Java, Lagom, Play, Akka

Slide 7

Slide 7 text

Events are — Interesting things, that have — happened in the past.

Slide 8

Slide 8 text

Systems have been event driven for centuries!

Slide 9

Slide 9 text

Event sourcing — The one thing missing from event storming models is a root entity — Commands are applied against an entity, the entity can accept or reject a command — If accepted, an entity will generate an event and then apply that event in order to change state

Slide 10

Slide 10 text

Defining aggregate boundaries — Structures implementation around related items — The yellow sticky in the middle represents state

Slide 11

Slide 11 text

Defining bounded contexts — Group together related business concepts — Contain domain complexity — A single person can master an aggregate — A single team can master a bounded context

Slide 12

Slide 12 text

Reactive Stock Trader — Reference application for Reactive in Practice — Vue, Lagom, Play, Akka, Cassandra, Kafka, Kubernetes — Bounded contexts — Portfolio — Broker — Wire transfers — https://github.com/RedElastic/reactive-stock-trader

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Interface architecture — Backend for Frontend (BFF) is a pattern that involves creating a 1:1 relationship between backends and UIs — The BFF then handles all calls to various underlying microservices (Portfolio, Broker, Wires, etc) — Also performs authentication, authorization, etc

Slide 15

Slide 15 text

CQRS for services — Play framework serves as BFF — Adapts and routes all requests to Lagom microservices — Lagom is a framework built on top of Akka for developing reactive microservices — Based on the principles of CQRS (Command Query Responsibility Segregation)

Slide 16

Slide 16 text

Placing a wire transfer We'll cover the command channel first. 1. Create command 2. Send over REST 3. Direct command to entity 4. Create event 5. Change state 6. Publish event

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Submit transfer request (Vue.js) onSubmit() { this.submitted = true; submitTransfer(this.form) .then(() => { this.submitted = false; this.onReset(); }); },

Slide 19

Slide 19 text

Play (command side) @Override public ServiceCall transferFunds() { TransferId transferId = TransferId.newId(); return transfer -> transferRepository .get(transferId) // 1 (get reference to entity) .ask(TransferCommand.TransferFunds.builder() .source(transfer.getSourceAccount()) .destination(transfer.getDestinationAccount()) .amount(transfer.getFunds()) .build() ) // 2 (ask pattern) .thenApply(done -> transferId); }

Slide 20

Slide 20 text

Lagom (command side) — Based around persistent entities — Backed by Cassandra as the event log public class TransferEntity extends PersistentEntity> { // ... }

Slide 21

Slide 21 text

Lagom (event sourcing) — No mutable data, only immutable state — State is only changed on successful event application — Events are journaled — Entity can always recover in- memory by replaying events from the journal — Entities are not queried directly, read-side queries are backed by views

Slide 22

Slide 22 text

Lagom (event sourcing) private Behavior empty() { BehaviorBuilder builder = newBehaviorBuilder(Optional.empty()); builder.setCommandHandler(TransferCommand.TransferFunds.class, (cmd, ctx) -> { // 1 (handler is invoked from ask pattern) TransferDetails transferDetails = TransferDetails.builder() .source(cmd.getSource()) .destination(cmd.getDestination()) .amount(cmd.getAmount()) .build(); // 2 (build up details of xfer) ObjectMapper mapper = new ObjectMapper(); TransferUpdated tc = buildTransferUpdated( transferDetails, "Transfer Initiated"); publishedTopic.publish( mapper.valueToTree(tc).toString()); // 3 (publish to stream) return ctx.thenPersist( new TransferEvent .TransferInitiated( getTransferId(), transferDetails), evt -> ctx.reply(Done.getInstance()) ); // 4 (persist event & return ID) }); builder.setEventHandlerChangingBehavior( TransferEvent.TransferInitiated.class, this::fundsRequested); // 5 (change state) return builder.build(); }

Slide 23

Slide 23 text

Lagom architecture

Slide 24

Slide 24 text

Streaming 1. Render initial page with precomputed view over HTTP/ REST 2. Switch to unidirectional streaming for updates (events over WS) 3. Commands over REST will still cause full page refreshes (can change unidirectional stream to bidi stream in future)

Slide 25

Slide 25 text

Streaming (Lagom) This code exposes a Reactive Streams Source via Lagom, for Play to attach to. @Override public ServiceCall> transferStream() { return request -> { // subscribe to events on a specific topic ("transfer") final PubSubRef topic = pubSub.refFor( TopicId.of(String.class, "transfer")); // return the Source as a future (standard async Java 8) return CompletableFuture.completedFuture(topic.subscriber()); }; }

Slide 26

Slide 26 text

Streaming (architecture) Pub-sub works by broadcasting events to subscribers. — Publisher is TransferEntity — Subscriber is WireTransferServiceImpl — This will create a streaming Source

Slide 27

Slide 27 text

Play Streaming Create a materialized graph all the way down.

Slide 28

Slide 28 text

WireTransferController public WebSocket ws() { return WebSocket.Text.acceptOrResult(req -> { return wireTransferService .transferStream() .invoke() .thenApply(source -> { return F.Either.Right( Flow.fromSinkAndSource( Sink.ignore(), source ) ); }); }); }

Slide 29

Slide 29 text

WebSockets (Vue.js) connect() { this.socket = new WebSocket( "ws://localhost:9000/api/transfer/stream"); this.socket.onopen = () => { this.socket.onmessage = (e) => { let event = JSON.parse(e.data); var index = -1; // 1. determine if we're updating a row (initiated) // or adding a new row (completed) for (var i = 0; i < this.transfers.length; i++) { if (this.transfers[i].id === event.id) { index = i; break; } } if (index === -1) { // unshift is similar to push, but prepends this.transfers.unshift({ // ... 3. create object with id, status, etc }); } else { let t = { // ... 4. create object with id, status, etc }; this.transfers.splice(index, 1, t); this.updateCashOnHand(); } }; }; }

Slide 30

Slide 30 text

What about views? — Read side processors build a precomputed table — Queries are only against precomputed tables — Use this to populate data on initial page load

Slide 31

Slide 31 text

We need to build the data pump using Lagom read side processors.

Slide 32

Slide 32 text

Lagom ReadSide processor Update a precomputed query table (Cassandra) on every new event we subscribe to. @Override public ReadSideHandler buildHandler() { return readSide.builder("portfolio_offset") // 1 .setGlobalPrepare(this::prepareCreateTables) // 2 .setPrepare(tag -> prepareWritePortfolios()) // 3 .setEventHandler(Opened.class, this::processPortfolioChanged) // 4 .build(); } private CompletionStage prepareWritePortfolios() { return session .prepare("INSERT INTO portfolio_summary (portfolioId, name) VALUES (?, ?)") .thenApply(ps -> { this.writePortfolios = ps; return Done.getInstance(); }); }

Slide 33

Slide 33 text

Queries are then executed against the precomputed table @Override public ServiceCall> getAllPortfolios() { return request -> { CompletionStage> result = db.selectAll("SELECT portfolioId, name FROM portfolio_summary;") .thenApply(rows -> { List summary = rows.stream().map(row -> PortfolioSummary.builder() .portfolioId(new PortfolioId( row.getString("portfolioId"))) .name(row.getString("name")) .build()) .collect(Collectors.toList()); return TreePVector.from(summary); }); return result; }; }

Slide 34

Slide 34 text

Building the read-side

Slide 35

Slide 35 text

Read side queries

Slide 36

Slide 36 text

Conclusion — CQRS separates writes and reads for reliability and performance — Event sourcing eliminates the mutability of relational databases — Operationally Lagom is cloud- native and ready to deploy to AWS, Azure, GCP, etc, via Kubernetes

Slide 37

Slide 37 text

Reactive in Practice For a complete look at this material, visit IBM Developer at developer.ibm.com and check out Reactive in Practice, a 12 part series. — Twitter: @kvnwbbr — Web: kevinwebber.ca — Medium: medium.com/ @kvnwbbr