Propulsion provides a granular suite of .NET NuGet packages for building Reactive event processing pipelines. It caters for:
AwaitCompletion
mechanisms in MemoryStore
and FeedSource
provide a clean way to structure test suites in a manner that achieves high test coverage without flaky tests or slow tests.DynamoStore
-related components implement support for running an end-to-end event sourced system using only Amazon DynamoDB and Lambda without requiring a long-lived host process.If you're looking for a good discussion forum on these kinds of topics, look no further than the DDD-CQRS-ES Discord's #equinox channel (invite link).
Propulsion
Implements core functionality in a channel-independent fashion. Depends on FSharp.Control.TaskSeq
, MathNet.Numerics
, Serilog
:
StreamsSink
: High performance pipeline that handles parallelized event processing. Ingestion of events, and checkpointing of progress are handled asynchronously. Each aspect of the pipeline is decoupled such that it can be customized as desired. Streams.Prometheus
: Helper that exposes per-scheduler metrics for Prometheus scraping.ParallelProjector
: Scaled down variant of StreamsSink
that does not preserve stream level ordering semanticsFeedSource
: Handles continual reading and checkpointing of events from a set of feeds ('tranches') of a 'source' that collectively represent a change data capture source for a given system (roughly analogous to how a CosmosDB Container presents a changefeed). A readTranches
function is used to identify the Tranches (sub-feeds) on startup. The Feed Source then operates a logical reader thread per Tranche. Tranches represent content as an incrementally retrievable change feed consisting of batches of FsCodec.ITimelineEvent
records. Each batch has an optional associated checkpointing callback that's triggered only when the Sink has handled all events within it.Monitor.AwaitCompletion
: Enables efficient waiting for completion of reaction processing within an integration test.PeriodicSource
: Handles regular crawling of an external datasource (such as a SQL database) where there is no way to save progress and then resume from that saved token (based on either the intrinsic properties of the data, or of the store itself). The source is expected to present its content as an IAsyncEnumerable
of FsCodec.StreamName * FsCodec.IEventData * context
. Checkpointing occurs only when all events have been deemed handled by the Sink.SinglePassFeedSource
: Handles single pass loading of large datasets (such as a SQL database), completing when the full data has been ingested.JsonSource
: Simple source that feeds items from a File containing JSON (such a file can be generated via eqx query -o JSONFILE from cosmos
etc)NOTE Propulsion.Feed
is a namespace within the main Propulsion
package that provides helpers for checkpointed consumption of a feed of stream-based inputs.
IFeedCheckpointStore
from e.g., Propulsion.CosmosStore|DynamoStore|MessageDb|SqlStreamStore
Propulsion.Prometheus
Provides helpers for checkpointed consumption of a feed of stream-based inputs. Provides for custom bindings (e.g. a third-party Feed API) or various other input configurations (e.g. periodically correlating with inputs from a non-streamed source such as a SQL Database). Provides a generic API for checkpoint storage, with diverse implementations hosted in the sibling packages associated with each concrete store (supported stores include DynamoStore, CosmosStore, SQL Server, Postgres). Depends on Propulsion
, a IFeedCheckpointStore
implementation (from e.g., Propulsion.CosmosStore|DynamoStore|MessageDb|SqlStreamStore
)
Propulsion.Prometheus
: Exposes processing throughput statistics to Prometheus.Propulsion.Feed.Prometheus
: Exposes reading statistics to Prometheus (including metrics from DynamoStore.DynamoStoreSource
, EventStoreDb.EventStoreSource
, MessageDb.MessageDbSource
and SqlStreamStore.SqlStreamStoreSource
). Propulsion.MemoryStore
. Provides bindings to Equinox.MemoryStore
. Depends on Equinox.MemoryStore
v 4.0.0
, FsCodec.Box
, Propulsion
MemoryStoreSource
: Presents a Source that adapts an Equinox.MemoryStore
to feed into a Propulsion.Sink
. Typically used as part of an overall test suite to enable efficient and deterministic testing where reactions are relevant to a given scenario.Monitor.AwaitCompletion
: Enables efficient deterministic waits for Reaction processing within integration or unit tests.ReaderCheckpoint
: ephemeral checkpoint storage for Propulsion.DynamoStore
/EventStoreDb
/Feed
/MessageDb
/SqlStreamSteamStore
in test contexts.Propulsion.CosmosStore
Provides bindings to Azure CosmosDB. Depends on Equinox.CosmosStore
v 4.0.0
CosmosStoreSource
: reading from CosmosDb's ChangeFeed using Microsoft.Azure.Cosmos
CosmosStoreSink
: writing to Equinox.CosmosStore
v 4.0.0
.CosmosStorePruner
: pruning from Equinox.CosmosStore
v 4.0.0
.ReaderCheckpoint
: checkpoint storage for Propulsion.DynamoStore
/EventStoreDb
/Feed
/MessageDb
/SqlStreamSteamStore
using Equinox.CosmosStore
v 4.0.0
.(Reading and position metrics are exposed via Propulsion.CosmosStore.Prometheus
)
Propulsion.DynamoStore
Provides bindings to Equinox.DynamoStore
. Depends on Equinox.DynamoStore
v 4.0.0
AppendsIndex
/AppendsEpoch
: Equinox.DynamoStore
aggregates that together form the DynamoStore IndexDynamoStoreIndexer
: writes to AppendsIndex
/AppendsEpoch
(used by Propulsion.DynamoStore.Indexer
, Propulsion.Tool
)DynamoStoreSource
: reads from AppendsIndex
/AppendsEpoch
(see DynamoStoreIndexer
)ReaderCheckpoint
: checkpoint storage for Propulsion.DynamoStore
/EventStoreDb
/Feed
/MessageDb
/SqlStreamSteamStore
using Equinox.DynamoStore
v 4.0.0
.(Reading and position metrics are exposed via Propulsion.Prometheus
)
Propulsion.DynamoStore.Indexer
AWS Lambda to index appends into an Index Table. Depends on Propulsion.DynamoStore
, Amazon.Lambda.Core
, Amazon.Lambda.DynamoDBEvents
, Amazon.Lambda.Serialization.SystemTextJson
Handler
: parses Dynamo DB Streams Source Mapping input, feeds into Propulsion.DynamoStore.DynamoStoreIndexer
Connector
: Store / environment variables wiring to connect DynamoStoreIndexer
to the Equinox.DynamoStore
Index Event StoreFunction
: AWS Lambda Function that can be fed via a DynamoDB Streams Event Source Mapping; passes to Handler
(Diagnostics are exposed via Console to CloudWatch)
Propulsion.DynamoStore.Notifier
AWS Lambda to report new events indexed by the Indexer to an SNS Topic, in order to enable triggering AWS Lambdas to service Reactions without requiring a long-lived host application. Depends on Amazon.Lambda.Core
, Amazon.Lambda.DynamoDBEvents
, Amazon.Lambda.Serialization.SystemTextJson
, AWSSDK.SimpleNotificationService
Handler
: parses Dynamo DB Streams Source Mapping input, generates a message per updated Partition in the batchFunction
: AWS Lambda Function that can be fed via a DynamoDB Streams Event Source Mapping; passes to Handler
(Diagnostics are exposed via Console to CloudWatch)
Propulsion.DynamoStore.Constructs
AWS Lambda CDK deploy logic. Depends on Amazon.CDK.Lib
(and, indirectly, on the binary assets included as content in the Propulsion.DynamoStore.Indexer
/Propulsion.DynamoStore.Notifier
NuGet packages)
DynamoStoreIndexerLambda
: CDK wiring for Propulsion.DynamoStore.Indexer
DynamoStoreNotifierLambda
: CDK wiring for Propulsion.DynamoStore.Notifier
DynamoStoreReactorLambda
: CDK wiring for a Reactor that's triggered based on messages supplied by Propulsion.DynamoStore.Notifier
Propulsion.DynamoStore.Lambda
Helpers for implementing Lambda Reactors. Depends on Amazon.Lambda.SQSEvents
SqsNotificationBatch.parse
: parses a batch of notification events (queued by a Notifier
) in a Amazon.Lambda.SQSEvents.SQSEvent
SqsNotificationBatch.batchResponseWithFailuresForPositionsNotReached
: Correlates the updated checkpoints with the input SQSEvent
, generating a SQSBatchResponse
that will requeue any notifications that have not yet been serviced.(Used by eqxShipping
template)
Propulsion.EventStoreDb
. Provides bindings to EventStoreDB, writing via Propulsion.EventStore.EventStoreSink
. Depends on Equinox.EventStoreDb
v 4.0.0
EventStoreSource
: reading from an EventStoreDB >= 20.10
$all
stream using the gRPC interface into a Propulsion.Sink
.EventStoreSink
: writing to Equinox.EventStoreDb
v 4.0.0
Propulsion.Kafka
Provides bindings for producing and consuming both streamwise and in parallel. Includes a standard codec for use with streamwise projection and consumption, Propulsion.Kafka.Codec.NewtonsoftJson.RenderedSpan
. Depends on FsKafka
v 1.7.0
-1.9.99
Propulsion.MessageDb
. Provides bindings to MessageDb, maintaining checkpoints in a postgres table Depends on Propulsion
, Npgsql
>= 7.0.7
#181 :pray: @nordfjord
MessageDbSource
: reading from one or more MessageDB categories into a Propulsion.Sink
CheckpointStore
: checkpoint storage for Propulsion.Feed
using Npgsql
(can be initialized via propulsion initpg -c connstr -s schema
)Propulsion.SqlStreamStore
. Provides bindings to SqlStreamStore, maintaining checkpoints in a SQL Server table. Depends on Propulsion
, SqlStreamStore
, Dapper
v 2.0
, Microsoft.Data.SqlClient
v 1.1.3
SqlStreamStoreSource
: reading from a SqlStreamStore $all
stream into a Propulsion.Sink
ReaderCheckpoint
: checkpoint storage for Propulsion.EventStoreDb
/Feed
/SqlStreamSteamStore
using Dapper
, Microsoft.Data.SqlClient
The ubiquitous Serilog
dependency is solely on the core module, not any sinks.
dotnet tool
provisioning / projections test toolPropulsion.Tool
: Tool used to initialize a Change Feed Processor aux
container for Propulsion.CosmosStore
and demonstrate basic projection, including to Kafka. See quickstart.
init
: CosmosDB: Initialize an -aux
Container for use by the CosmosDb client library ChangeFeedProcessorinitpg
: MessageDb: Initialize a checkpoints table in a Postgres Databaseindex
: DynamoStore: validate and/or reindex DynamoStore Indexcheckpoint
: CosmosStore/DynamoStore/EventStoreDb/Feed/MessageDb/SqlStreamStore: adjust checkpoints in DynamoStore/CosmosStore/SQL Server/Postgresproject
: CosmosDB/DynamoStore/EventStoreDb/MessageDb: walk change feeds/indexes and/or project to KafkaPropulsion supports recent versions of Equinox and other Store Clients within reason - these components are intended for use on a short term basis as a way to manage phased updates from older clients to current ones by adjusting package references while retaining source compatibility to the maximum degree possible.
Propulsion.CosmosStore3
Provides bindings to Azure CosmosDB. Depends on Equinox.CosmosStore
v 3.0.7
, Microsoft.Azure.Cosmos
v 3.27.0
CosmosStoreSource
: reading from CosmosDb's ChangeFeed using Microsoft.Azure.Cosmos
(relies on explicit checkpointing that entered GA in 3.21.0
)CosmosStoreSink
: writing to Equinox.CosmosStore
v 3.0.7
.CosmosStorePruner
: pruning from Equinox.CosmosStore
v 3.0.7
.ReaderCheckpoint
: checkpoint storage for Propulsion.EventStoreDb
/DynamoStore
/'Feed'/SqlStreamSteamStore
using Equinox.CosmosStore
v 3.0.7
.(Reading and position metrics are exposed via Propulsion.CosmosStore.Prometheus
)
Propulsion.EventStore
. Provides bindings to EventStore, writing via Propulsion.EventStore.EventStoreSink
Depends on Equinox.EventStore
v 4.0.0
proSync
template(Reading and position metrics are emitted to Console / Serilog; no Prometheus support)
See the Equinox QuickStart for examples of using this library to project to Kafka from Equinox.CosmosStore
, Equinox.DynamoStore
and/or Equinox.EventStoreDb
.
See the dotnet new
templates repo for examples using the packages herein:
Propulsion-specific templates:
proProjector
template for CosmosStoreSource
+StreamsSink
logic consuming from a CosmosDb ChangeFeedProcessor
.proProjector
template (in --kafka
mode) for producer logic using StreamsProducerSink
or ParallelProducerSink
.proConsumer
template for example consumer logic using ParallelConsumer
and StreamsConsumer
etc.eqxShipping
: Event-sourced example with a Process Manager. Includes a Watchdog
component that uses a StreamsSink
, with example wiring for CosmosStore
, DynamoStore
and EventStoreDb
proIndexer
. single-source StreamsSink
based Reactor. More legible version of proReactor
template, currently only supports Propulsion.CosmosStore
, and provides some specific extensions such as updating snapshots.proReactor
generic template, supporting multiple sources and multiple processing modessummaryConsumer
consumes from the output of a proReactor --kafka
, saving them in an Equinox.CosmosStore
storetrackingConsumer
consumes from Kafka, feeding into example Ingester logic in an Equinox.CosmosStore
store proSync
is a fully fledged store <-> store synchronization tool syncing from a CosmosStoreSource
or EventStoreSource
to a CosmosStoreSink
or EventStoreSink
feedConsumer
,feedSource
: illustrating usage of Propulsion.Feed.FeedSource
periodicIngester
: illustrating usage of Propulsion.Feed.PeriodicSource
proArchiver
, proPruner
: illustrating usage of hot/cold support and support for secondary fallback in Equinox.CosmosStore
See the FsKafka
repo for BatchedProducer
and BatchedConsumer
implementations (together with the KafkaConsumerConfig
and KafkaProducerConfig
used in the Parallel and Streams wrappers in Propulsion.Kafka
)
Propulsion and Equinox have a Yin and yang relationship; their use cases naturally interlock and overlap.
See the Equinox Documentation's Overview Diagrams for the perspective from the other side (TL;DR the same topology, with elements that are de-emphasized here central over there, and vice versa)
Equinox focuses on the Consistent Processing element of building an event-sourced decision processing system, offering relevant components that interact with a specific Consistent Event Store. Propulsion elements support the building of complementary facilities as part of an overall Application. Conceptually one can group such processing based on high level roles such as:
The overall territory is laid out here in this C4 System Context Diagram:
See Overview section in DOCUMENTATION
.md for further drill down
propulsion
tool to run a CosmosDb ChangeFeedProcessor or DynamoStoreSource projectordotnet tool uninstall Propulsion.Tool -g
dotnet tool install Propulsion.Tool -g --prerelease
propulsion init -ru 400 cosmos # generates a -aux container for the ChangeFeedProcessor to maintain consumer group progress within
# -V for verbose ChangeFeedProcessor logging
# `-g projector1` represents the consumer group - >=1 are allowed, allowing multiple independent projections to run concurrently
# stats specifies one only wants stats regarding items (other options include `kafka` to project to Kafka)
# cosmos specifies source overrides (using defaults in step 1 in this instance)
propulsion -V sync -g projector1 stats from cosmos
# load events with 2 parallel readers, detailed store logging and a read timeout of 20s
propulsion -VS sync -g projector1 stats from dynamo -rt 20 -d 2
propulsion
tool to Run a CosmosDb ChangeFeedProcessor or DynamoStoreSource projector, emitting to a Kafka topic$env:PROPULSION_KAFKA_BROKER="instance.kafka.mysite.com:9092" # or use -b
# `-V` for verbose logging
# `-g projector3` represents the consumer group; >=1 are allowed, allowing multiple independent projections to run concurrently
# `-l 5` to report ChangeFeed lags every 5 minutes
# `kafka` specifies one wants to emit to Kafka
# `temp-topic` is the topic to emit to
# `cosmos` specifies source overrides (using defaults in step 1 in this instance)
propulsion -V sync -g projector3 -l 5 kafka temp-topic from cosmos
propulsion
tool to inspect DynamoStore IndexSummarize current state of the index being prepared by Propulsion.DynamoStore.Indexer
propulsion index dynamo -t equinox-test
Example output:
19:15:50 I Current Partitions / Active Epochs [[0, 354], [2, 15], [3, 13], [4, 13], [5, 13], [6, 64], [7, 53], [8, 53], [9, 60]]
19:15:50 I Inspect Index Partitions list events 👉 eqx -C dump '$AppendsIndex-0' dynamo -t equinox-test-index
19:15:50 I Inspect Batches in Epoch 2 of Index Partition 0 👉 eqx -C dump '$AppendsEpoch-0_2' -B dynamo -t equinox-test-index
propulsion
tool to validate DynamoStoreSource IndexValidate Propulsion.DynamoStore.Indexer
has not missed any events (normally you guarantee this by having alerting on Lambda failures)
propulsion index -p 0 dynamo -t equinox-test
propulsion
tool to reindex and/or add missing notificationsIn addition to being able to validate the index (see preceding step), the tool facilitates ingestion of missing events from a complete DynamoDB JSON Export. Steps are as follows:
.json.gz
filesRun ingestion job
propulsion index -t 0 $HOME/Downloads/DynamoDbS3Export/*.json dynamo -t equinox-test
See CONTRIBUTING.md
The best place to start, sample-wise is with the the Equinox QuickStart, which walks you through sample code, tuned for approachability, from dotnet new
templates stored in a dedicated repo.
Please note the QuickStart is probably the best way to gain an overview, and the templates are the best way to see how to consume it; these instructions are intended mainly for people looking to make changes.
NB The Propulsion.Kafka.Integration
tests are reliant on a TEST_KAFKA_BROKER
environment variable pointing to a Broker that has been configured to auto-create ephemeral Kafka Topics as required by the tests (each test run blindly writes to a guid-named topic and trusts the broker will accept the write without any initialization step)
dotnet build build.proj -v n
Well, Kafka is definitely not a critical component or a panacea.
You're correct that the bulk of things that can be achieved using Kafka can be accomplished via usage of the ChangeFeed. One thing to point out is that in the context of enterprise systems, having a well maintained Kafka cluster does have less incremental (or total) cost than it might do if you're building a smaller system from nothing.
Some negatives of consuming from the ChangeFeed directly:
Many of these concerns can be alleviated to varying degrees by splitting the storage up into multiple Containers (potentially using database level RU allocations) such that each consumer will intrinsically be interested in a large proportion of the data it will observe, the write amplification effects of having multiple consumers will always be more significant when reading directly than when having a single reader emit to Kafka. The design of Kafka is specifically geared to running lots of concurrent readers.
However, splitting event categories into Containers solely to optimize these effects can also make the management of the transactional workload more complex; the ideal for any given Container is thus to balance the concerns of:
I know for unit testing, I can just test the obvious parts. Or if end to end testing is even required
Depends what you want to achieve. One important technique for doing end-to-end scenarios, especially where some reaction is supposed to feed back into Equinox is to use Equinox.MemoryStore
as the store, and then wire the Propulsion Sink (that will be fed from your real store when deployed in a production scenario) consume from that using Propulsion.MemoryStore.MemoryStoreProjector
.
Other techniques I've seen/heard are:
Shipping.Watchdog.Integration
test suite in the equinox-shipping
template for an example.In general I'd be looking to use MemoryStoreProjector
as a default technique, as it provides:
AwaitCompletion
facility to efficiently wait for the exact moment at which the event has been handled by the reactor component without padded Sleep sequences (or, worse: retry loops). To answer more completely, I'd say given a scenario involving Propulsion and Equinox, you'll typically have the following ingredients:
writing to the store - you can either assume that's well-tested infra or take the view that you need to validate that you wired it up properly
serialization/deserialization - you can either have unit tests and/or property tests to validate round-tripping as an orthogonal concern, or you can take the view that it's critical to know it really works with real data
reading from the store's change feed and propagating to handler - that's harder to config and has the biggest variability in a test scenario so either:
validating that triggered reactions are handled and complete cleanly - yes you can and should unit test that, but maybe you want to know it works end-to-end with a much larger proportion of the overall system in play
does it trigger follow-on work, i.e. a cascade of reactions. You can either do triangulation and say its proven if I observe the trigger for the next bit, or you may want to prove that end to end
does the entire system as a whole really work - sometimes you want to be able to validate workflows rather than having to pay the complexity tax of going in the front door for every aspect (though you'll typically want to have a meaningful set of smoke tests that validate basic system integrity without requiring manual testing or back-door interfaces)
While the implementation and patterns in Propulsion happen to overlap to a degree with the use cases of the ESDB's subscription mechanisms, the main reason they are not used directly stems from the needs and constraints that Propulsion was evolved to cover.
One thing that should be clear is that Propulsion is definitely not attempting to be the simplest conceivable projection library with a low concept count that's easy to get started with. If you were looking to build such a library, you'll likely give yourself some important guiding non-goals to enable that, e.g., if you had to add 3 concepts to get a 50% improvement in throughput, whether or not that's worth it depends on the context - if you're trying to have a low concept count, you might be prepared to leave some performance on the table to enable that.
For Propulsion, almost literally, job one was to be able to shift 1TB of ordered events in streams to/from ESDB/Cosmos/Kafka in well under 24h - a naive implementation reading and writing in small batches takes more like 24d to do the same thing. A key secondary goal was to be able to keep them in sync continually after that point (it's definitely more than a one time bulk ingestion system).
While Propulsion scales down to running simple subscriptions, its got quite a few additional concepts compared to using something built literally for that exact job; the general case of arbitrary projections was almost literally an afterthought.
That's not to say that Propulsion's concepts make for a more complex system when all is said and done; there are lots of scenarios where you avoid having to do concurrent/async tricks one might otherwise do more explicitly in a more simplistic subscription system.
When looking at the vast majority of typical projections/reactions/denormalizers one runs in an event-sourced system it should come as no surprise that EventStoreDB's subscription features offer plenty ways of achieving those common goals with a good balance of:
That's literally the company's goal: enabling rapidly building systems to solve business problems, without overfitting to any specific industry or application's needs.
The potential upsides that Propulsion can offer when used as a projection system can definitely be valuable when actually needed, but on average, they can equally be overkill for a given specific requirement.
With that context set, here are some notable aspects of using Propulsion for Projectors rather than building bespoke wiring on a case by case basis:
Propulsion.Feed
)Equinox.CosmosStore
or Equinox.DynamoStore
(compared to e.g. using a store such as Redis)Azure.Cosmos
V4 SDK will be a matter of changing package references and fixing some minimal compilation errors, as opposed to learning a whole new API setEventStore.ClientAPI
to the gRPC based >= v20 clients is a simple package switchEquinox.CosmosStore
or Equinox.DynamoStore
, i.e. "we're using Propulsion to isolate us from deciding between SSS or ESDB" may not be a good enough reason on its own)The order in which the need for various components arose (as a side effect of building out Equinox; solving specific needs in terms of feeding events into and out of EventStoreDB, CosmosDB and Kafka) was also an influence on the abstractions within and general facilities of Propulsion.
Propulsion.Cosmos
's Source
was the first bit done; it's a light wrapper over the CFP V2 client. Key implications from that are:
Propulsion.Kafka
's Sink
was next; the central goal here is to be able to replicate events being read from CosmosDB onto a Kafka Topic maintaining the ordering guarantees.
There are two high level ways of achieving ordering guarantees in Kafka:
Confluent.Kafka
at the time)=> The approach used is to continuously emit messages concurrently in order to maintain throughput, but guarantee to never emit messages for the same key at the same time.
Propulsion.Cosmos
's Sink
was next up. It writes to CosmosDB using Equinox.Cosmos
. Key implications:
there is no one-size fits all batch size (yet) that balances
You'll often need a small batch size, which implies larger per-event checkpointing overhead unless you make the checkpointing asynchronous
=> The implementation thus:
Propulsion.EventStore
's Source
and Sink
were being implemented (within weeks of the CosmosStore
equivalents; largely overlapping), the implications from realizing goals of providing good throughput while avoiding adding new concepts if that can be avoided are:
$all
stream.CosmosStore
but saving them in an external store such as an Equinox.CosmosStore
makes sense)CosmosSStoreink
apply too; you don't want to sit around retrying the last request out of a batch of 100 while tens of thousands of provisioned RUs are sitting idle in Cosmos with throughput sitting close to zeroThe things Propulsion in general accomplishes in the projections space:
Propulsion.Feed
)CosmosStoreSource
provides for automatic load balancing over multiple instances of a Reactor application akin to how Kafka Clients manage that (as a light wrapper over the Cosmos SDK's ChangeFeedProcessor lease management system without any custom semantics beyond the proven scheme the SDK implements)Propulsion.Cosmos
to Propulsion.CosmosStore3
and Propulsion.CosmosStore
by swapping driver modules; similar to how Equinox.Cosmos
vs Equinox.EventStore
provides a common programming model despite the underpinnings being fundamentally quite different in naturePropulsion.CosmosStore
(for the V3 SDK) with close-to-identical interfaces (Similarly there's a Propulsion.EventStoreDb
using the gRPC-based SDKs, replacing the deprecated Propulsion.EventStore
)dotnet new proProjector
app, and tweak the ~30 lines of consumer app wireup to connect to Kafka instead of CosmosDBThings EventStoreDB's subscriptions can do that are not presently covered in Propulsion:
$et-
, $ec-
streams$all
orderThis repo is derived from FsKafka
; the history has been edited to focus only on edits to the Propulsion
libraries.