-
Notifications
You must be signed in to change notification settings - Fork 13
Add an Akka Stream Sink graph stage for Kinesis. #47
Changes from 10 commits
7adf700
62cba2a
2c9ef82
4bc1e93
34d638b
a0f50f3
1d7e39b
5f34f19
a0d31bc
0c64c97
bc2f91f
f8730d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ It's worth familiarising yourself with [Sequence numbers and Sub sequence number | |
* [Usage: Producer](#usage-usage-producer) | ||
* [Actor Based Implementation](#usage-usage-producer-actor-based-implementation) | ||
* [Pure Scala based implementation (simple wrapper around KPL)](#usage-usage-producer-pure-scala-based-implementation-simple-wrapper-around-kpl) | ||
* [Akka Stream Sink](#akka-stream-sink) | ||
* [Running the reliability test](#running-the-reliability-test) | ||
* [Delete & recreate kinesisstreams and dynamo table](#running-the-reliability-test-delete-recreate-kinesisstreams-and-dynamo-table) | ||
* [Running the producer-consumer test](#running-the-reliability-test-running-the-producer-consumer-test) | ||
|
@@ -484,6 +485,55 @@ callback onFailure { | |
} | ||
``` | ||
|
||
<a name="akka-stream-sink"></a> | ||
### Akka Stream Sink | ||
|
||
An Akka `Sink` is provided which can be used to publish messages via streams. | ||
Every message is send as `ProduserEvent` to the `Sink`, which defines the PartitionKey as well as the payload. | ||
The `Sink` is created from a `ProducerConf` or directly with a `KinesisProducerActor`. See [Kinesis](https://github.com/WW-Digital/reactive-kinesis/blob/master/src/main/scala/com/weightwatchers/reactive/kinesis/stream/Kinesis.scala) for the various options. | ||
|
||
The `Sink` expects an acknowledgement for every messages send to Kinesis. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
An amount of unacknowledged messages can be configured, before back pressure is applied. | ||
See the throttling conf for defining this configuration value. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be clearer as: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed much better. |
||
Please note: a default value (1000 messages) is applied, if throttling is not configured. | ||
|
||
The provided `Sink` produces a `Future[Done]` as materialized value. | ||
This future succeeds, if all messages from upstream are send to Kinesis and acknowledged. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just swap out There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
It fails if a message could not be send to Kinesis or upstream fails. | ||
|
||
```scala | ||
import akka.stream.scaladsl.Source | ||
import com.weightwatchers.reactive.kinesis.models._ | ||
import com.weightwatchers.reactive.kinesis.stream._ | ||
|
||
Source(1.to(100).map(_.toString)) | ||
.map(num => ProducerEvent(num, num)) | ||
.runWith(Kinesis.sink("producer-name")) | ||
.onComplete { | ||
case Success(_) => println("All messages are published successfully.") | ||
case Failure(ex) => println(s"Failed to publish messages: ${ex.getMessage}") | ||
} | ||
``` | ||
|
||
A long running flow can be easily achieved using a `SourceQueue`. | ||
In this case the flow stays open as long as needed. | ||
New elements can be published via the materialized queue: | ||
|
||
```scala | ||
import akka.stream.scaladsl.Source | ||
import com.weightwatchers.reactive.kinesis.models._ | ||
import com.weightwatchers.reactive.kinesis.stream._ | ||
|
||
val sourceQueue = Source.queue[ProducerEvent](1000, OverflowStrategy.fail) | ||
.toMat(Kinesis.sink("producer-name"))(Keep.left) | ||
.run() | ||
|
||
sourceQueue.offer(ProducerEvent("foo", "bar")) | ||
sourceQueue.offer(ProducerEvent("foo", "baz")) | ||
``` | ||
|
||
The `Sink` uses a `KinesisProducerActor` under the cover. All rules regarding this actor also apply for the `Sink`. | ||
|
||
|
||
<a name="running-the-reliability-test"></a> | ||
# Running the reliability test | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.weightwatchers.reactive.kinesis.stream | ||
|
||
import akka.stream.scaladsl.Source | ||
import com.weightwatchers.reactive.kinesis.common.{ | ||
AkkaUnitTestLike, | ||
KinesisConfiguration, | ||
KinesisSuite | ||
} | ||
import com.weightwatchers.reactive.kinesis.models.ProducerEvent | ||
import org.scalatest.{FreeSpec, Matchers} | ||
|
||
import scala.concurrent.duration._ | ||
|
||
class KinesisSinkGraphStageIntegrationSpec | ||
extends FreeSpec | ||
with KinesisSuite | ||
with KinesisConfiguration | ||
with AkkaUnitTestLike | ||
with Matchers { | ||
|
||
"KinesisSinkGraph" - { | ||
|
||
"produced messages are written to the stream" in new withKinesisConfForApp("sink_produce") { | ||
val messageCount = 100 | ||
val elements = 1.to(messageCount).map(_.toString) | ||
Source(elements) | ||
.map(num => ProducerEvent(num, num)) | ||
.runWith(Kinesis.sink(producerConf())) | ||
.futureValue | ||
val list = testConsumer.retrieveRecords(TestStreamName, messageCount) | ||
list should contain allElementsOf elements | ||
testConsumer.shutdown() | ||
} | ||
|
||
"upstream fail should fail the materialized value of the sink" in new withKinesisConfForApp( | ||
"sink_fail" | ||
) { | ||
Source | ||
.failed(new IllegalStateException("Boom")) | ||
.runWith(Kinesis.sink(producerConf())) | ||
.failed | ||
.futureValue shouldBe a[IllegalStateException] | ||
} | ||
} | ||
|
||
// do not create messages in setup, we will create messages inside the test | ||
override def TestStreamNrOfMessagesPerShard: Long = 0 | ||
override implicit def patienceConfig: PatienceConfig = PatienceConfig(60.seconds, 1.second) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiny grammar correction:
Every message is sent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grammar my old enemy - thanks for correction.