Slide 1

Slide 1 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. REACT.SPHERE 2018

Slide 2

Slide 2 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. STREAM DRIVEN DEVELOPMENT Design your data pipeline with Akka Streams

Slide 3

Slide 3 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 3 © IKEA

Slide 4

Slide 4 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 4 © RonTech2000/iStock

Slide 5

Slide 5 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. Stefano Bonetti Software Engineer @svezfaz @svez_faz

Slide 6

Slide 6 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 6

Slide 7

Slide 7 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 7 ADVERTISEMENT PLATFORMS

Slide 8

Slide 8 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. EXAMPLE 8 streams

Slide 9

Slide 9 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. EXAMPLE 9 streams

Slide 10

Slide 10 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. STAGES TYPES CODE TESTS

Slide 11

Slide 11 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. TYPES STAGES CODE TESTS

Slide 12

Slide 12 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 12 SOURCE (1 output) FAN-IN (n inputs, 1 output) FAN-OUT (1 input, n outputs) RUNNABLEGRAPH (no input or output) STREAMS STAGES FLOW (1 input, 1 output) SINK (1 input) ... CUSTOM

Slide 13

Slide 13 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 13 Source ~> Flow = Source Flow ~> Sink = Sink Flow ~> Flow = Flow Broadcast ~> Merge = Flow Source ~> Sink = RunnableGraph Source ~> Flow ~> Sink = RunnableGraph STAGE ARITHMETIC

Slide 14

Slide 14 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 14

Slide 15

Slide 15 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 15 Source Sink

Slide 16

Slide 16 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 16 Flow Source Sink

Slide 17

Slide 17 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 17 Flow RunnableGraph Source Sink

Slide 18

Slide 18 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. TYPES STAGES CODE TESTS

Slide 19

Slide 19 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. STAGE SIGNATURE 19 Flow[In, Out, Mat]

Slide 20

Slide 20 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. STAGE SIGNATURE 20 Flow[In, Out, Mat] PORTS

Slide 21

Slide 21 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. STAGE SIGNATURE 21 Flow[In, Out, Mat] MATERIALIZED VALUE

Slide 22

Slide 22 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 22 Flow RunnableGraph Source Sink

Slide 23

Slide 23 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 23 Flow RunnableGraph Source[PropertyChange, NotUsed] Sink

Slide 24

Slide 24 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 24 Flow[PropertyChange, AdwordsChange, NotUsed] RunnableGraph Source[PropertyChange, NotUsed] Sink

Slide 25

Slide 25 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 25 Flow[PropertyChange, AdwordsChange, NotUsed] RunnableGraph Source[PropertyChange, NotUsed] Sink[AdwordsChange, Future[Done]]

Slide 26

Slide 26 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 26 Flow[PropertyChange, AdwordsChange, NotUsed] RunnableGraph[Future[Done]] Source[PropertyChange, NotUsed] Sink[AdwordsChange, Future[Done]]

Slide 27

Slide 27 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 27 Flow[PropertyChange, AdwordsChange, NotUsed] RunnableGraph[Future[Done]] Source[PropertyChange, NotUsed] Sink[AdwordsChange, Future[Done]]

Slide 28

Slide 28 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 28 SIDE EFFECTS Error Handling Logging Monitoring ...

Slide 29

Slide 29 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 29 SIDE EFFECTS Error Handling Logging Monitoring EVENTS Errors Audit ... ...

Slide 30

Slide 30 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 30 SIDE EFFECTS Error Handling Logging Monitoring EVENTS Errors Audit ... ... REFERENTIALLY TRANSPARENT STAGES Flow[In, Either[Error, Out], Mat] Flow[In, (Seq[Audit], Out), Mat] Flow[(Offset,In), (Offset, Out), Mat] ...

Slide 31

Slide 31 text

Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed] Source[PropertyChange, NotUsed] Sink[AdwordsChange, Future[Done]] RunnableGraph[Future[Done]] Sink[ValidationError, NotUsed]

Slide 32

Slide 32 text

Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed] Source[PropertyChange, NotUsed] Flow[AdwordsChange, Either[StorageError, Stored], NotUsed] RunnableGraph[Future[Done]] Sink[Error, NotUsed] Sink[Stored, Future[Done]]

Slide 33

Slide 33 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. CODE TYPES STAGES TESTS

Slide 34

Slide 34 text

Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed] Source[PropertyChange, NotUsed] Flow[AdwordsChange, Either[StorageError, Stored], NotUsed] RunnableGraph[Future[Done]] Sink[Error, NotUsed] Sink[Stored, Future[Done]]

Slide 35

Slide 35 text

def propertySource(config: KafkaConfig): Source[PropertyChange, NotUsed] = { def settings(config: KafkaConfig): ConsumerSettings[String, PropertyChange] = ??? val kafkaSrc: Source[ConsumerRecord[String, PropertyChange], Control] = Consumer.plainSource( settings(config), Subscriptions.topics(config.topic) ) kafkaSrc .map(_.value) .mapMaterializedValue { _ ⇒ NotUsed } } SOURCE

Slide 36

Slide 36 text

def propertySource(config: KafkaConfig): Source[PropertyChange, NotUsed] = { def settings(config: KafkaConfig): ConsumerSettings[String, PropertyChange] = ??? val kafkaSrc: Source[ConsumerRecord[String, PropertyChange], Control] = Consumer.plainSource( settings(config), Subscriptions.topics(config.topic) ) kafkaSrc .map(_.value) .mapMaterializedValue { _ ⇒ NotUsed } } SOURCE - REACTIVE KAFKA

Slide 37

Slide 37 text

def propertySource(config: KafkaConfig): Source[PropertyChange, NotUsed] = { def settings(config: KafkaConfig): ConsumerSettings[String, PropertyChange] = ??? val kafkaSrc: Source[ConsumerRecord[String, PropertyChange], Control] = Consumer.plainSource( settings(config), Subscriptions.topics(config.topic) ) kafkaSrc .map(_.value) .mapMaterializedValue { _ ⇒ NotUsed } } SOURCE - REACTIVE KAFKA

Slide 38

Slide 38 text

Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed] Source[PropertyChange, NotUsed] Flow[AdwordsChange, Either[StorageError, Stored], NotUsed] RunnableGraph[Future[Done]] Sink[Error, NotUsed] Sink[Stored, Future[Done]]

Slide 39

Slide 39 text

def processingFlow(): Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed] = { val service: PropertyProcessingService = ??? Flow.fromFunction(service.process) } PROCESSING FLOW

Slide 40

Slide 40 text

def processingFlow(): Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed] = { val validationService : PropertyValidationService = ??? val transformationService: PropertyTransformationService = ??? Flow.fromFunction(validationService.validate) .async .map(transformationService.transform) } PROCESSING FLOW

Slide 41

Slide 41 text

Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed] Source[PropertyChange, NotUsed] Flow[AdwordsChange, Either[StorageError, Stored], NotUsed] RunnableGraph[Future[Done]] Sink[Error, NotUsed] Sink[Stored, Future[Done]]

Slide 42

Slide 42 text

STORING FLOW def adwordsFlow(config: AdwordsConfig): Flow[AdwordsChange, Either[StorageError, Stored], NotUsed] = { val service: AdwordsService = ??? Flow[AdwordsChange].mapAsync(config.parallelism)(service.store) }

Slide 43

Slide 43 text

STORING FLOW - WITH THROTTLING def adwordsFlow(config: AdwordsConfig): Flow[AdwordsChange, Either[StorageError, Stored], NotUsed] = { val service: AdwordsService = ??? Flow[AdwordsChange] .throttle(50, per = 1.second, maximumBurst = 50, mode = Shaping) .mapAsync(config.parallelism)(service.store) }

Slide 44

Slide 44 text

STORING FLOW - WITH BATCHING def adwordsFlow(config: AdwordsConfig): Flow[AdwordsChange, Either[StorageError, Stored], NotUsed] = { val service: AdwordsService = ??? Flow[AdwordsChange] .batch(max = 5000, seed = List(_))(_ :+ _) .throttle(50, per = 1.second, maximumBurst = 50, mode = Shaping) .mapAsync(config.parallelism)(service.store) }

Slide 45

Slide 45 text

Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed] Source[PropertyChange, NotUsed] Flow[AdwordsChange, Either[StorageError, Stored], NotUsed] RunnableGraph[Future[Done]] Sink[Error, NotUsed] Sink[Stored, Future[Done]]

Slide 46

Slide 46 text

ERROR SINK def errorSink(cfg: ErrorConfig): Sink[AdwordsStreamError, NotUsed] = { val service: AdwordsErrorService = ??? Sink.foreach[AdwordsStreamError](service.handle) .mapMaterializedValue(_ ⇒ NotUsed) }

Slide 47

Slide 47 text

Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed] Source[PropertyChange, NotUsed] Flow[AdwordsChange, Either[StorageError, Stored], NotUsed] RunnableGraph[Future[Done]] Sink[Error, NotUsed] Sink[Stored, Future[Done]]

Slide 48

Slide 48 text

def graph(source : Source[PropertyChange, NotUsed], process : Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed], store : Flow[AdwordsChange, Either[StorageError, Stored], NotUsed], errorSink: Sink[AdwordsStreamError, NotUsed]): RunnableGraph[Future[Done]] = { val processAndDivert: Flow[PropertyChange, AdwordsChange, NotUsed] = process via divertErrors(to = errorSink) val storeAndDivert: Flow[AdwordsChange, Stored, NotUsed] = store via divertErrors(to = errorSink) source .via(processAndDivert) .via(storeAndDivert) .toMat(Sink.ignore)(Keep.right) } GRAPH

Slide 49

Slide 49 text

def graph(source : Source[PropertyChange, NotUsed], process : Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed], store : Flow[AdwordsChange, Either[StorageError, Stored], NotUsed], errorSink: Sink[AdwordsStreamError, NotUsed]): RunnableGraph[Future[Done]] = { val processAndDivert: Flow[PropertyChange, AdwordsChange, NotUsed] = process via divertErrors(to = errorSink) val storeAndDivert: Flow[AdwordsChange, Stored, NotUsed] = store via divertErrors(to = errorSink) source .via(processAndDivert) .via(storeAndDivert) .toMat(Sink.ignore)(Keep.right) } GRAPH

Slide 50

Slide 50 text

def graph(source : Source[PropertyChange, NotUsed], process : Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed], store : Flow[AdwordsChange, Either[StorageError, Stored], NotUsed], errorSink: Sink[AdwordsStreamError, NotUsed]): RunnableGraph[Future[Done]] = { val processAndDivert: Flow[PropertyChange, AdwordsChange, NotUsed] = process via divertErrors(to = errorSink) val storeAndDivert: Flow[AdwordsChange, Stored, NotUsed] = store via divertErrors(to = errorSink) source .via(processAndDivert) .via(storeAndDivert) .toMat(Sink.ignore)(Keep.right) } GRAPH

Slide 51

Slide 51 text

def graph(source : Source[PropertyChange, NotUsed], process : Flow[PropertyChange, Either[ValidationError, AdwordsChange], NotUsed], store : Flow[AdwordsChange, Either[StorageError, Stored], NotUsed], errorSink: Sink[AdwordsStreamError, NotUsed]): RunnableGraph[Future[Done]] = { val processAndDivert: Flow[PropertyChange, AdwordsChange, NotUsed] = process via divertErrors(to = errorSink) val storeAndDivert: Flow[AdwordsChange, Stored, NotUsed] = store via divertErrors(to = errorSink) source .via(processAndDivert) .via(storeAndDivert) .toMat(Sink.ignore)(Keep.right) } GRAPH

Slide 52

Slide 52 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 52 Flow[Either[E,T], T, M] Sink[E, M] Either[E, T] T E DIVERT ERRORS

Slide 53

Slide 53 text

def divertErrors[T, E, M](sink: Sink[E, M]): Flow[Either[E, T], T, M] = Flow[Either[E, T]] .divertToMat(sink.contramap(_.left.get), when = _.isLeft)(Keep.right) .map(_.right.get) GRAPH - DIVERT ERRORS

Slide 54

Slide 54 text

def divertErrors[T, E, M](to: Sink[E, M]): Flow[Either[E, T], T, M] = { Flow.fromGraph(GraphDSL.create(to) { implicit b ⇒ sink ⇒ val partition = b.add(Partition[Either[E, T]](2, _.fold(_ ⇒ 0, _ ⇒ 1))) val left = b.add(Flow[Either[E, T]].map (_.left.get)) val right = b.add(Flow[Either[E, T]].map (_.right.get)) partition ~> left ~> sink partition ~> right FlowShape(partition.in, right.out) }) } GRAPH - DIVERT ERRORS (ante 2.5.10)

Slide 55

Slide 55 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 55 PropertyProcessingService def process(p: PropertyChange): Either[ValidationError, AdwordsChange] PropertyChange AdwordsService def store(p: AdwordsChange): Future[Either[StorageError, Stored]] DOMAIN ValidationError AdwordsChange Stored StorageError AdwordsErrorService def handle(p: AdwordsError): Unit

Slide 56

Slide 56 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 56 APPLICATION Failure Handling Config Management Materialization Stage Creation

Slide 57

Slide 57 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 57 Application layer Graph Events Services Domain layer Repositories Factories Sources Flows Sinks Other Reactive layer

Slide 58

Slide 58 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. TESTS STAGES CODE TYPES

Slide 59

Slide 59 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 59 Application Domain TEST PYRAMID E2E Domain Tests (unit / integration) Graph Other Stages Stages Tests Graph Tests

Slide 60

Slide 60 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 60 Application Domain TEST PYRAMID E2E Domain Tests (unit / integration) Graph Other Stages Stages Tests Graph Tests

Slide 61

Slide 61 text

"application" should "run the stream" in { Application.run() eventually { // verify side-effects } } END TO END TEST

Slide 62

Slide 62 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 62 Application Domain TEST PYRAMID E2E Domain Tests (unit / integration) Graph Other Stages Stages Tests Graph Tests

Slide 63

Slide 63 text

"graph" should "connect the provided stages" in { val changesOfMixedValidity: List[PropertyChange] = ??? val source = Source(changesOfMixedValidity) val processingFlow = Flow[PropertyChange].map { ??? } val storingFlow = Flow[AdwordsChange].map { ??? } val errorSink = Flow[Error].map { ??? }.to(Sink.ignore) Graph(source, processingFlow, storingFlow, errorSink).run().futureValue // verify side-effects } GRAPH TEST

Slide 64

Slide 64 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. 64 Application Domain TEST PYRAMID E2E Domain Tests (unit / integration) Graph Other Stages Stages Tests Graph Tests

Slide 65

Slide 65 text

"property change source" should "read events from Kafka" in { val change: PropertyChange = ??? val config: KafkaConfig = ??? val kafka = new EmbeddedKafka(config) kafka.publish(config.topic, change) val source = PropertySource(config) source.runWith(TestSink.probe) .request(1) .expectNext(change) } STAGE TEST - SOURCE

Slide 66

Slide 66 text

"property change source" should "read events from Kafka" in { val change: PropertyChange = ??? val config: KafkaConfig = ??? val kafka = new EmbeddedKafka(config) kafka.publish(config.topic, change) val source = PropertySource(config) source.runWith(TestSink.probe) .request(1) .expectNext(change) } STAGE TEST - SOURCE

Slide 67

Slide 67 text

"property change source" should "read events from Kafka" in { val change: PropertyChange = ??? val config: KafkaConfig = ??? val kafka = new EmbeddedKafka(config) kafka.publish(config.topic, change) val source = PropertySource(config) val result: Future[PropertyChange] = source.runWith(Sink.head) result.futureValue shouldBe Some(change) } STAGE TEST - SOURCE

Slide 68

Slide 68 text

"property change source" should "read events from Kafka" in { val change: PropertyChange = ??? val config: KafkaConfig = ??? val kafka = new EmbeddedKafka(config) kafka.publish(config.topic, change) val source = PropertySource(config) val result: Future[Seq[PropertyChange]] = source.take(2) .runWith(Sink.seq) result.futureValue shouldBe Seq(change1, change2) } STAGE TEST - SOURCE

Slide 69

Slide 69 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. STAGES TYPES CODE TESTS

Slide 70

Slide 70 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. TYPES STAGES CODE TESTS

Slide 71

Slide 71 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. TYPES STAGES CODE TESTS

Slide 72

Slide 72 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. CODE TYPES STAGES TESTS

Slide 73

Slide 73 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. TESTS STAGES CODE TYPES

Slide 74

Slide 74 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. STAGES TYPES CODE TESTS

Slide 75

Slide 75 text

© 2018 HOMEAWAY. ALL RIGHTS RESERVED. Stefano Bonetti Software Engineer @svezfaz @svez_faz Thank you. homeaway.com/careers