├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── code_quality.yml.txt │ ├── preview.yml │ ├── publish.yml │ ├── pull-request-docs.yml │ ├── pull-request.yml │ └── test-results.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Directory.Packages.props ├── Eventuous.sln ├── Eventuous.sln.DotSettings ├── LICENSE ├── README.md ├── docker-compose.yml ├── docs ├── README.md ├── babel.config.js ├── docs │ ├── application │ │ ├── app-service.md │ │ ├── command-api.md │ │ ├── command-map.mdx │ │ ├── func-service.md │ │ └── index.mdx │ ├── diagnostics │ │ ├── _category_.json │ │ └── details.md │ ├── domain │ │ ├── aggregate.md │ │ ├── domain-events.md │ │ └── state.md │ ├── faq │ │ ├── _category_.json │ │ ├── compare.md │ │ ├── compatibility.md │ │ └── persistence.md │ ├── gateway │ │ ├── implementation │ │ │ └── index.md │ │ └── index.mdx │ ├── infra │ │ ├── _category_.json │ │ ├── elastic │ │ │ └── index.md │ │ ├── esdb │ │ │ └── index.md │ │ ├── kafka │ │ │ └── index.md │ │ ├── mongodb │ │ │ └── index.md │ │ ├── mssql │ │ │ └── index.md │ │ ├── postgres │ │ │ └── index.md │ │ ├── pubsub │ │ │ └── index.md │ │ └── rabbitmq │ │ │ └── index.md │ ├── intro.mdx │ ├── persistence │ │ ├── aggregate-store │ │ │ ├── images │ │ │ │ ├── reading-dark.png │ │ │ │ ├── reading.png │ │ │ │ ├── replication-dark.png │ │ │ │ └── replication.png │ │ │ └── index.mdx │ │ ├── aggregate-stream.md │ │ ├── event-store.md │ │ ├── index.mdx │ │ └── serialisation.md │ ├── producers │ │ ├── implementation.md │ │ ├── index.mdx │ │ └── providers.md │ ├── prologue │ │ ├── introduction.md │ │ ├── quick-start.md │ │ └── the-right-way │ │ │ ├── images │ │ │ ├── flaming-bus.jpg │ │ │ ├── the-right-way-dark.png │ │ │ └── the-right-way.png │ │ │ └── index.mdx │ ├── read-models │ │ ├── rm-concept.md │ │ └── supported-projectors.md │ ├── subscriptions │ │ ├── checkpoint │ │ │ ├── images │ │ │ │ ├── commit-handler-dark.png │ │ │ │ └── commit-handler.png │ │ │ └── index.mdx │ │ ├── eventhandler │ │ │ └── index.md │ │ ├── pipes │ │ │ ├── images │ │ │ │ ├── concurrent-filter-dark.png │ │ │ │ ├── concurrent-filter.png │ │ │ │ ├── default-pipe-dark.png │ │ │ │ ├── default-pipe.png │ │ │ │ ├── partitioning-filter-dark.png │ │ │ │ └── partitioning-filter.png │ │ │ └── index.mdx │ │ ├── sub-base │ │ │ └── index.md │ │ ├── subs-concept │ │ │ ├── images │ │ │ │ ├── subscriptions-dark.png │ │ │ │ └── subscriptions.png │ │ │ └── index.mdx │ │ └── subs-diagnostics │ │ │ ├── images │ │ │ └── sub-trace.png │ │ │ └── index.md │ └── whats-new.mdx ├── docusaurus.config.ts ├── package.json ├── pnpm-lock.yaml ├── sidebars.ts ├── src │ ├── components │ │ ├── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ └── highlight.jsx │ ├── css │ │ └── custom.css │ ├── pages │ │ ├── index.module.css │ │ └── index.tsx │ └── plugins │ │ ├── posthog │ │ ├── index.js │ │ └── posthog.js │ │ └── segment │ │ ├── index.js │ │ └── segment.js ├── static │ ├── .nojekyll │ ├── _redirects │ └── img │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── featured-background.jpg │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── site.webmanifest │ │ └── social-card.png ├── tsconfig.json ├── versioned_docs │ ├── version-0.14 │ │ ├── application │ │ │ ├── app-service.md │ │ │ ├── command-api.md │ │ │ ├── command-map.mdx │ │ │ ├── func-service.md │ │ │ └── index.mdx │ │ ├── diagnostics │ │ │ ├── _category_.json │ │ │ └── index.md │ │ ├── domain │ │ │ ├── aggregate.md │ │ │ ├── domain-events.md │ │ │ └── state.md │ │ ├── faq │ │ │ ├── _category_.json │ │ │ ├── compare.md │ │ │ ├── compatibility.md │ │ │ └── persistence.md │ │ ├── gateway │ │ │ ├── implementation │ │ │ │ └── index.md │ │ │ └── index.mdx │ │ ├── infra │ │ │ ├── _category_.json │ │ │ ├── elastic │ │ │ │ └── index.md │ │ │ ├── esdb │ │ │ │ └── index.md │ │ │ ├── kafka │ │ │ │ └── index.md │ │ │ ├── mongodb │ │ │ │ └── index.md │ │ │ ├── mssql │ │ │ │ └── index.md │ │ │ ├── postgres │ │ │ │ └── index.md │ │ │ ├── pubsub │ │ │ │ └── index.md │ │ │ └── rabbitmq │ │ │ │ └── index.md │ │ ├── intro.mdx │ │ ├── persistence │ │ │ ├── aggregate-store │ │ │ │ ├── images │ │ │ │ │ ├── reading-dark.png │ │ │ │ │ ├── reading.png │ │ │ │ │ ├── replication-dark.png │ │ │ │ │ └── replication.png │ │ │ │ └── index.mdx │ │ │ ├── aggregate-stream.md │ │ │ ├── event-store.md │ │ │ ├── index.mdx │ │ │ └── serialisation.md │ │ ├── producers │ │ │ ├── implementation.md │ │ │ ├── index.mdx │ │ │ └── providers.md │ │ ├── prologue │ │ │ ├── introduction.md │ │ │ ├── quick-start.md │ │ │ └── the-right-way │ │ │ │ ├── images │ │ │ │ ├── flaming-bus.jpg │ │ │ │ ├── the-right-way-dark.png │ │ │ │ └── the-right-way.png │ │ │ │ └── index.mdx │ │ ├── read-models │ │ │ ├── rm-concept.md │ │ │ └── supported-projectors.md │ │ └── subscriptions │ │ │ ├── checkpoint │ │ │ ├── images │ │ │ │ ├── commit-handler-dark.png │ │ │ │ └── commit-handler.png │ │ │ └── index.mdx │ │ │ ├── eventhandler │ │ │ └── index.md │ │ │ ├── pipes │ │ │ ├── images │ │ │ │ ├── concurrent-filter-dark.png │ │ │ │ ├── concurrent-filter.png │ │ │ │ ├── default-pipe-dark.png │ │ │ │ ├── default-pipe.png │ │ │ │ ├── partitioning-filter-dark.png │ │ │ │ └── partitioning-filter.png │ │ │ └── index.mdx │ │ │ ├── sub-base │ │ │ └── index.md │ │ │ ├── subs-concept │ │ │ ├── images │ │ │ │ ├── subscriptions-dark.png │ │ │ │ └── subscriptions.png │ │ │ └── index.mdx │ │ │ └── subs-diagnostics │ │ │ ├── images │ │ │ └── sub-trace.png │ │ │ └── index.md │ └── version-0.15 │ │ ├── application │ │ ├── _result.mdx │ │ ├── _service_http.mdx │ │ ├── _service_no_throw.mdx │ │ ├── _service_registration.mdx │ │ ├── app-service.mdx │ │ ├── command-api.md │ │ ├── func-service.mdx │ │ └── index.mdx │ │ ├── diagnostics │ │ ├── index.mdx │ │ ├── logs.md │ │ ├── metrics.md │ │ ├── opentelemetry.md │ │ └── traces.md │ │ ├── domain │ │ ├── aggregate.md │ │ ├── domain-events.md │ │ └── state.md │ │ ├── faq │ │ ├── _category_.json │ │ ├── compare.md │ │ ├── compatibility.md │ │ └── persistence.md │ │ ├── gateway │ │ ├── implementation │ │ │ └── index.md │ │ └── index.mdx │ │ ├── infra │ │ ├── _category_.json │ │ ├── elastic │ │ │ └── index.md │ │ ├── esdb │ │ │ └── index.md │ │ ├── kafka │ │ │ └── index.md │ │ ├── mongodb │ │ │ └── index.md │ │ ├── mssql │ │ │ └── index.md │ │ ├── postgres │ │ │ └── index.md │ │ ├── pubsub │ │ │ └── index.md │ │ └── rabbitmq │ │ │ └── index.md │ │ ├── intro.mdx │ │ ├── persistence │ │ ├── aggregate-store.mdx │ │ ├── aggregate-stream.md │ │ ├── event-store.mdx │ │ ├── images │ │ │ ├── reading-dark.png │ │ │ ├── reading.png │ │ │ ├── replication-dark.png │ │ │ └── replication.png │ │ ├── index.mdx │ │ └── serialisation.md │ │ ├── producers │ │ ├── implementation.md │ │ ├── index.mdx │ │ └── providers.md │ │ ├── prologue │ │ ├── introduction.md │ │ ├── quick-start.md │ │ └── the-right-way │ │ │ ├── images │ │ │ ├── flaming-bus.jpg │ │ │ ├── the-right-way-dark.png │ │ │ └── the-right-way.png │ │ │ └── index.mdx │ │ ├── read-models │ │ ├── rm-concept.md │ │ └── supported-projectors.md │ │ ├── subscriptions │ │ ├── checkpoint │ │ │ ├── images │ │ │ │ ├── commit-handler-dark.png │ │ │ │ └── commit-handler.png │ │ │ └── index.mdx │ │ ├── eventhandler │ │ │ └── index.md │ │ ├── pipes │ │ │ ├── images │ │ │ │ ├── concurrent-filter-dark.png │ │ │ │ ├── concurrent-filter.png │ │ │ │ ├── default-pipe-dark.png │ │ │ │ ├── default-pipe.png │ │ │ │ ├── partitioning-filter-dark.png │ │ │ │ └── partitioning-filter.png │ │ │ └── index.mdx │ │ ├── sub-base │ │ │ └── index.md │ │ ├── subs-concept │ │ │ ├── images │ │ │ │ ├── subscriptions-dark.png │ │ │ │ └── subscriptions.png │ │ │ └── index.mdx │ │ └── subs-diagnostics │ │ │ ├── images │ │ │ └── sub-trace.png │ │ │ └── index.md │ │ └── whats-new.mdx ├── versioned_sidebars │ ├── version-0.14-sidebars.json │ └── version-0.15-sidebars.json └── versions.json ├── e-logo.png ├── props └── Common.props ├── qodana.yaml ├── samples ├── Directory.Build.props ├── esdb │ ├── Bookings.Domain │ │ ├── Bookings.Domain.csproj │ │ ├── Bookings │ │ │ ├── Booking.cs │ │ │ ├── BookingEvents.cs │ │ │ ├── BookingId.cs │ │ │ └── BookingState.cs │ │ ├── DomainModule.cs │ │ ├── Money.cs │ │ ├── RoomId.cs │ │ ├── Services.cs │ │ └── StayPeriod.cs │ ├── Bookings.Payments │ │ ├── Application │ │ │ ├── CommandApi.cs │ │ │ └── CommandService.cs │ │ ├── Bookings.Payments.csproj │ │ ├── Domain │ │ │ ├── Money.cs │ │ │ ├── Payment.cs │ │ │ └── PaymentEvents.cs │ │ ├── Infrastructure │ │ │ ├── Logging.cs │ │ │ └── Mongo.cs │ │ ├── Integration │ │ │ └── Payments.cs │ │ ├── Program.cs │ │ ├── Registrations.cs │ │ └── appsettings.json │ ├── Bookings │ │ ├── .dockerignore │ │ ├── Application │ │ │ ├── BookingsCommandService.cs │ │ │ ├── BookingsQueryService.cs │ │ │ ├── Commands.cs │ │ │ └── Queries │ │ │ │ ├── BookingDocument.cs │ │ │ │ ├── BookingStateProjection.cs │ │ │ │ ├── MyBookings.cs │ │ │ │ └── MyBookingsProjection.cs │ │ ├── Bookings.csproj │ │ ├── Dockerfile │ │ ├── HttpApi │ │ │ └── Bookings │ │ │ │ ├── CommandApi.cs │ │ │ │ ├── CommandApiWithCustomResult.cs │ │ │ │ └── QueryApi.cs │ │ ├── Infrastructure │ │ │ └── Mongo.cs │ │ ├── Integration │ │ │ └── Payments.cs │ │ ├── Program.cs │ │ ├── Registrations.cs │ │ └── appsettings.json │ ├── deploy │ │ └── cloudrun │ │ │ ├── .gitignore │ │ │ ├── Pulumi.dev.yaml │ │ │ ├── Pulumi.yaml │ │ │ ├── index.ts │ │ │ ├── package.json │ │ │ ├── tsconfig.json │ │ │ └── yarn.lock │ ├── docker-compose.yml │ ├── grafana │ │ ├── __inputs.json │ │ └── datasources.yml │ └── prometheus │ │ └── prometheus.yml └── postgres │ ├── Bookings.Domain │ └── Bookings.Domain.csproj │ ├── Bookings.Payments │ ├── Bookings.Payments.csproj │ ├── Integration │ │ └── Payments.cs │ ├── Program.cs │ ├── Registrations.cs │ └── appsettings.json │ ├── Bookings │ ├── .dockerignore │ ├── Application │ │ ├── BookingsCommandService.cs │ │ ├── Commands.cs │ │ └── Queries │ │ │ ├── BookingDocument.cs │ │ │ ├── BookingStateProjection.cs │ │ │ ├── MyBookings.cs │ │ │ └── MyBookingsProjection.cs │ ├── Bookings.csproj │ ├── Dockerfile │ ├── HttpApi │ │ └── Bookings │ │ │ ├── CommandApi.cs │ │ │ └── QueryApi.cs │ ├── Infrastructure │ │ ├── Logging.cs │ │ ├── Mongo.cs │ │ └── Telemetry.cs │ ├── Integration │ │ └── Payments.cs │ ├── Program.cs │ ├── Registrations.cs │ └── appsettings.json │ ├── README.md │ ├── deploy │ └── cloudrun │ │ ├── .gitignore │ │ ├── Pulumi.dev.yaml │ │ ├── Pulumi.yaml │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── yarn.lock │ ├── docker-compose.yml │ ├── grafana │ ├── __inputs.json │ └── datasources.yml │ └── prometheus │ └── prometheus.yml ├── src ├── Benchmarks │ └── Benchmarks │ │ ├── Benchmarks.csproj │ │ ├── GapDetectionBenchmarks.cs │ │ ├── Program.cs │ │ ├── Tools │ │ └── DynamicType.cs │ │ └── TypeMapBenchmark.cs ├── Core │ ├── Eventuous.Core.slnf │ ├── src │ │ ├── Eventuous.Application │ │ │ ├── AggregateService │ │ │ │ ├── CommandHandlerBuilder.cs │ │ │ │ ├── CommandHandlersMap.cs │ │ │ │ ├── CommandService.Async.cs │ │ │ │ ├── CommandService.Sync.cs │ │ │ │ ├── CommandService.cs │ │ │ │ └── CommandServiceDelegates.cs │ │ │ ├── Diagnostics │ │ │ │ ├── ApplicationEventSource.cs │ │ │ │ ├── CommandServiceActivity.cs │ │ │ │ ├── CommandServiceMetrics.cs │ │ │ │ └── TracedCommandService.cs │ │ │ ├── Eventuous.Application.csproj │ │ │ ├── Eventuous.Application.csproj.DotSettings │ │ │ ├── Exceptions │ │ │ │ ├── ExceptionMessages.cs │ │ │ │ ├── ExceptionMessages.restext │ │ │ │ └── Exceptions.cs │ │ │ ├── ExpectedState.cs │ │ │ ├── FunctionalService │ │ │ │ ├── CommandHandlerBuilder.cs │ │ │ │ ├── CommandService.cs │ │ │ │ ├── FuncHandlerDelegateExtensions.cs │ │ │ │ ├── FuncServiceDelegates.cs │ │ │ │ └── HandlersMap.cs │ │ │ ├── ICommandService.cs │ │ │ ├── MessageMap.cs │ │ │ ├── Persistence │ │ │ │ ├── ProposedAppend.cs │ │ │ │ └── WriterExtensions.cs │ │ │ ├── Result.cs │ │ │ ├── Shared │ │ │ │ └── IDefineAppendAmendment.cs │ │ │ └── ThrowingCommandService.cs │ │ ├── Eventuous.Diagnostics │ │ │ ├── ActivityExtensions.cs │ │ │ ├── ActivityStatus.cs │ │ │ ├── DiagnosticName.cs │ │ │ ├── DummyActivityListener.cs │ │ │ ├── Eventuous.Diagnostics.csproj │ │ │ ├── Eventuous.Diagnostics.csproj.DotSettings │ │ │ ├── EventuousDiagnostics.cs │ │ │ ├── GenericListener.cs │ │ │ ├── MetadataExtensions.cs │ │ │ ├── Metrics │ │ │ │ ├── GenericObserver.cs │ │ │ │ ├── Measure.cs │ │ │ │ └── MetricsListener.cs │ │ │ ├── Tags │ │ │ │ ├── IWithCustomTags.cs │ │ │ │ ├── MetaMappings.cs │ │ │ │ ├── MetaTags.cs │ │ │ │ └── TracingMeta.cs │ │ │ ├── TelemetryTags.cs │ │ │ └── Tracing │ │ │ │ └── Constants.cs │ │ ├── Eventuous.Domain │ │ │ ├── Aggregate.cs │ │ │ ├── Eventuous.Domain.csproj │ │ │ ├── Eventuous.Domain.csproj.DotSettings │ │ │ ├── Exceptions │ │ │ │ ├── DomainException.cs │ │ │ │ ├── ExceptionMessages.cs │ │ │ │ ├── ExceptionMessages.restext │ │ │ │ └── Exceptions.cs │ │ │ ├── Id.cs │ │ │ └── State.cs │ │ ├── Eventuous.Persistence │ │ │ ├── AggregateFactory.cs │ │ │ ├── AggregateStore │ │ │ │ ├── AggregatePersistenceExtensions.cs │ │ │ │ ├── AggregateStore.cs │ │ │ │ ├── AggregateStoreExceptions.cs │ │ │ │ ├── AggregateStoreExtensions.cs │ │ │ │ ├── AggregateStoreWithArchive.cs │ │ │ │ └── IAggregateStore.cs │ │ │ ├── AmendEvent.cs │ │ │ ├── AppendEventsResult.cs │ │ │ ├── Diagnostics │ │ │ │ ├── PersistenceEventSource.cs │ │ │ │ ├── PersistenceMetrics.cs │ │ │ │ └── Tracing │ │ │ │ │ ├── BaseTracer.cs │ │ │ │ │ ├── TracedEventReader.cs │ │ │ │ │ ├── TracedEventStore.cs │ │ │ │ │ └── TracedEventWriter.cs │ │ │ ├── EventStore │ │ │ │ ├── EventStoreExceptions.cs │ │ │ │ ├── IEventReader.cs │ │ │ │ ├── IEventStore.cs │ │ │ │ ├── IEventWriter.cs │ │ │ │ ├── StoreFunctions.cs │ │ │ │ ├── TieredEventReader.cs │ │ │ │ └── TieredEventStore.cs │ │ │ ├── Eventuous.Persistence.csproj │ │ │ ├── Eventuous.Persistence.csproj.DotSettings │ │ │ ├── ExpectedStreamVersion.cs │ │ │ ├── StateStore │ │ │ │ ├── FoldedEventStream.cs │ │ │ │ ├── IStateStore.cs │ │ │ │ ├── StateStore.cs │ │ │ │ └── StateStoreFunctions.cs │ │ │ ├── StreamEvent.cs │ │ │ ├── StreamNameFactory.cs │ │ │ └── StreamNameMap.cs │ │ ├── Eventuous.Producers │ │ │ ├── AckProduce.cs │ │ │ ├── BaseProducer.cs │ │ │ ├── Chunk.cs │ │ │ ├── Diagnostics │ │ │ │ ├── ProducerActivity.cs │ │ │ │ ├── ProducerEventSource.cs │ │ │ │ └── ProducerTracingOptions.cs │ │ │ ├── Eventuous.Producers.csproj │ │ │ ├── IProducer.cs │ │ │ ├── ProducedMessage.cs │ │ │ ├── ProducerExtensions.cs │ │ │ └── RegistrationExtensions.cs │ │ ├── Eventuous.Serialization │ │ │ ├── DefaultEventSerializer.cs │ │ │ ├── DefaultMetadataSerializer.cs │ │ │ ├── Eventuous.Serialization.csproj │ │ │ ├── IEventSerializer.cs │ │ │ ├── IMetadataSerializer.cs │ │ │ └── MetadataDeserializationException.cs │ │ ├── Eventuous.Shared │ │ │ ├── Eventuous.Shared.csproj │ │ │ ├── Eventuous.Shared.csproj.DotSettings │ │ │ ├── Exceptions │ │ │ │ ├── ExceptionMessages.cs │ │ │ │ ├── ExceptionMessages.restext │ │ │ │ └── Exceptions.cs │ │ │ ├── Meta │ │ │ │ ├── MetaTags.cs │ │ │ │ ├── Metadata.cs │ │ │ │ └── MetadataExtensions.cs │ │ │ ├── Store │ │ │ │ └── StreamName.cs │ │ │ ├── Tools │ │ │ │ ├── Ensure.cs │ │ │ │ ├── TaskExtensions.cs │ │ │ │ ├── TaskRunner.cs │ │ │ │ └── TypeExtensions.cs │ │ │ └── TypeMap │ │ │ │ ├── ITypeMapper.cs │ │ │ │ ├── TypeMap.cs │ │ │ │ ├── TypeMapEventSource.cs │ │ │ │ ├── TypeMapper.cs │ │ │ │ └── TypeMapperExtensions.cs │ │ ├── Eventuous.Subscriptions │ │ │ ├── Channels │ │ │ │ ├── ChannelExtensions.cs │ │ │ │ ├── ChannelFullException.cs │ │ │ │ ├── ChannelWorkerBase.cs │ │ │ │ └── ChannelWorkers.cs │ │ │ ├── Checkpoints │ │ │ │ ├── Checkpoint.cs │ │ │ │ ├── CheckpointCommitHandler.cs │ │ │ │ ├── CommitPositionSequence.cs │ │ │ │ ├── ICheckpointStore.cs │ │ │ │ ├── MeasuredCheckpointStore.cs │ │ │ │ └── NoOpCheckpointStore.cs │ │ │ ├── Consumers │ │ │ │ ├── DefaultConsumer.cs │ │ │ │ ├── IMessageConsumer.cs │ │ │ │ └── MessageConsumeContextConverter.cs │ │ │ ├── Context │ │ │ │ ├── AsyncConsumeContext.cs │ │ │ │ ├── ContextBaggageExtensions.cs │ │ │ │ ├── ContextExtensions.cs │ │ │ │ ├── ContextItemKeys.cs │ │ │ │ ├── ContextItems.cs │ │ │ │ ├── ContextResultExtensions.cs │ │ │ │ ├── IMessageConsumeContext.cs │ │ │ │ ├── MessageConsumeContext.cs │ │ │ │ └── WrappedConsumeContext.cs │ │ │ ├── Diagnostics │ │ │ │ ├── CheckpointCommitMetrics.cs │ │ │ │ ├── HealthReport.cs │ │ │ │ ├── SubscriptionActivity.cs │ │ │ │ ├── SubscriptionGapMeasure.cs │ │ │ │ ├── SubscriptionHealth.cs │ │ │ │ ├── SubscriptionMetrics.cs │ │ │ │ └── SubscriptionsEventSource.cs │ │ │ ├── DropReason.cs │ │ │ ├── EventSubscription.cs │ │ │ ├── EventSubscriptionWithCheckpoint.cs │ │ │ ├── Eventuous.Subscriptions.csproj │ │ │ ├── Eventuous.Subscriptions.csproj.DotSettings │ │ │ ├── Exceptions.cs │ │ │ ├── Filters │ │ │ │ ├── AsyncHandlingFilter.cs │ │ │ │ ├── ConsumePipe.cs │ │ │ │ ├── ConsumePipeExtensions.cs │ │ │ │ ├── ConsumerFilter.cs │ │ │ │ ├── IConsumeFilter.cs │ │ │ │ ├── MessageFilter.cs │ │ │ │ ├── Partitioning │ │ │ │ │ ├── MurmurHash3.cs │ │ │ │ │ └── Partitioner.cs │ │ │ │ ├── PartitioningFilter.cs │ │ │ │ └── TracingFilter.cs │ │ │ ├── Handlers │ │ │ │ ├── BaseEventHandler.cs │ │ │ │ ├── EventHandler.cs │ │ │ │ ├── EventHandlingResult.cs │ │ │ │ ├── EventHandlingStatus.cs │ │ │ │ ├── IEventHandler.cs │ │ │ │ └── TracedEventHandler.cs │ │ │ ├── IMessageSubscription.cs │ │ │ ├── Logging │ │ │ │ ├── CheckpointLogging.cs │ │ │ │ ├── InternalLogger.cs │ │ │ │ ├── Logger.cs │ │ │ │ └── SubscriptionLogging.cs │ │ │ ├── MetaSerializerExtensions.cs │ │ │ ├── Properties │ │ │ │ └── InternalVisibleTo.cs │ │ │ ├── Registrations │ │ │ │ ├── KeyedServiceProvider.cs │ │ │ │ ├── NamedRegistrations.cs │ │ │ │ ├── SubscriptionBuilder.cs │ │ │ │ ├── SubscriptionBuilderExtensions.cs │ │ │ │ └── SubscriptionRegistrationExtensions.cs │ │ │ ├── SubscriptionHostedService.cs │ │ │ └── SubscriptionOptions.cs │ │ └── Eventuous │ │ │ └── Eventuous.csproj │ └── test │ │ ├── Eventuous.Tests.Application │ │ ├── BookingFuncService.cs │ │ ├── CommandServiceTests.cs │ │ ├── Eventuous.Tests.Application.csproj │ │ ├── FunctionalServiceTests.cs │ │ ├── Helpers.cs │ │ ├── ServiceTestBase.Amendments.cs │ │ ├── ServiceTestBase.OnAny.cs │ │ ├── ServiceTestBase.OnExisting.cs │ │ ├── ServiceTestBase.OnNew.cs │ │ ├── ServiceTestBase.cs │ │ └── StateWithIdTests.cs │ │ ├── Eventuous.Tests.Persistence.Base │ │ ├── Eventuous.Tests.Persistence.Base.csproj │ │ ├── Fixtures │ │ │ ├── DomainFixture.cs │ │ │ ├── Helpers.cs │ │ │ └── StoreFixtureBase.cs │ │ └── Store │ │ │ ├── Append.cs │ │ │ ├── OtherMethods.cs │ │ │ ├── Read.cs │ │ │ └── TieredStoreTests.cs │ │ ├── Eventuous.Tests.Subscriptions.Base │ │ ├── Eventuous.Tests.Subscriptions.Base.csproj │ │ ├── Eventuous.Tests.Subscriptions.Base.csproj.DotSettings │ │ ├── Fixtures │ │ │ ├── SubscriptionExtensions.cs │ │ │ ├── SubscriptionFixtureBase.cs │ │ │ ├── TestEventHandler.cs │ │ │ └── TracedHandler.cs │ │ ├── SubscribeToAll.cs │ │ ├── SubscribeToStream.cs │ │ └── SubscriptionTestBase.cs │ │ ├── Eventuous.Tests.Subscriptions │ │ ├── ConsumePipeTests.cs │ │ ├── DefaultConsumerTests.cs │ │ ├── Eventuous.Tests.Subscriptions.csproj │ │ ├── HandlingStatusTests.cs │ │ ├── RegistrationTests.cs │ │ ├── SequenceTests.cs │ │ └── TestContext.cs │ │ └── Eventuous.Tests │ │ ├── AggregateWithId │ │ └── OperateOnAggregateWithId.cs │ │ ├── Aggregates │ │ ├── OperateOnExistingSpec.cs │ │ └── TwoAggregateOpsSpec.cs │ │ ├── Eventuous.Tests.csproj │ │ ├── Fixtures │ │ ├── IdGenerator.cs │ │ └── NaiveFixture.cs │ │ ├── ForgotToSetId.cs │ │ ├── StoringEvents.cs │ │ ├── StoringEventsWithCustomStream.cs │ │ ├── StreamNameMapTests.cs │ │ ├── StreamNameTests.cs │ │ └── TypeRegistrationTests.cs ├── Diagnostics │ ├── docker-compose.yml │ ├── src │ │ ├── Eventuous.Diagnostics.Logging │ │ │ ├── Eventuous.Diagnostics.Logging.csproj │ │ │ ├── LoggingEventListener.cs │ │ │ └── README.md │ │ ├── Eventuous.Diagnostics.OpenTelemetry │ │ │ ├── Eventuous.Diagnostics.OpenTelemetry.csproj │ │ │ ├── MeterProviderBuilderExtensions.cs │ │ │ └── TracerProviderBuilderExtensions.cs │ │ └── Eventuous.Diagnostics │ │ │ ├── DiagnosticName.cs │ │ │ ├── Eventuous.Diagnostics.csproj │ │ │ └── Properties │ │ │ └── InternalVisibleTo.cs │ └── test │ │ └── Eventuous.Tests.OpenTelemetry │ │ ├── Eventuous.Tests.OpenTelemetry.csproj │ │ ├── Fakes │ │ ├── MessageCounter.cs │ │ ├── MetricValue.cs │ │ ├── TestExporter.cs │ │ └── TestHandler.cs │ │ ├── Fixtures │ │ └── MetricsSubscriptionFixtureBase.cs │ │ └── MetricsTests.cs ├── Directory.Build.props ├── Directory.Build.targets ├── Directory.Testable.targets ├── Directory.Untestable.targets ├── EventStore │ ├── Eventuous.EventStore.slnf │ ├── docker-compose.yml │ ├── src │ │ └── Eventuous.EventStore │ │ │ ├── EsdbEventStore.cs │ │ │ ├── Eventuous.EventStore.csproj │ │ │ ├── Eventuous.EventStore.csproj.DotSettings │ │ │ ├── Producers │ │ │ ├── EventStoreProduceOptions.cs │ │ │ └── EventStoreProducer.cs │ │ │ ├── README.md │ │ │ ├── StreamRevisionExtensions.cs │ │ │ └── Subscriptions │ │ │ ├── AllPersistentSubscription.cs │ │ │ ├── AllStreamSubscription.cs │ │ │ ├── ConsumePipeExtensions.cs │ │ │ ├── Diagnostics │ │ │ ├── AllStreamSubscriptionMeasure.cs │ │ │ ├── BaseSubscriptionMeasure.cs │ │ │ └── StreamSubscriptionMeasure.cs │ │ │ ├── EsdbMappings.cs │ │ │ ├── EventStoreCatchUpSubscriptionBase.cs │ │ │ ├── EventStoreExtensions.cs │ │ │ ├── Options │ │ │ ├── AllPersistentSubscriptionOptions.cs │ │ │ ├── AllStreamSubscriptionOptions.cs │ │ │ ├── CatchUpSubscriptionOptions.cs │ │ │ ├── EventStoreSubscriptionOptions.cs │ │ │ ├── EventStoreSubscriptionWithCheckpointOptions.cs │ │ │ ├── PersistentSubscriptionOptions.cs │ │ │ ├── StreamPersistentSubscriptionOptions.cs │ │ │ └── StreamSubscriptionOptions.cs │ │ │ ├── PersistentSubscriptionBase.cs │ │ │ ├── StreamPersistentSubscription.cs │ │ │ └── StreamSubscription.cs │ └── test │ │ └── Eventuous.Tests.EventStore │ │ ├── AppServiceTests.cs │ │ ├── Eventuous.Tests.EventStore.csproj │ │ ├── Fixtures │ │ ├── DomainFixture.cs │ │ ├── EsdbContainer.cs │ │ ├── StoreFixture.cs │ │ └── TestCheckpointStore.cs │ │ ├── Limiter.cs │ │ ├── Metrics │ │ ├── MetricsFixture.cs │ │ └── MetricsTests.cs │ │ ├── ProducerTracesTests.cs │ │ ├── RegistrationTests.cs │ │ ├── Store │ │ ├── AggregateStoreTests.cs │ │ ├── EventStoreAggregateTests.cs │ │ ├── StoreTests.cs │ │ └── TieredStoreTests.cs │ │ └── Subscriptions │ │ ├── CustomDependenciesTests.cs │ │ ├── Fixtures │ │ ├── CatchUpSubscriptionFixture.cs │ │ ├── LegacySubscriptionFixture.cs │ │ └── PersistentSubscriptionFixture.cs │ │ ├── PublishAndSubscribeManyPartitionedTests.cs │ │ ├── PublishAndSubscribeManyTests.cs │ │ ├── PublishAndSubscribeOneTests.cs │ │ ├── StreamPersistentPublishAndSubscribeManyTests.cs │ │ ├── StreamSubscriptionDeletedEventsTests.cs │ │ ├── StreamSubscriptionWithLinksTests.cs │ │ ├── SubscribeTests.cs │ │ └── SubscriptionIgnoredMessagesTests.cs ├── Experimental │ └── src │ │ ├── ElasticPlayground │ │ ├── CombinedStore.cs │ │ ├── ConfigureElastic.cs │ │ ├── ConnectorAndArchive.cs │ │ ├── ElasticOnly.cs │ │ ├── ElasticPlayground.csproj │ │ ├── Generator.cs │ │ ├── MiscExtensions.cs │ │ ├── OnlyArchive.cs │ │ ├── Program.cs │ │ ├── ResultExtensions.cs │ │ └── docker-compose.yml │ │ ├── Eventuous.ElasticSearch │ │ ├── Eventuous.ElasticSearch.csproj │ │ ├── Index │ │ │ ├── IndexConfig.cs │ │ │ └── IndexSetup.cs │ │ ├── Producers │ │ │ └── ElasticProducer.cs │ │ ├── Projections │ │ │ └── ElasticCheckpointStore.cs │ │ └── Store │ │ │ ├── ElasticEventStore.cs │ │ │ ├── ElasticSerializer.cs │ │ │ └── PersistedEvent.cs │ │ └── Eventuous.Spyglass │ │ ├── Accessor.cs │ │ ├── Eventuous.Spyglass.csproj │ │ ├── InsidePeek.cs │ │ ├── RegistrationExtensions.cs │ │ └── SpyglassApi.cs ├── Extensions │ ├── src │ │ ├── Eventuous.Extensions.AspNetCore │ │ │ ├── Diagnostics │ │ │ │ └── ExtensionsEventSource.cs │ │ │ ├── Eventuous.Extensions.AspNetCore.csproj │ │ │ ├── Http │ │ │ │ ├── CommandHttpApiBase.cs │ │ │ │ ├── CommandServiceRouteBuilder.cs │ │ │ │ ├── ContentTypes.cs │ │ │ │ ├── ControllerAttributes.cs │ │ │ │ ├── HttpCommandAttribute.cs │ │ │ │ ├── HttpCommandMapping.cs │ │ │ │ ├── HttpCommandMappingExt.cs │ │ │ │ ├── ResultExtensions.cs │ │ │ │ └── RouteHandlerBuilderExt.cs │ │ │ └── Logging │ │ │ │ └── AppBuilderLoggingExtensions.cs │ │ ├── Eventuous.Extensions.DependencyInjection │ │ │ ├── Eventuous.Extensions.DependencyInjection.csproj │ │ │ ├── LoggingServiceProviderExtensions.cs │ │ │ ├── README.md │ │ │ └── Registrations │ │ │ │ ├── AggregateFactory.cs │ │ │ │ ├── AggregateStore.cs │ │ │ │ ├── Services.cs │ │ │ │ ├── StoreWithArchive.cs │ │ │ │ └── Stores.cs │ │ ├── Eventuous.Extensions.Logging │ │ │ ├── Eventuous.Extensions.Logging.csproj │ │ │ └── LoggerFactoryExtensions.cs │ │ └── Eventuous.Subscriptions.Polly │ │ │ ├── Eventuous.Subscriptions.Polly.csproj │ │ │ ├── PollyEventHandler.cs │ │ │ └── SubscriptionBuilderExtensions.cs │ └── test │ │ ├── Eventuous.Sut.AspNetCore │ │ ├── BookingApi.cs │ │ ├── BookingService.cs │ │ ├── Eventuous.Sut.AspNetCore.csproj │ │ ├── Program.cs │ │ ├── TestConfig.cs │ │ ├── TestData.cs │ │ └── appsettings.json │ │ ├── Eventuous.Tests.DependencyInjection │ │ ├── AggregateFactoryRegistrationTests.cs │ │ ├── Eventuous.Tests.DependencyInjection.csproj │ │ └── Sut │ │ │ ├── FakeStore.cs │ │ │ ├── TestAggregate.cs │ │ │ └── TestDependency.cs │ │ └── Eventuous.Tests.Extensions.AspNetCore │ │ ├── AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRouteWithGenericAttr_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt │ │ ├── AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRoute_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt │ │ ├── AggregateCommandsTests.MapAggregateContractToCommandExplicitly_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt │ │ ├── AggregateCommandsTests.MapEnrichedCommand_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt │ │ ├── AggregateCommandsTests.cs │ │ ├── ControllerTests.cs │ │ ├── DiscoveredCommandsTests.CallDiscoveredCommandRoute_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt │ │ ├── DiscoveredCommandsTests.cs │ │ ├── Eventuous.Tests.Extensions.AspNetCore.csproj │ │ └── Fixture │ │ ├── Commands.cs │ │ ├── Enricher.cs │ │ ├── Module.cs │ │ ├── ServerFixture.cs │ │ ├── TestAggregate.cs │ │ └── TestBaseWithLogs.cs ├── Gateway │ ├── src │ │ └── Eventuous.Gateway │ │ │ ├── Eventuous.Gateway.csproj │ │ │ ├── GatewayHandler.cs │ │ │ ├── GatewayHandlerFactory.cs │ │ │ ├── GatewayMessage.cs │ │ │ ├── GatewayMetaHelper.cs │ │ │ ├── GatewayProducer.cs │ │ │ ├── IGatewayTransform.cs │ │ │ └── Registrations │ │ │ └── GatewayRegistrations.cs │ └── test │ │ └── Eventuous.Tests.Gateway │ │ ├── Eventuous.Tests.Gateway.csproj │ │ └── RegistrationTests.cs ├── GooglePubSub │ ├── src │ │ ├── Eventuous.GooglePubSub.CloudRun │ │ │ ├── CloudRunPubSubSubscription.cs │ │ │ ├── CloudRunPubSubSubscriptionOptions.cs │ │ │ ├── EndpointMappingExtensions.cs │ │ │ └── Eventuous.GooglePubSub.CloudRun.csproj │ │ └── Eventuous.GooglePubSub │ │ │ ├── Eventuous.GooglePubSub.csproj │ │ │ ├── Producers │ │ │ ├── ClientCache.cs │ │ │ ├── GooglePubSubProducer.cs │ │ │ ├── PubSubProduceOptions.cs │ │ │ └── PubSubProducerOptions.cs │ │ │ ├── PubSubAttributes.cs │ │ │ ├── README.md │ │ │ ├── Shared │ │ │ └── PubSub.cs │ │ │ └── Subscriptions │ │ │ ├── GooglePubSubSubscription.cs │ │ │ └── PubSubSubscriptionOptions.cs │ └── test │ │ └── Eventuous.Tests.GooglePubSub │ │ ├── Eventuous.Tests.GooglePubSub.csproj │ │ ├── PubSubFixture.cs │ │ └── PubSubTests.cs ├── Kafka │ ├── docker-compose.yml │ ├── src │ │ └── Eventuous.Kafka │ │ │ ├── Eventuous.Kafka.csproj │ │ │ ├── KafkaHeaderKeys.cs │ │ │ ├── MetadataExtensions.cs │ │ │ ├── Producers │ │ │ ├── KafkaBasicProducer.cs │ │ │ ├── KafkaProduceOptions.cs │ │ │ └── KafkaProducerOptions.cs │ │ │ ├── README.md │ │ │ └── Subscriptions │ │ │ ├── KafkaBasicSubscription.cs │ │ │ └── KafkaSubscriptionOptions.cs │ ├── start.sh │ └── test │ │ └── Eventuous.Tests.Kafka │ │ ├── BasicProducerTests.cs │ │ ├── Eventuous.Tests.Kafka.csproj │ │ └── KafkaFixture.cs ├── Mongo │ ├── docker-compose.yml │ ├── src │ │ └── Eventuous.Projections.MongoDB │ │ │ ├── Eventuous.Projections.MongoDB.csproj │ │ │ ├── Eventuous.Projections.MongoDB.csproj.DotSettings │ │ │ ├── Functional.cs │ │ │ ├── MongoCheckpointStore.cs │ │ │ ├── MongoOperationBuilder.cs │ │ │ ├── MongoProjector.cs │ │ │ ├── Operations │ │ │ ├── BulkBuilder.cs │ │ │ ├── DeleteBuilder.cs │ │ │ ├── InsertBuilder.cs │ │ │ └── UpdateBuilder.cs │ │ │ ├── Options.cs │ │ │ ├── README.md │ │ │ └── Tools │ │ │ ├── Document.cs │ │ │ ├── MongoCollectionExtensions.cs │ │ │ ├── MongoCollectionName.cs │ │ │ ├── MongoDatabaseExtensions.cs │ │ │ └── MongoDefaults.cs │ └── test │ │ └── Eventuous.Tests.Projections.MongoDB │ │ ├── Eventuous.Tests.Projections.MongoDB.csproj │ │ ├── Fixtures │ │ ├── DomainFixture.cs │ │ └── IntegrationFixture.cs │ │ ├── ProjectWithBuilder.cs │ │ ├── ProjectWithBulkBuilder.cs │ │ ├── ProjectingWithTypedHandlers.cs │ │ └── ProjectionTestBase.cs ├── Postgres │ ├── src │ │ └── Eventuous.Postgresql │ │ │ ├── Eventuous.Postgresql.csproj │ │ │ ├── Extensions │ │ │ ├── PostgresExtensions.cs │ │ │ └── RegistrationExtensions.cs │ │ │ ├── PostgresStore.cs │ │ │ ├── Projections │ │ │ └── PostgresProjector.cs │ │ │ ├── Schema.cs │ │ │ ├── SchemaInitializer.cs │ │ │ ├── Scripts │ │ │ ├── 1_Schema.sql │ │ │ ├── 2_AppendEvents.sql │ │ │ ├── 3_CheckStream.sql │ │ │ ├── 4_ReadAllForwards.sql │ │ │ ├── 5_ReadStreamBackwards.sql │ │ │ ├── 6_ReadStreamForwards.sql │ │ │ ├── 7_ReadStreamSub.sql │ │ │ └── 8_TruncateStream.sql │ │ │ └── Subscriptions │ │ │ ├── PostgresAllStreamSubscription.cs │ │ │ ├── PostgresCheckpointStore.cs │ │ │ ├── PostgresStreamSubscription.cs │ │ │ └── PostgresSubscriptionBase.cs │ └── test │ │ └── Eventuous.Tests.Postgres │ │ ├── Eventuous.Tests.Postgres.csproj │ │ ├── Fixtures │ │ └── PostgresContainer.cs │ │ ├── Metrics │ │ ├── MetricsFixture.cs │ │ └── MetricsTests.cs │ │ ├── Projections │ │ └── ProjectorTests.cs │ │ ├── Registrations │ │ └── RegistrationTests.cs │ │ ├── Store │ │ ├── StoreFixture.cs │ │ ├── StoreTests.cs │ │ └── TieredStoreTests.cs │ │ └── Subscriptions │ │ ├── SubscribeTests.cs │ │ └── SubscriptionFixture.cs ├── RabbitMq │ ├── docker-compose.yml │ ├── src │ │ └── Eventuous.RabbitMq │ │ │ ├── Diagnostics │ │ │ └── RabbitMqTelemetryTags.cs │ │ │ ├── Eventuous.RabbitMq.csproj │ │ │ ├── Producers │ │ │ ├── ExchangeCache.cs │ │ │ ├── RabbitMqProduceOptions.cs │ │ │ └── RabbitMqProducer.cs │ │ │ ├── README.md │ │ │ ├── Shared │ │ │ └── RabbitMqExchangeOptions.cs │ │ │ └── Subscriptions │ │ │ ├── RabbitMqSubscription.cs │ │ │ ├── RabbitMqSubscriptionOptions.cs │ │ │ └── Timestamp.cs │ └── test │ │ └── Eventuous.Tests.RabbitMq │ │ ├── Eventuous.Tests.RabbitMq.csproj │ │ ├── RabbitMqFixture.cs │ │ └── SubscriptionSpec.cs ├── Redis │ ├── docker-compose.yml │ ├── src │ │ └── Eventuous.Redis │ │ │ ├── Eventuous.Redis.csproj │ │ │ ├── Module.cs │ │ │ ├── RedisKeys.cs │ │ │ ├── RedisStore.cs │ │ │ ├── Scripts │ │ │ └── AppendEvents.lua │ │ │ ├── Subscriptions │ │ │ ├── ReceivedEvent.cs │ │ │ ├── RedisAllStreamSubscription.cs │ │ │ ├── RedisCheckpointStore.cs │ │ │ ├── RedisStreamSubscription.cs │ │ │ └── RedisSubscriptionBase.cs │ │ │ └── Tools │ │ │ └── Conversions.cs │ └── test │ │ └── Eventuous.Tests.Redis │ │ ├── Eventuous.Tests.Redis.csproj │ │ ├── Fixtures │ │ ├── DomainFixture.cs │ │ ├── IntegrationFixture.cs │ │ └── SubscriptionFixture.cs │ │ ├── Store │ │ ├── Append.cs │ │ ├── Helpers.cs │ │ └── Read.cs │ │ └── Subscriptions │ │ ├── SubscribeToAll.cs │ │ └── SubscribeToStream.cs ├── Relational │ └── src │ │ └── Eventuous.Sql.Base │ │ ├── Eventuous.Sql.Base.csproj │ │ ├── PersistedEvent.cs │ │ ├── Producers │ │ └── PostgresProducer.cs │ │ ├── ReaderExtensions.cs │ │ ├── SqlEventStoreBase.cs │ │ └── Subscriptions │ │ ├── SqlSubscriptionBase.cs │ │ └── SqlSubscriptionOptionsBase.cs ├── SqlServer │ ├── src │ │ └── Eventuous.SqlServer │ │ │ ├── ConnectionFactory.cs │ │ │ ├── Eventuous.SqlServer.csproj │ │ │ ├── Extensions │ │ │ ├── RegistrationExtensions.cs │ │ │ └── SqlExtensions.cs │ │ │ ├── Projections │ │ │ ├── SqlServerConnectionOptions.cs │ │ │ └── SqlServerProjector.cs │ │ │ ├── Schema.cs │ │ │ ├── SchemaInitializer.cs │ │ │ ├── Scripts │ │ │ ├── 1_Schema.sql │ │ │ ├── 2_AppendEvents.sql │ │ │ ├── 3_CheckStream.sql │ │ │ ├── 4_ReadAllForwards.sql │ │ │ ├── 5_ReadStreamBackwards.sql │ │ │ ├── 6_ReadStreamForwards.sql │ │ │ ├── 7_ReadStreamSub.sql │ │ │ └── 8_TruncateStream.sql │ │ │ ├── SqlServerStore.cs │ │ │ └── Subscriptions │ │ │ ├── SqlServerAllStreamSubscription.cs │ │ │ ├── SqlServerCheckpointStore.cs │ │ │ ├── SqlServerStreamSubscription.cs │ │ │ ├── SqlServerStreamSubscriptionOptions.cs │ │ │ └── SqlServerSubscriptionBase.cs │ └── test │ │ └── Eventuous.Tests.SqlServer │ │ ├── Eventuous.Tests.SqlServer.csproj │ │ ├── Fixtures │ │ └── SqlContainer.cs │ │ ├── Limiter.cs │ │ ├── Metrics │ │ ├── MetricsFixture.cs │ │ └── MetricsTests.cs │ │ ├── Projections │ │ └── ProjectorTests.cs │ │ ├── Registrations │ │ └── RegistrationTests.cs │ │ ├── Store │ │ ├── StoreFixture.cs │ │ ├── StoreTests.cs │ │ └── TieredStoreTests.cs │ │ └── Subscriptions │ │ ├── SubscribeTests.cs │ │ └── SubscriptionFixture.cs └── Testing │ └── src │ └── Eventuous.Testing │ ├── AggregateFactoryExtensions.cs │ ├── AggregateSpec.cs │ ├── AggregateWithIdSpec.cs │ ├── CommandServiceFixture.cs │ ├── Eventuous.Testing.csproj │ └── InMemoryEventStore.cs └── test ├── Directory.Build.props ├── Directory.Build.targets ├── Eventuous.Sut.App ├── BookingService.cs ├── Commands.cs └── Eventuous.Sut.App.csproj ├── Eventuous.Sut.Domain ├── Booking.cs ├── BookingEvents.cs ├── BookingState.cs ├── Eventuous.Sut.Domain.csproj ├── Money.cs └── StayPeriod.cs ├── Eventuous.TestHelpers.TUnit ├── Assertions.cs ├── Eventuous.TestHelpers.TUnit.csproj ├── Logging │ ├── LoggingExtensions.cs │ └── TUnitLogger.cs └── TestEventListener.cs ├── Eventuous.TestHelpers ├── Eventuous.TestHelpers.csproj ├── RecordedTrace.cs ├── TestHelper.cs └── TestPrimitives.cs └── xunit.runner.json /.dockerignore: -------------------------------------------------------------------------------- 1 | # Git 2 | **/.git 3 | **/.gitattributes 4 | **/.gitignore 5 | 6 | # GitLab 7 | **/.gitlab-ci.yml 8 | 9 | # JetBrains 10 | **/.idea/ 11 | **/*DotSettings* 12 | 13 | # Node 14 | **/logs 15 | **/*.log 16 | **/npm-debug.log* 17 | **/yarn-debug.log* 18 | **/yarn-error.log* 19 | **/node_modules/ 20 | 21 | # Visual Studio Code 22 | **/.vs 23 | **/.vscode 24 | **/*.code-workspace 25 | 26 | # .Net 27 | **/bin/ 28 | **/obj/ 29 | **/wwwroot/ 30 | **/*.trx 31 | 32 | # Docker 33 | **/Dockerfile* 34 | **/docker-compose* 35 | 36 | # macOS 37 | **/*.DS_Store 38 | **/.AppleDouble 39 | **/.LSOverride 40 | **/._* 41 | 42 | # Others 43 | **/*.md 44 | 45 | 46 | tools/ 47 | docs/ 48 | artifacts/ 49 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Eventuous 4 | 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | 8 | - package-ecosystem: "nuget" 9 | directory: "/" 10 | open-pull-requests-limit: 0 11 | schedule: 12 | interval: "daily" -------------------------------------------------------------------------------- /.github/workflows/code_quality.yml.txt: -------------------------------------------------------------------------------- 1 | name: Qodana 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: 7 | - dev 8 | 9 | jobs: 10 | qodana: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - 17 | name: 'Qodana Scan' 18 | uses: JetBrains/qodana-action@v2023.2 19 | with: 20 | pr-mode: false 21 | env: 22 | QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/pull-request-docs.yml: -------------------------------------------------------------------------------- 1 | name: Build and test PRs with docs 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - docs/** 7 | 8 | jobs: 9 | docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Install pnpm 14 | uses: pnpm/action-setup@v4 15 | with: 16 | version: 9 17 | - 18 | name: Checkout 19 | uses: actions/checkout@v4 20 | - 21 | name: Build docs 22 | run: | 23 | cd docs 24 | pnpm install 25 | pnpm build 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | Here are simple rules that we expect to be followed by contributors: 4 | 5 | - Preferrably, open an issue before submitting a contribution, unless there's an existing issue you are solving. 6 | - Restrain yourself from rants and complaints when you contribute in issues and PR discussions. Respect the people who maintain this project. 7 | - Avoid large PRs addressing multiple issues at once. 8 | - Avoid making unrelated changes like reformatting the code in places that aren't related to the actual change. 9 | - Use the code formats of the repository, we have them in the `.editorconfig` file. Reformat the code accordingly if you copy it from somewhere. 10 | - Only contribute with your own work, or if you have a permission from the others to contribute their code. 11 | 12 | Thanks for contributing! 13 | -------------------------------------------------------------------------------- /Eventuous.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/diagnostics/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Diagnostics", 3 | "position": 9, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Observability of systems powered by Eventuous." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/faq/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "FAQ", 3 | "position": 100, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Find answers to most common questions here." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/faq/compatibility.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Compatibility" 3 | description: "Platforms and SDKs" 4 | --- 5 | 6 | ## Only .NET 6+? Why not .NET Framework 3.5? 7 | 8 | Eventuous uses the latest features of C#, like records and advanced pattern matching. Therefore, we rely on compiler versions which supports C# 11. 9 | 10 | We also aim to support the current application hosting model that only got consistent and stable in .NET 6+. 11 | 12 | Eventuous supports .NET Core 3.1, but it's not a priority. Some packages only support .NET 6 and .NET 7 as they need the latest features like minimal API. Right now, Eventuous provides packages for the following runtimes: 13 | 14 | - .NET Core 3.1 15 | - .NET 6 16 | - .NET 7 17 | 18 | Targets will be added and removed when getting our of support or when new versions get released. 19 | 20 | -------------------------------------------------------------------------------- /docs/docs/infra/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Infrastructure", 3 | "position": 10, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Supported infrastructure." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/infra/elastic/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Elasticsearch" 3 | description: "Storing and archiving events in Elasticsearch" 4 | --- 5 | 6 | WIP -------------------------------------------------------------------------------- /docs/docs/infra/mssql/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Microsoft SQL Server" 3 | description: "Supported Microsoft SQL Server infrastructure" 4 | sidebar_position: 4 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/docs/infra/rabbitmq/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "RabbitMQ" 3 | description: "Producers and subscriptions for RabbitMQ" 4 | sidebar_position: 5 5 | --- 6 | 7 | WIP -------------------------------------------------------------------------------- /docs/docs/persistence/aggregate-store/images/reading-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/persistence/aggregate-store/images/reading-dark.png -------------------------------------------------------------------------------- /docs/docs/persistence/aggregate-store/images/reading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/persistence/aggregate-store/images/reading.png -------------------------------------------------------------------------------- /docs/docs/persistence/aggregate-store/images/replication-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/persistence/aggregate-store/images/replication-dark.png -------------------------------------------------------------------------------- /docs/docs/persistence/aggregate-store/images/replication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/persistence/aggregate-store/images/replication.png -------------------------------------------------------------------------------- /docs/docs/persistence/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Persistence" 3 | weight: 300 4 | description: > 5 | Event-sourced persistence abstractions and implementations. 6 | --- 7 | import DocCardList from '@theme/DocCardList'; 8 | 9 | As mentioned in the [Prologue](../prologue), Event Sourcing is a way to persist state. Therefore, the way to handle _persistence_ is one of the fundamental differences of event-sourced systems, compared with state-based systems. 10 | 11 | Read more about essential concepts of event-sourced persistence. 12 | 13 | :::info 14 | Make sure to read about [events serialisation](serialisation). 15 | ::: 16 | 17 | -------------------------------------------------------------------------------- /docs/docs/producers/providers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Available producers" 3 | description: "List of producers available in Eventuous" 4 | --- 5 | 6 | Eventuous supports the following producers: 7 | 8 | - [EventStoreDB](../infra/esdb/index.md#producer) 9 | - [Apache Kafka](../infra/kafka/index.md#producer) 10 | - [RabbitMQ](../infra/rabbitmq/index.md#producer) 11 | - [Google PubSub](../infra/pubsub/index.md#producer) 12 | - [Elasticsearch](../infra/elastic/index.md) -------------------------------------------------------------------------------- /docs/docs/prologue/quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Quick start" 3 | description: "Sample application" 4 | sidebar_position: 2 5 | --- 6 | 7 | You can find a bookings sample application in the following sample repositories: 8 | - [.NET EventStoreDB write => MongoDB read](https://github.com/Eventuous/dotnet-sample) 9 | - [.NET PostgreSQL write => MongoDB read](https://github.com/Eventuous/dotnet-sample-postgres) 10 | 11 | All samples are being updated with the latest features and improvements. 12 | -------------------------------------------------------------------------------- /docs/docs/prologue/the-right-way/images/flaming-bus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/prologue/the-right-way/images/flaming-bus.jpg -------------------------------------------------------------------------------- /docs/docs/prologue/the-right-way/images/the-right-way-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/prologue/the-right-way/images/the-right-way-dark.png -------------------------------------------------------------------------------- /docs/docs/prologue/the-right-way/images/the-right-way.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/prologue/the-right-way/images/the-right-way.png -------------------------------------------------------------------------------- /docs/docs/read-models/supported-projectors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Supported projectors 3 | description: Built-in projectors supported by Eventuous. 4 | --- 5 | 6 | Eventuous supports the following projection targets: 7 | 8 | - MongoDB using [MongoDB projections](/docs/infra/mongodb) 9 | - PostgreSQL using [PostgreSQL projections](/docs/infra/postgres/#projections) 10 | 11 | You can project to any other database using a custom projector, which can be built as a [custom event handler](/docs/subscriptions/eventhandler/#custom-handlers). -------------------------------------------------------------------------------- /docs/docs/subscriptions/checkpoint/images/commit-handler-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/checkpoint/images/commit-handler-dark.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/checkpoint/images/commit-handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/checkpoint/images/commit-handler.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/pipes/images/concurrent-filter-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/pipes/images/concurrent-filter-dark.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/pipes/images/concurrent-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/pipes/images/concurrent-filter.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/pipes/images/default-pipe-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/pipes/images/default-pipe-dark.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/pipes/images/default-pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/pipes/images/default-pipe.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/pipes/images/partitioning-filter-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/pipes/images/partitioning-filter-dark.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/pipes/images/partitioning-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/pipes/images/partitioning-filter.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/subs-concept/images/subscriptions-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/subs-concept/images/subscriptions-dark.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/subs-concept/images/subscriptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/subs-concept/images/subscriptions.png -------------------------------------------------------------------------------- /docs/docs/subscriptions/subs-diagnostics/images/sub-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/docs/subscriptions/subs-diagnostics/images/sub-trace.png -------------------------------------------------------------------------------- /docs/docs/whats-new.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # What's new in vNext 6 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/components/highlight.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function Highlight({children, color}) { 4 | return ( 5 | 12 | {children} 13 | 14 | ); 15 | } -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/plugins/posthog/posthog.js: -------------------------------------------------------------------------------- 1 | import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; 2 | 3 | export default (function () { 4 | if (!ExecutionEnvironment.canUseDOM) { 5 | return null; 6 | } 7 | 8 | return { 9 | onRouteUpdate() { 10 | window.posthog.capture('$pageview'); 11 | }, 12 | }; 13 | })(); -------------------------------------------------------------------------------- /docs/src/plugins/segment/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function (context, fromOptions) { 4 | const isProd = true; //process.env.NODE_ENV === 'production'; 5 | 6 | return { 7 | name: 'segment', 8 | 9 | getClientModules() { 10 | return isProd ? [path.resolve(__dirname, './segment')] : []; 11 | }, 12 | }; 13 | }; -------------------------------------------------------------------------------- /docs/src/plugins/segment/segment.js: -------------------------------------------------------------------------------- 1 | import ExecutionEnvironment from "@docusaurus/ExecutionEnvironment"; 2 | 3 | export default (function () { 4 | if (!ExecutionEnvironment.canUseDOM) { 5 | return null; 6 | } 7 | 8 | return { 9 | onRouteUpdate() { 10 | // if (!window.analytics) return; 11 | 12 | // setTimeout(() => window.analytics.page(), 0); 13 | }, 14 | }; 15 | })(); -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/_redirects: -------------------------------------------------------------------------------- 1 | /docs/sinks/elasticsearch/ https://connect.eventuous.dev/docs/sinks/elasticsearch/ 301 2 | /docs/deployment/ https://connect.eventuous.dev/docs/deployment/ 301 3 | /docs/custom-connector/ https://connect.eventuous.dev/docs/custom-connector/ 301 4 | /docs/category/sinks https://connect.eventuous.dev/docs/category/sinks 301 5 | /docs/category/projectors https://connect.eventuous.dev/docs/category/projectors 301 6 | /docs/sinks/grpc/ https://connect.eventuous.dev/docs/sinks/grpc/ 301 7 | /docs/projectors/grpc/ https://connect.eventuous.dev/docs/projectors/grpc/ 301 8 | /docs/sinks/sqlserver/ https://connect.eventuous.dev/docs/sinks/sqlserver/ 301 9 | /docs/sinks/mongodb/ https://connect.eventuous.dev/docs/sinks/mongodb/ 301 -------------------------------------------------------------------------------- /docs/static/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/img/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/static/img/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/img/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/static/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/img/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/static/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/img/favicon-16x16.png -------------------------------------------------------------------------------- /docs/static/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/img/favicon-32x32.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/featured-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/img/featured-background.jpg -------------------------------------------------------------------------------- /docs/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/img/logo.png -------------------------------------------------------------------------------- /docs/static/img/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /docs/static/img/social-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/static/img/social-card.png -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/diagnostics/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Diagnostics", 3 | "position": 9, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Observability of applications build with Eventuous. WIP" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/diagnostics/index.md: -------------------------------------------------------------------------------- 1 | # Diagnostics 2 | 3 | Eventuous provides built-in metrics and traces for: 4 | - Event store 5 | - Subscriptions, consumers, and event handlers 6 | - Command services 7 | 8 | The built-in diagnostics integrate with [OpenTelemetry](https://opentelemetry.io/) and [Prometheus](https://prometheus.io/). 9 | 10 | The documentation for diagnostics is not complete yet. Please refer to the [samples](https://github.com/eventuous/dotnet-sample) for now. You can also find more information about diagnostics for subscriptions on the [subscriptions diagnostics](../subscriptions/subs-diagnostics) page. -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/faq/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "FAQ", 3 | "position": 100, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Find answers to most common questions here." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/faq/compatibility.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Compatibility" 3 | description: "Platforms and SDKs" 4 | --- 5 | 6 | ## Only .NET 6+? Why not .NET Framework 3.5? 7 | 8 | Eventuous uses the latest features of C#, like records and advanced pattern matching. Therefore, we rely on compiler versions which supports C# 11. 9 | 10 | We also aim to support the current application hosting model that only got consistent and stable in .NET 6+. 11 | 12 | Eventuous supports .NET Core 3.1, but it's not a priority. Some packages only support .NET 6 and .NET 7 as they need the latest features like minimal API. Right now, Eventuous provides packages for the following runtimes: 13 | 14 | - .NET Core 3.1 15 | - .NET 6 16 | - .NET 7 17 | 18 | Targets will be added and removed when getting our of support or when new versions get released. 19 | 20 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/infra/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Infrastructure", 3 | "position": 10, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Supported infrastructure." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/infra/elastic/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Elasticsearch" 3 | description: "Storing and archiving events in Elasticsearch" 4 | --- 5 | 6 | WIP -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/infra/kafka/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Apache Kafka" 3 | description: "Producers and subscriptions for Kafka" 4 | --- 5 | 6 | WIP 7 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/infra/mssql/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Microsoft SQL Server" 3 | description: "Supported Microsoft SQL Server infrastructure" 4 | sidebar_position: 4 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/infra/pubsub/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Google PubSub" 3 | description: "Producers and subscriptions for Google PubSub" 4 | sidebar_position: 6 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/infra/rabbitmq/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "RabbitMQ" 3 | description: "Producers and subscriptions for RabbitMQ" 4 | sidebar_position: 5 5 | --- 6 | 7 | WIP -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/persistence/aggregate-store/images/reading-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/persistence/aggregate-store/images/reading-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/persistence/aggregate-store/images/reading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/persistence/aggregate-store/images/reading.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/persistence/aggregate-store/images/replication-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/persistence/aggregate-store/images/replication-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/persistence/aggregate-store/images/replication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/persistence/aggregate-store/images/replication.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/persistence/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Persistence" 3 | weight: 300 4 | description: > 5 | Event-sourced persistence abstractions and implementations. 6 | --- 7 | import DocCardList from '@theme/DocCardList'; 8 | 9 | As mentioned in the [Prologue](../prologue), Event Sourcing is a way to persist state. Therefore, the way to handle _persistence_ is one of the fundamental differences of event-sourced systems, compared with state-based systems. 10 | 11 | Read more about essential concepts of event-sourced persistence. 12 | 13 | :::info 14 | Make sure to read about [events serialisation](serialisation). 15 | ::: 16 | 17 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/producers/providers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Available producers" 3 | description: "List of producers available in Eventuous" 4 | --- 5 | 6 | Eventuous supports the following producers: 7 | 8 | - [RabbitMQ](../infra/rabbitmq) 9 | - [EventStoreDB](../infra/esdb/#producer) 10 | - [Google PubSub](../infra/pubsub) 11 | - [Apache Kafka](../infra/kafka) 12 | - [Elasticsearch](../infra/elastic) -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/prologue/quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Quick start" 3 | description: "Sample application" 4 | sidebar_position: 2 5 | --- 6 | 7 | You can find a bookings sample application in the following sample repositories: 8 | - [.NET EventStoreDB write => MongoDB read](https://github.com/Eventuous/dotnet-sample) 9 | - [.NET PostgreSQL write => MongoDB read](https://github.com/Eventuous/dotnet-sample-postgres) 10 | 11 | All samples are being updated with the latest features and improvements. 12 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/prologue/the-right-way/images/flaming-bus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/prologue/the-right-way/images/flaming-bus.jpg -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/prologue/the-right-way/images/the-right-way-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/prologue/the-right-way/images/the-right-way-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/prologue/the-right-way/images/the-right-way.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/prologue/the-right-way/images/the-right-way.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/read-models/supported-projectors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Supported projectors 3 | description: Built-in projectors supported by Eventuous. 4 | --- 5 | 6 | Eventuous supports the following projection targets: 7 | 8 | - MongoDB using [MongoDB projections](/docs/infra/mongodb) 9 | - PostgreSQL using [PostgreSQL projections](/docs/infra/postgres/#projections) 10 | 11 | You can project to any other database using a custom projector, which can be built as a [custom event handler](/docs/subscriptions/eventhandler/#custom-handlers). -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/checkpoint/images/commit-handler-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/checkpoint/images/commit-handler-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/checkpoint/images/commit-handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/checkpoint/images/commit-handler.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/pipes/images/concurrent-filter-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/pipes/images/concurrent-filter-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/pipes/images/concurrent-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/pipes/images/concurrent-filter.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/pipes/images/default-pipe-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/pipes/images/default-pipe-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/pipes/images/default-pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/pipes/images/default-pipe.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/pipes/images/partitioning-filter-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/pipes/images/partitioning-filter-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/pipes/images/partitioning-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/pipes/images/partitioning-filter.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/subs-concept/images/subscriptions-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/subs-concept/images/subscriptions-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/subs-concept/images/subscriptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/subs-concept/images/subscriptions.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.14/subscriptions/subs-diagnostics/images/sub-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.14/subscriptions/subs-diagnostics/images/sub-trace.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/application/_service_http.mdx: -------------------------------------------------------------------------------- 1 | ## Application HTTP API 2 | 3 | The most common use case is to connect the command service to an HTTP API using controllers or minimal API mappings. 4 | 5 | Read the [Command API](./command-api) feature documentation for more details. 6 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/application/_service_no_throw.mdx: -------------------------------------------------------------------------------- 1 | :::caution Handling failures 2 | The last point above translates to: the command service **does not throw exceptions**. It [returns](#result) an instance of `Result.Error` instead. It is your responsibility to handle the error. 3 | ::: 4 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/application/_service_registration.mdx: -------------------------------------------------------------------------------- 1 | ## Bootstrap 2 | 3 | If you registered an implementation of `IEventStore` in the DI container, you can also register the command service: 4 | 5 | ```csharp title="Program.cs" 6 | builder.Services.AddCommandService(); 7 | ``` 8 | 9 | The `AddCommandService` extension will register the `BookingService`, and also as `ICommandService`, as a singleton. Remember that all the DI extensions are part of the `Eventuous.Extensions.DependencyInjection` NuGet package. 10 | 11 | When you also use `AddControllers`, you get the command service injected to your controllers. 12 | 13 | You can simplify your application and avoid creating HTTP endpoints explicitly (as controllers or minimal API endpoints) if you use the [command API feature](command-api.md). 14 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/diagnostics/logs.md: -------------------------------------------------------------------------------- 1 | # Logging 2 | 3 | Eventuous uses the ASP.NET Core logging with `ILoggerFactory` and `ILogger`, so you can use the standard logging facilities to log diagnostics. For internal logging, Eventuous uses multiple [event sources](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/eventsource), which you can collect and analyse with tools like `dotnet-trace`. 4 | 5 | It's also possible to expose internal Eventuous logs using the `UseEventuousLogs` extension for `IHost`, which is available as part of `Eventuous.Extensions.DependencyInjection` package. 6 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/faq/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "FAQ", 3 | "position": 100, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Find answers to most common questions here." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/faq/compatibility.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Compatibility" 3 | description: "Platforms and SDKs" 4 | --- 5 | 6 | ## Only .NET 6+? Why not .NET Framework 3.5? 7 | 8 | Eventuous uses the latest features of C#, like records and advanced pattern matching. Therefore, we rely on compiler versions which supports C# 11. 9 | 10 | We also aim to support the current application hosting model that only got consistent and stable in .NET 6+. 11 | 12 | Eventuous supports .NET Core 3.1, but it's not a priority. Some packages only support .NET 6 and .NET 8 as they need the latest features like minimal API. Right now, Eventuous provides packages for the following runtimes: 13 | 14 | - .NET 6 15 | - .NET 8 16 | 17 | Targets will be added and removed when getting our of support or when new versions get released. 18 | 19 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/infra/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Infrastructure", 3 | "position": 10, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Supported infrastructure." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/infra/elastic/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Elasticsearch" 3 | description: "Storing and archiving events in Elasticsearch" 4 | --- 5 | 6 | WIP -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/persistence/images/reading-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/persistence/images/reading-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/persistence/images/reading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/persistence/images/reading.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/persistence/images/replication-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/persistence/images/replication-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/persistence/images/replication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/persistence/images/replication.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/persistence/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Persistence" 3 | weight: 300 4 | description: > 5 | Event-sourced persistence abstractions and implementations. 6 | --- 7 | import DocCardList from '@theme/DocCardList'; 8 | 9 | As mentioned in the [Prologue](../prologue), Event Sourcing is a way to persist state. Therefore, the way to handle _persistence_ is one of the fundamental differences of event-sourced systems, compared with state-based systems. 10 | 11 | Read more about essential concepts of event-sourced persistence. 12 | 13 | :::info 14 | Make sure to read about [events serialisation](serialisation). 15 | ::: 16 | 17 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/producers/providers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Available producers" 3 | description: "List of producers available in Eventuous" 4 | --- 5 | 6 | Eventuous supports the following producers: 7 | 8 | - [EventStoreDB](../infra/esdb/index.md#producer) 9 | - [Apache Kafka](../infra/kafka/index.md#producer) 10 | - [RabbitMQ](../infra/rabbitmq/index.md#producer) 11 | - [Google PubSub](../infra/pubsub/index.md#producer) 12 | - [Elasticsearch](../infra/elastic/index.md) -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/prologue/quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Quick start" 3 | description: "Sample application" 4 | sidebar_position: 2 5 | --- 6 | 7 | You can find a bookings sample application in the following sample repositories: 8 | - [.NET EventStoreDB write => MongoDB read](https://github.com/Eventuous/dotnet-sample) 9 | - [.NET PostgreSQL write => MongoDB read](https://github.com/Eventuous/dotnet-sample-postgres) 10 | 11 | All samples are being updated with the latest features and improvements. 12 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/prologue/the-right-way/images/flaming-bus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/prologue/the-right-way/images/flaming-bus.jpg -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/prologue/the-right-way/images/the-right-way-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/prologue/the-right-way/images/the-right-way-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/prologue/the-right-way/images/the-right-way.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/prologue/the-right-way/images/the-right-way.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/read-models/supported-projectors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Supported projectors 3 | description: Built-in projectors supported by Eventuous. 4 | --- 5 | 6 | Eventuous supports the following projection targets: 7 | 8 | - MongoDB using [MongoDB projections](/docs/infra/mongodb) 9 | - PostgreSQL using [PostgreSQL projections](/docs/infra/postgres/#projections) 10 | 11 | You can project to any other database using a custom projector, which can be built as a [custom event handler](/docs/subscriptions/eventhandler/#custom-handlers). -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/checkpoint/images/commit-handler-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/checkpoint/images/commit-handler-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/checkpoint/images/commit-handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/checkpoint/images/commit-handler.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/pipes/images/concurrent-filter-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/pipes/images/concurrent-filter-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/pipes/images/concurrent-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/pipes/images/concurrent-filter.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/pipes/images/default-pipe-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/pipes/images/default-pipe-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/pipes/images/default-pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/pipes/images/default-pipe.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/pipes/images/partitioning-filter-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/pipes/images/partitioning-filter-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/pipes/images/partitioning-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/pipes/images/partitioning-filter.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/subs-concept/images/subscriptions-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/subs-concept/images/subscriptions-dark.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/subs-concept/images/subscriptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/subs-concept/images/subscriptions.png -------------------------------------------------------------------------------- /docs/versioned_docs/version-0.15/subscriptions/subs-diagnostics/images/sub-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/docs/versioned_docs/version-0.15/subscriptions/subs-diagnostics/images/sub-trace.png -------------------------------------------------------------------------------- /docs/versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | "0.15", 3 | "0.14" 4 | ] 5 | -------------------------------------------------------------------------------- /e-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eventuous/eventuous/035e3921d9f90bca3fd40551a890cdf4075fff4d/e-logo.png -------------------------------------------------------------------------------- /props/Common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /qodana.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | linter: jetbrains/qodana-dotnet:2023.2 3 | dotnet: 4 | solution: Eventuous.sln -------------------------------------------------------------------------------- /samples/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | enable 5 | enable 6 | preview 7 | false 8 | $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)).Parent.FullName) 9 | $(RepoRoot)\src\Core\src 10 | $(RepoRoot)\src 11 | Debug;Release 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/esdb/Bookings.Domain/Bookings.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Debug;Release 4 | AnyCPU 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/esdb/Bookings.Domain/Bookings/BookingId.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | 3 | namespace Bookings.Domain.Bookings; 4 | 5 | public record BookingId(string Value) : Id(Value); -------------------------------------------------------------------------------- /samples/esdb/Bookings.Domain/DomainModule.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Runtime.CompilerServices; 3 | using Eventuous; 4 | 5 | namespace Bookings.Domain; 6 | 7 | static class DomainModule { 8 | [ModuleInitializer] 9 | [SuppressMessage("Usage", "CA2255", MessageId = "The \'ModuleInitializer\' attribute should not be used in libraries")] 10 | internal static void InitializeDomainModule() => TypeMap.RegisterKnownEventTypes(); 11 | } -------------------------------------------------------------------------------- /samples/esdb/Bookings.Domain/RoomId.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | 3 | namespace Bookings.Domain; 4 | 5 | public record RoomId(string Value) : Id(Value); -------------------------------------------------------------------------------- /samples/esdb/Bookings.Domain/Services.cs: -------------------------------------------------------------------------------- 1 | namespace Bookings.Domain; 2 | 3 | public static class Services { 4 | public delegate ValueTask IsRoomAvailable(RoomId roomId, StayPeriod period); 5 | 6 | public delegate Money ConvertCurrency(Money from, string targetCurrency); 7 | } -------------------------------------------------------------------------------- /samples/esdb/Bookings.Domain/StayPeriod.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | using NodaTime; 3 | 4 | namespace Bookings.Domain; 5 | 6 | public record StayPeriod { 7 | public LocalDate CheckIn { get; } 8 | public LocalDate CheckOut { get; } 9 | 10 | internal StayPeriod() { } 11 | 12 | public StayPeriod(LocalDate checkIn, LocalDate checkOut) { 13 | if (checkIn > checkOut) throw new DomainException("Check in date must be before check out date"); 14 | 15 | (CheckIn, CheckOut) = (checkIn, checkOut); 16 | } 17 | } -------------------------------------------------------------------------------- /samples/esdb/Bookings.Payments/Application/CommandApi.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Payments.Domain; 2 | using Eventuous; 3 | using Eventuous.Extensions.AspNetCore; 4 | using Microsoft.AspNetCore.Mvc; 5 | using static Bookings.Payments.Application.PaymentCommands; 6 | 7 | namespace Bookings.Payments.Application; 8 | 9 | [Route("payment")] 10 | public class CommandApi(ICommandService service) : CommandHttpApiBase(service) { 11 | [HttpPost] 12 | public Task.Ok>> RegisterPayment([FromBody] RecordPayment cmd, CancellationToken cancellationToken) 13 | => Handle(cmd, cancellationToken); 14 | } -------------------------------------------------------------------------------- /samples/esdb/Bookings.Payments/Domain/Payment.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | using static Bookings.Payments.Domain.PaymentEvents; 3 | 4 | namespace Bookings.Payments.Domain; 5 | 6 | public class Payment : Aggregate { 7 | public void ProcessPayment(string bookingId, Money amount, string method, string provider) 8 | => Apply(new PaymentRecorded(bookingId, amount.Amount, amount.Currency, method, provider)); 9 | } 10 | 11 | public record PaymentState : State { 12 | public string BookingId { get; init; } = null!; 13 | public float Amount { get; init; } 14 | 15 | public PaymentState() { 16 | On((state, recorded) => state with { BookingId = recorded.BookingId, Amount = recorded.Amount }); 17 | } 18 | } 19 | 20 | public record PaymentId(string Value) : Id(Value); 21 | -------------------------------------------------------------------------------- /samples/esdb/Bookings.Payments/Domain/PaymentEvents.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | 3 | namespace Bookings.Payments.Domain; 4 | 5 | public static class PaymentEvents { 6 | [EventType("PaymentRecorded")] 7 | public record PaymentRecorded(string BookingId, float Amount, string Currency, string Method, string Provider); 8 | } 9 | -------------------------------------------------------------------------------- /samples/esdb/Bookings.Payments/Infrastructure/Mongo.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using MongoDB.Driver.Core.Extensions.DiagnosticSources; 3 | 4 | namespace Bookings.Payments.Infrastructure; 5 | 6 | public static class Mongo { 7 | public static IMongoDatabase ConfigureMongo(IConfiguration configuration) { 8 | var settings = MongoClientSettings.FromConnectionString(configuration["Mongo:ConnectionString"]); 9 | settings.ClusterConfigurator = cb => cb.Subscribe(new DiagnosticsActivityEventSubscriber()); 10 | return new MongoClient(settings).GetDatabase(configuration["Mongo:Database"]); 11 | } 12 | } -------------------------------------------------------------------------------- /samples/esdb/Bookings.Payments/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Mongo": { 3 | "ConnectionString": "mongodb://mongoadmin:secret@localhost:27017", 4 | "Database": "Payments" 5 | }, 6 | "EventStore": { 7 | "ConnectionString": "esdb://localhost:2113?tls=false" 8 | }, 9 | "Logging": { 10 | "LogLevel": { 11 | "Default": "Debug", 12 | "Microsoft.AspNetCore": "Warning" 13 | } 14 | }, 15 | "AllowedHosts": "*" 16 | } 17 | -------------------------------------------------------------------------------- /samples/esdb/Bookings/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /samples/esdb/Bookings/Application/BookingsQueryService.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Application.Queries; 2 | using Eventuous.Projections.MongoDB.Tools; 3 | using MongoDB.Driver; 4 | 5 | namespace Bookings.Application; 6 | 7 | public class BookingsQueryService(IMongoDatabase database) { 8 | public async Task GetUserBookings(string userId) => await database.LoadDocument(userId); 9 | } 10 | -------------------------------------------------------------------------------- /samples/esdb/Bookings/Application/Commands.cs: -------------------------------------------------------------------------------- 1 | namespace Bookings.Application; 2 | 3 | public static class BookingCommands { 4 | public record BookRoom( 5 | string BookingId, 6 | string GuestId, 7 | string RoomId, 8 | DateTime CheckInDate, 9 | DateTime CheckOutDate, 10 | float BookingPrice, 11 | float PrepaidAmount, 12 | string Currency, 13 | DateTimeOffset BookingDate 14 | ); 15 | 16 | public record RecordPayment(string BookingId, float PaidAmount, string Currency, string PaymentId, string PaidBy); 17 | } -------------------------------------------------------------------------------- /samples/esdb/Bookings/Application/Queries/BookingDocument.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Projections.MongoDB.Tools; 2 | using NodaTime; 3 | 4 | // ReSharper disable UnusedAutoPropertyAccessor.Global 5 | namespace Bookings.Application.Queries; 6 | 7 | public record BookingDocument(string Id) : ProjectedDocument(Id) { 8 | public string GuestId { get; init; } = null!; 9 | public string RoomId { get; init; } = null!; 10 | public LocalDate CheckInDate { get; init; } 11 | public LocalDate CheckOutDate { get; init; } 12 | public float BookingPrice { get; init; } 13 | public float PaidAmount { get; init; } 14 | public float Outstanding { get; init; } 15 | public bool Paid { get; init; } 16 | } 17 | -------------------------------------------------------------------------------- /samples/esdb/Bookings/Application/Queries/MyBookings.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Projections.MongoDB.Tools; 2 | using NodaTime; 3 | 4 | // ReSharper disable CollectionNeverUpdated.Global 5 | 6 | namespace Bookings.Application.Queries; 7 | 8 | public record MyBookings(string Id) : ProjectedDocument(Id) { 9 | public List Bookings { get; init; } = []; 10 | 11 | public record Booking(string BookingId, LocalDate CheckInDate, LocalDate CheckOutDate, float Price); 12 | } 13 | -------------------------------------------------------------------------------- /samples/esdb/Bookings/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base 2 | WORKDIR /app 3 | 4 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 5 | WORKDIR /src 6 | COPY . . 7 | RUN dotnet restore "Bookings/Bookings.csproj" 8 | WORKDIR "/src/Bookings" 9 | RUN dotnet build "Bookings.csproj" -c Release -o /app/build 10 | 11 | FROM build AS publish 12 | RUN dotnet publish "Bookings.csproj" -c Release -o /app/publish 13 | 14 | FROM base AS final 15 | WORKDIR /app 16 | COPY --from=publish /app/publish . 17 | ENTRYPOINT ["dotnet", "Bookings.dll"] 18 | -------------------------------------------------------------------------------- /samples/esdb/Bookings/HttpApi/Bookings/QueryApi.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Domain.Bookings; 2 | using Eventuous; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Bookings.HttpApi.Bookings; 6 | 7 | [Route("/bookings")] 8 | public class QueryApi(IEventStore store) : ControllerBase { 9 | readonly StreamNameMap _streamNameMap = new(); 10 | 11 | [HttpGet] 12 | [Route("{id}")] 13 | public async Task GetBooking(string id, CancellationToken cancellationToken) { 14 | var booking = await store.LoadState(_streamNameMap, new(id), cancellationToken: cancellationToken); 15 | 16 | return booking.State; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/esdb/Bookings/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Mongo": { 3 | "ConnectionString": "mongodb://localhost:27017", 4 | "User": "mongoadmin", 5 | "Password": "secret", 6 | "Database": "Bookings" 7 | }, 8 | "EventStore": { 9 | "ConnectionString": "esdb://localhost:2113?tls=false" 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /samples/esdb/deploy/cloudrun/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /samples/esdb/deploy/cloudrun/Pulumi.dev.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | cloudrun:esdbConnectionString: 3 | secure: AAABAKUgHNKrbteWTPdchE2RY5ylwiVqxpW2ONz3gXXnxrXrIt/51Zv/J072LgF1mK/NCMUogwpOjLAmdw8LL+G7oLxia3CvYod7lZNsbZOficHj2PE2ylICMt94/ONgQMX1H5JfjyqW6LXmChOV 4 | cloudrun:mongoConnectionString: 5 | secure: AAABAInFo85SooX04/SqvBOQT0oFJK9Safq2WBdt9LeGCW+8zBBfQ7iKrVG0CJceIN+ksDX3IVtk0E97oASgoOILME98t2ndNpTSruMogg== 6 | cloudrun:otelEndpoint: https://api.honeycomb.io 7 | cloudrun:otelHeaders: 8 | secure: AAABAOFb1napHEajKJ8i6tunpIwh/iW3bE25DZv7kRtV6fO2w++F+WrU67GzOtJudV2ig5cWy2KSn5snthaoXeWQIl3lzLlaRlJDwyZz0q5G5ECYWckzj8HEhb3Cs/pkxazAcPvrmBXDExhEhwYu0Q== 9 | cloudrun:vpcConnectorName: vpc-connector-044a222 10 | gcp:project: esc-platform-advocacy 11 | gcp:region: europe-west2 12 | -------------------------------------------------------------------------------- /samples/esdb/deploy/cloudrun/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: cloudrun 2 | runtime: nodejs 3 | description: Eventuous sample application deployment to Google Cloud Run 4 | -------------------------------------------------------------------------------- /samples/esdb/deploy/cloudrun/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudrun", 3 | "devDependencies": { 4 | "@types/node": "^20.14.2" 5 | }, 6 | "dependencies": { 7 | "@pulumi/docker": "^4.5.4", 8 | "@pulumi/gcp": "^7.27.0", 9 | "@pulumi/pulumi": "^3.120.0", 10 | "@opentelemetry/api": "^1.9.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/esdb/deploy/cloudrun/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "outDir": "bin", 5 | "target": "es2016", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "experimentalDecorators": true, 10 | "pretty": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "noImplicitReturns": true, 13 | "forceConsistentCasingInFileNames": true 14 | }, 15 | "files": [ 16 | "index.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /samples/esdb/grafana/__inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "prometheus", 6 | "description": "Default data source", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /samples/esdb/grafana/datasources.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: prometheus 5 | type: prometheus 6 | access: proxy 7 | orgId: 1 8 | url: http://prometheus:9090 9 | isDefault: true 10 | version: 1 11 | editable: false -------------------------------------------------------------------------------- /samples/esdb/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 5s 3 | 4 | scrape_configs: 5 | - job_name: 'prometheus' 6 | static_configs: 7 | - targets: ['localhost:9090'] 8 | - job_name: 'bookings' 9 | static_configs: 10 | - targets: 11 | - 'host.docker.internal:5000' -------------------------------------------------------------------------------- /samples/postgres/Bookings.Domain/Bookings.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Debug;Release 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/postgres/Bookings.Payments/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Mongo": { 3 | "ConnectionString": "mongodb://mongoadmin:secret@localhost:27017", 4 | "Database": "Payments" 5 | }, 6 | "Postgres": { 7 | "ConnectionString": "Host=localhost;Username=postgres;Password=secret;Database=eventuous;Include Error Detail=true;", 8 | "Schema": "payments", 9 | "InitializeDatabase": true 10 | }, 11 | "RabbitMq": { 12 | "ConnectionString": "amqp://guest:guest@localhost:5672/" 13 | }, 14 | "Logging": { 15 | "LogLevel": { 16 | "Default": "Debug", 17 | "Microsoft.AspNetCore": "Warning" 18 | } 19 | }, 20 | "AllowedHosts": "*" 21 | } 22 | -------------------------------------------------------------------------------- /samples/postgres/Bookings/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /samples/postgres/Bookings/Application/Commands.cs: -------------------------------------------------------------------------------- 1 | namespace Bookings.Application; 2 | 3 | public static class BookingCommands { 4 | public record BookRoom( 5 | string BookingId, 6 | string GuestId, 7 | string RoomId, 8 | DateTime CheckInDate, 9 | DateTime CheckOutDate, 10 | float BookingPrice, 11 | float PrepaidAmount, 12 | string Currency, 13 | DateTimeOffset BookingDate 14 | ); 15 | 16 | public record RecordPayment(string BookingId, float PaidAmount, string Currency, string PaymentId, string PaidBy); 17 | } -------------------------------------------------------------------------------- /samples/postgres/Bookings/Application/Queries/BookingDocument.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Projections.MongoDB.Tools; 2 | using NodaTime; 3 | 4 | // ReSharper disable UnusedAutoPropertyAccessor.Global 5 | 6 | namespace Bookings.Application.Queries; 7 | 8 | public record BookingDocument(string Id) : ProjectedDocument(Id) { 9 | public string? GuestId { get; init; } 10 | public string? RoomId { get; init; } 11 | public LocalDate CheckInDate { get; init; } 12 | public LocalDate CheckOutDate { get; init; } 13 | public float BookingPrice { get; init; } 14 | public float PaidAmount { get; init; } 15 | public float Outstanding { get; init; } 16 | public bool Paid { get; init; } 17 | } 18 | -------------------------------------------------------------------------------- /samples/postgres/Bookings/Application/Queries/MyBookings.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Projections.MongoDB.Tools; 2 | using NodaTime; 3 | 4 | namespace Bookings.Application.Queries; 5 | 6 | public record MyBookings : ProjectedDocument { 7 | public MyBookings(string id) : base(id) { } 8 | 9 | public List Bookings { get; init; } = new(); 10 | 11 | public record Booking(string BookingId, LocalDate CheckInDate, LocalDate CheckOutDate, float Price); 12 | } -------------------------------------------------------------------------------- /samples/postgres/Bookings/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base 2 | WORKDIR /app 3 | 4 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 5 | WORKDIR /src 6 | COPY . . 7 | RUN dotnet restore "Bookings/Bookings.csproj" 8 | WORKDIR "/src/Bookings" 9 | RUN dotnet build "Bookings.csproj" -c Release -o /app/build 10 | 11 | FROM build AS publish 12 | RUN dotnet publish "Bookings.csproj" -c Release -o /app/publish 13 | 14 | FROM base AS final 15 | WORKDIR /app 16 | COPY --from=publish /app/publish . 17 | ENTRYPOINT ["dotnet", "Bookings.dll"] 18 | -------------------------------------------------------------------------------- /samples/postgres/Bookings/HttpApi/Bookings/QueryApi.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Domain.Bookings; 2 | using Eventuous; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Bookings.HttpApi.Bookings; 6 | 7 | [Route("/bookings")] 8 | public class QueryApi(IEventReader store) : ControllerBase { 9 | readonly StreamNameMap _streamNameMap = new(); 10 | 11 | [HttpGet] 12 | [Route("{id}")] 13 | public async Task GetBooking(string id, CancellationToken cancellationToken) { 14 | var booking = await store.LoadState(_streamNameMap, new(id), cancellationToken: cancellationToken); 15 | 16 | return booking.State; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/postgres/Bookings/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Mongo": { 3 | "ConnectionString": "mongodb://localhost:27017", 4 | "User": "mongoadmin", 5 | "Password": "secret", 6 | "Database": "Bookings" 7 | }, 8 | "Postgres": { 9 | "ConnectionString": "Host=localhost;Username=postgres;Password=secret;Database=eventuous;Include Error Detail=true;", 10 | "Schema": "bookings", 11 | "InitializeDatabase": true 12 | }, 13 | "RabbitMq": { 14 | "ConnectionString": "amqp://guest:guest@localhost:5672/" 15 | }, 16 | "AllowedHosts": "*" 17 | } 18 | -------------------------------------------------------------------------------- /samples/postgres/deploy/cloudrun/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /samples/postgres/deploy/cloudrun/Pulumi.dev.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | cloudrun:esdbConnectionString: 3 | secure: AAABAKUgHNKrbteWTPdchE2RY5ylwiVqxpW2ONz3gXXnxrXrIt/51Zv/J072LgF1mK/NCMUogwpOjLAmdw8LL+G7oLxia3CvYod7lZNsbZOficHj2PE2ylICMt94/ONgQMX1H5JfjyqW6LXmChOV 4 | cloudrun:mongoConnectionString: 5 | secure: AAABAInFo85SooX04/SqvBOQT0oFJK9Safq2WBdt9LeGCW+8zBBfQ7iKrVG0CJceIN+ksDX3IVtk0E97oASgoOILME98t2ndNpTSruMogg== 6 | cloudrun:otelEndpoint: https://api.honeycomb.io 7 | cloudrun:otelHeaders: 8 | secure: AAABAOFb1napHEajKJ8i6tunpIwh/iW3bE25DZv7kRtV6fO2w++F+WrU67GzOtJudV2ig5cWy2KSn5snthaoXeWQIl3lzLlaRlJDwyZz0q5G5ECYWckzj8HEhb3Cs/pkxazAcPvrmBXDExhEhwYu0Q== 9 | cloudrun:vpcConnectorName: vpc-connector-044a222 10 | gcp:project: esc-platform-advocacy 11 | gcp:region: europe-west2 12 | -------------------------------------------------------------------------------- /samples/postgres/deploy/cloudrun/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: cloudrun 2 | runtime: nodejs 3 | description: Eventuous sample application deployment to Google Cloud Run 4 | -------------------------------------------------------------------------------- /samples/postgres/deploy/cloudrun/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudrun", 3 | "devDependencies": { 4 | "@types/node": "^20.14.2" 5 | }, 6 | "dependencies": { 7 | "@pulumi/docker": "^4.5.4", 8 | "@pulumi/gcp": "^7.27.0", 9 | "@pulumi/pulumi": "^3.120.0", 10 | "@opentelemetry/api": "^1.9.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/postgres/deploy/cloudrun/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "outDir": "bin", 5 | "target": "es2016", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "experimentalDecorators": true, 10 | "pretty": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "noImplicitReturns": true, 13 | "forceConsistentCasingInFileNames": true 14 | }, 15 | "files": [ 16 | "index.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /samples/postgres/grafana/__inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "prometheus", 6 | "description": "Default data source", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /samples/postgres/grafana/datasources.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: prometheus 5 | type: prometheus 6 | access: proxy 7 | orgId: 1 8 | url: http://prometheus:9090 9 | isDefault: true 10 | version: 1 11 | editable: false -------------------------------------------------------------------------------- /samples/postgres/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 5s 3 | 4 | scrape_configs: 5 | - job_name: 'prometheus' 6 | static_configs: 7 | - targets: ['localhost:9090'] 8 | - job_name: 'bookings' 9 | static_configs: 10 | - targets: 11 | - 'host.docker.internal:5000' -------------------------------------------------------------------------------- /src/Benchmarks/Benchmarks/Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Benchmarks/Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BenchmarkDotNet.Running; 3 | 4 | BenchmarkRunner.Run(Assembly.GetExecutingAssembly()); 5 | -------------------------------------------------------------------------------- /src/Core/Eventuous.Core.slnf: -------------------------------------------------------------------------------- 1 | { 2 | "solution": { 3 | "path": "..\\..\\Eventuous.sln", 4 | "projects": [ 5 | "src\\Core\\src\\Eventuous\\Eventuous.csproj", 6 | "src\\Core\\src\\Eventuous.Subscriptions\\Eventuous.Subscriptions.csproj", 7 | "src\\Core\\src\\Eventuous.Producers\\Eventuous.Producers.csproj", 8 | "src\\Core\\test\\Eventuous.Tests\\Eventuous.Tests.csproj", 9 | "test\\Eventuous.Sut.Domain\\Eventuous.Sut.Domain.csproj", 10 | "test\\Eventuous.Sut.App\\Eventuous.Sut.App.csproj" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Application/AggregateService/CommandServiceDelegates.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | public static class CommandServiceDelegates { 7 | internal delegate ValueTask HandleUntypedCommand(TAggregate aggregate, object command, CancellationToken cancellationToken) 8 | where TAggregate : Aggregate where TState : State, new(); 9 | 10 | internal delegate ValueTask GetIdFromUntypedCommand(object command, CancellationToken cancellationToken) where TId : Id; 11 | } 12 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Application/Eventuous.Application.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Application/Exceptions/ExceptionMessages.restext: -------------------------------------------------------------------------------- 1 | AggregateIdEmpty=Aggregate id {0} cannot have an empty value 2 | MissingCommandHandler=No handler found for command {0} 3 | DuplicateTypeKey=Type {0} has already been added to the map 4 | DuplicateCommandHandler=Command handler for {0} already registered 5 | MissingCommandMap=No map found between {0} and {1} -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Application/Exceptions/Exceptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | using static ExceptionMessages; 7 | 8 | public static class Exceptions { 9 | public class CommandHandlerNotFound(Type type) : Exception(MissingCommandHandler(type)); 10 | 11 | public class CommandHandlerNotFound() : CommandHandlerNotFound(typeof(T)); 12 | 13 | public class CommandHandlerAlreadyRegistered() : Exception(DuplicateCommandHandler()); 14 | 15 | public class DuplicateTypeException() : ArgumentException(DuplicateTypeKey(), typeof(T).FullName); 16 | 17 | public class MessageMappingException() : Exception(MissingCommandMap()); 18 | } 19 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Application/ExpectedState.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | public enum ExpectedState { 7 | New, 8 | Existing, 9 | Any, 10 | Unknown 11 | } 12 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Application/ICommandService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | // ReSharper disable UnusedTypeParameter 5 | 6 | namespace Eventuous; 7 | 8 | public interface ICommandService where TState : State, new() { 9 | Task> Handle(TCommand command, CancellationToken cancellationToken) where TCommand : class; 10 | } 11 | 12 | public interface ICommandService : ICommandService 13 | where T : Aggregate 14 | where TState : State, new() 15 | where TId : Id; -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Application/Persistence/ProposedAppend.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Eventuous.Persistence; 7 | 8 | [StructLayout(LayoutKind.Auto)] 9 | public record struct ProposedEvent(object Data, Metadata Metadata); 10 | 11 | [StructLayout(LayoutKind.Auto)] 12 | public record struct ProposedAppend(StreamName StreamName, ExpectedStreamVersion ExpectedVersion, ProposedEvent[] Events); 13 | 14 | public delegate ProposedAppend AmendAppend(ProposedAppend originalEvent, T context); 15 | 16 | delegate ProposedAppend AmendAppend(ProposedAppend originalEvent, object context); 17 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Application/Shared/IDefineAppendAmendment.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Eventuous.Persistence; 5 | 6 | namespace Eventuous.Shared; 7 | 8 | public interface IDefineAppendAmendment where TCommand : class { 9 | /// 10 | /// Amends the proposed append before it gets stored. 11 | /// 12 | /// A function to amend the proposed append 13 | /// 14 | void AmendAppend(AmendAppend amendAppend); 15 | } 16 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/ActivityStatus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Diagnostics; 5 | 6 | public record ActivityStatus(ActivityStatusCode StatusCode, string? Description, Exception? Exception) { 7 | public static ActivityStatus Ok(string? description = null) 8 | => new(ActivityStatusCode.Ok, description, null); 9 | 10 | public static ActivityStatus Error(Exception? exception = null, string? description = null) 11 | => new(ActivityStatusCode.Error, description ?? exception?.Message, exception); 12 | } 13 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/DiagnosticName.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Diagnostics; 5 | 6 | static class DiagnosticName { 7 | public const string BaseName = "eventuous"; 8 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/DummyActivityListener.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Diagnostics; 5 | 6 | public static class DummyActivityListener { 7 | public static ActivityListener Create() 8 | => new() { 9 | ShouldListenTo = x => x.Name.StartsWith(EventuousDiagnostics.InstrumentationName), 10 | Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/Eventuous.Diagnostics.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/Eventuous.Diagnostics.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/Metrics/GenericObserver.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Diagnostics.Metrics; 5 | 6 | class GenericObserver(Action? onNext, Action? onCompleted = null) : IObserver { 7 | public void OnCompleted() => _onCompleted(); 8 | 9 | public void OnError(Exception error) { } 10 | 11 | public void OnNext(T value) => _onNext(value); 12 | 13 | readonly Action _onNext = onNext ?? (_ => { }); 14 | readonly Action _onCompleted = onCompleted ?? (() => { }); 15 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/Metrics/Measure.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Diagnostics.Metrics; 5 | 6 | public sealed class Measure(DiagnosticSource diagnosticSource, object context) : IDisposable { 7 | public static Measure Start(DiagnosticSource source, object context) => new(source, context); 8 | 9 | public const string EventName = "Stopped"; 10 | 11 | public void SetError() => _error = true; 12 | 13 | void Record() { 14 | var stoppedAt = DateTime.UtcNow; 15 | var duration = stoppedAt - _startedAt; 16 | diagnosticSource.Write(EventName, new MeasureContext(duration, _error, context)); 17 | } 18 | 19 | readonly DateTime _startedAt = DateTime.UtcNow; 20 | 21 | bool _error; 22 | 23 | public void Dispose() => Record(); 24 | } 25 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/Metrics/MetricsListener.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Diagnostics.Metrics; 5 | 6 | namespace Eventuous.Diagnostics.Metrics; 7 | 8 | public sealed class MetricsListener(string name, Histogram duration, Counter errors, Func getTags) : GenericListener(name), IDisposable { 9 | protected override void OnEvent(KeyValuePair data) { 10 | if (data.Key != Measure.EventName || data.Value is not MeasureContext { Context: T context } ctx) return; 11 | 12 | var tags = getTags(context); 13 | 14 | duration.Record(ctx.Duration.TotalMilliseconds, tags); 15 | if (ctx.Error) errors.Add(1, tags); 16 | } 17 | } 18 | 19 | record MeasureContext(TimeSpan Duration, bool Error, object Context); 20 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/Tags/IWithCustomTags.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Diagnostics; 5 | 6 | public interface IWithCustomTags { 7 | void SetCustomTags(TagList customTags); 8 | } 9 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/Tags/MetaTags.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Diagnostics; 5 | 6 | public static class DiagnosticTags { 7 | public const string TraceId = "$traceId"; 8 | public const string SpanId = "$spanId"; 9 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Diagnostics/Tags/TracingMeta.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Diagnostics; 5 | 6 | public record TracingMeta(string? TraceId, string? SpanId) { 7 | bool IsValid() => TraceId != null && SpanId != null; 8 | 9 | public ActivityContext? ToActivityContext(bool isRemote) { 10 | try { 11 | return IsValid() ? 12 | new ActivityContext( 13 | ActivityTraceId.CreateFromString(TraceId), 14 | ActivitySpanId.CreateFromString(SpanId), 15 | ActivityTraceFlags.Recorded, 16 | isRemote: isRemote 17 | ) : default; 18 | } 19 | catch (Exception) { 20 | return default; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Domain/Eventuous.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Eventuous 4 | 5 | 6 | 7 | Eventuous.ExceptionMessages.resources 8 | 9 | 10 | 11 | 12 | Tools\Ensure.cs 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Domain/Eventuous.Domain.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Domain/Exceptions/DomainException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | public class DomainException(string message) : Exception(message); -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Domain/Exceptions/ExceptionMessages.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Reflection; 5 | using System.Resources; 6 | 7 | namespace Eventuous; 8 | 9 | static class ExceptionMessages { 10 | static readonly ResourceManager Resources = new("Eventuous.ExceptionMessages", Assembly.GetExecutingAssembly()); 11 | 12 | internal static string AggregateIdEmpty(Type idType) 13 | => string.Format(Resources.GetString("AggregateIdEmpty")!, idType.Name); 14 | 15 | internal static string DuplicateTypeKey() 16 | => string.Format(Resources.GetString("DuplicateTypeKey")!, typeof(T).Name); 17 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Domain/Exceptions/ExceptionMessages.restext: -------------------------------------------------------------------------------- 1 | AggregateIdEmpty=Aggregate id {0} cannot have an empty value 2 | DuplicateTypeKey=Type {0} has already been added to the map 3 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Domain/Exceptions/Exceptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | static class Exceptions { 7 | public class InvalidIdException(Type idType) : Exception(ExceptionMessages.AggregateIdEmpty(idType)) { 8 | public InvalidIdException(Id id) 9 | : this(id.GetType()) { } 10 | } 11 | 12 | public class InvalidIdException() : InvalidIdException(typeof(T)) where T : Id; 13 | 14 | internal class DuplicateTypeException() : ArgumentException(ExceptionMessages.DuplicateTypeKey(), typeof(T).FullName); 15 | } 16 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Domain/Id.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | [Obsolete("Use Id instead")] 7 | public abstract record AggregateId : Id { 8 | protected AggregateId(string value) : base(value) { } 9 | } 10 | 11 | [PublicAPI] 12 | public abstract record Id { 13 | protected Id(string value) { 14 | if (string.IsNullOrWhiteSpace(value)) { 15 | throw new Exceptions.InvalidIdException(this); 16 | } 17 | 18 | Value = value; 19 | } 20 | 21 | public string Value { get; } 22 | 23 | public sealed override string ToString() => Value; 24 | 25 | public static implicit operator string(Id? id) => id?.ToString() ?? throw new Exceptions.InvalidIdException(typeof(Id)); 26 | 27 | public void Deconstruct(out string value) => value = Value; 28 | } 29 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Persistence/AmendEvent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | /// 7 | /// Function to add additional information to the event before it's stored. 8 | /// 9 | public delegate NewStreamEvent AmendEvent(NewStreamEvent originalEvent); 10 | 11 | public delegate NewStreamEvent AmendEvent(NewStreamEvent originalEvent, T context); -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Persistence/AppendEventsResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | public record AppendEventsResult(ulong GlobalPosition, long NextExpectedVersion) { 7 | public static readonly AppendEventsResult NoOp = new(0, -1); 8 | } 9 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Persistence/Eventuous.Persistence.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Persistence/StreamEvent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Eventuous; 7 | 8 | [StructLayout(LayoutKind.Auto)] 9 | public record struct NewStreamEvent(Guid Id, object? Payload, Metadata Metadata); 10 | 11 | [StructLayout(LayoutKind.Auto)] 12 | public record struct StreamEvent(Guid Id, object? Payload, Metadata Metadata, string ContentType, long Position, bool FromArchive = false); -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Persistence/StreamNameFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | public static class StreamNameFactory { 7 | public static StreamName For(TId id) where TAggregate : Aggregate where TState : State, new() where TId : Id 8 | => new($"{typeof(TAggregate).Name}-{Ensure.NotEmptyString(id.Value)}"); 9 | 10 | public static StreamName For(TId id) where TId : Id { 11 | var idTypeName = typeof(TId).Name; 12 | 13 | var idSpan = idTypeName.AsSpan(); 14 | 15 | if (idSpan.EndsWith("Id")) { 16 | idSpan = idSpan[..^2]; 17 | } 18 | 19 | return idSpan.Length > 0 ? new($"{idSpan}-{id.Value}") : new($"{idTypeName}-{id.Value}"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Producers/AckProduce.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Producers; 5 | 6 | public delegate ValueTask AcknowledgeProduce(ProducedMessage message); 7 | 8 | public delegate ValueTask ReportFailedProduce(ProducedMessage message, string error, Exception? exception); -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Producers/Eventuous.Producers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Diagnostics\DiagnosticName.cs 12 | 13 | 14 | Tools\TaskExtensions.cs 15 | 16 | 17 | Tools\Ensure.cs 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Serialization/Eventuous.Serialization.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Eventuous 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Serialization/IEventSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | public interface IEventSerializer { 7 | DeserializationResult DeserializeEvent(ReadOnlySpan data, string eventType, string contentType); 8 | 9 | SerializationResult SerializeEvent(object evt); 10 | } 11 | 12 | public record SerializationResult(string EventType, string ContentType, byte[] Payload); 13 | 14 | public abstract record DeserializationResult { 15 | public record SuccessfullyDeserialized(object Payload) : DeserializationResult; 16 | 17 | public record FailedToDeserialize(DeserializationError Error) : DeserializationResult; 18 | } 19 | 20 | public enum DeserializationError { 21 | UnknownType, 22 | ContentTypeMismatch, 23 | PayloadEmpty 24 | } 25 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Serialization/IMetadataSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | public interface IMetadataSerializer { 7 | byte[] Serialize(Metadata evt); 8 | 9 | /// 10 | /// Deserializes the metadata 11 | /// 12 | /// Serialized metadata as bytes 13 | /// Deserialized metadata object 14 | /// MetadataDeserializationException if the metadata cannot be deserialized 15 | Metadata? Deserialize(ReadOnlySpan bytes); 16 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Serialization/MetadataDeserializationException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | public class MetadataDeserializationException(Exception inner) : Exception("Failed to deserialize metadata", inner); 7 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Shared/Eventuous.Shared.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Shared/Exceptions/ExceptionMessages.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Reflection; 5 | using System.Resources; 6 | 7 | namespace Eventuous; 8 | 9 | static class ExceptionMessages { 10 | static readonly ResourceManager Resources = new("Eventuous.ExceptionMessages", Assembly.GetExecutingAssembly()); 11 | 12 | internal static string DuplicateTypeKey() => string.Format(Resources.GetString("DuplicateTypeKey")!, typeof(T).Name); 13 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Shared/Exceptions/ExceptionMessages.restext: -------------------------------------------------------------------------------- 1 | DuplicateTypeKey=Type {0} has already been added to the map 2 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Shared/Exceptions/Exceptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | static class Exceptions { 7 | internal class DuplicateTypeException() : ArgumentException(ExceptionMessages.DuplicateTypeKey(), typeof(T).FullName); 8 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Shared/Meta/MetaTags.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous; 5 | 6 | public static class MetaTags { 7 | const string Prefix = "eventuous"; 8 | 9 | public const string MessageId = $"{Prefix}.message-id"; 10 | public const string CorrelationId = $"{Prefix}.correlation-id"; 11 | public const string CausationId = $"{Prefix}.causation-id"; 12 | } 13 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Shared/Tools/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Extensions.AspNetCore; 5 | 6 | static class TypeExtensions { 7 | public static T? GetAttribute(this Type type) where T : class => Attribute.GetCustomAttribute(type, typeof(T)) as T; 8 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Channels/ChannelFullException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Channels; 5 | 6 | public class ChannelFullException() : Exception("Channel worker unable to write to the channel because it's full"); -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Checkpoints/Checkpoint.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Eventuous.Subscriptions.Checkpoints; 7 | 8 | [PublicAPI] 9 | [StructLayout(LayoutKind.Auto)] 10 | public record struct Checkpoint(string Id, ulong? Position) { 11 | public static Checkpoint Empty(string id) => new(id, null); 12 | 13 | public readonly bool IsEmpty => Position == null; 14 | } 15 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Checkpoints/ICheckpointStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Checkpoints; 5 | 6 | [PublicAPI] 7 | public interface ICheckpointStore { 8 | ValueTask GetLastCheckpoint(string checkpointId, CancellationToken cancellationToken); 9 | 10 | ValueTask StoreCheckpoint(Checkpoint checkpoint, bool force, CancellationToken cancellationToken); 11 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Consumers/IMessageConsumer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Consumers; 5 | 6 | using Context; 7 | 8 | public interface IMessageConsumer where TContext : class, IMessageConsumeContext { 9 | ValueTask Consume(TContext context); 10 | } 11 | 12 | public interface IMessageConsumer : IMessageConsumer; -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Context/ContextExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Context; 5 | 6 | public static class ContextExtensions { 7 | public static T? GetContext(this IMessageConsumeContext ctx) where T : class, IMessageConsumeContext { 8 | while (true) { 9 | if (typeof(T) == ctx.GetType()) return (T)ctx; 10 | 11 | if (ctx is WrappedConsumeContext wrapped) { 12 | ctx = wrapped.InnerContext; 13 | continue; 14 | } 15 | 16 | return null; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Context/ContextItemKeys.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Context; 5 | 6 | public static class ContextItemKeys { 7 | public const string Activity = "activity"; 8 | } 9 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Diagnostics/HealthReport.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Diagnostics; 5 | 6 | readonly record struct HealthReport { 7 | HealthReport(bool isHealthy, Exception? lastException) { 8 | IsHealthy = isHealthy; 9 | LastException = lastException; 10 | } 11 | 12 | public static HealthReport Healthy() => new(true, null); 13 | 14 | public static HealthReport Unhealthy(Exception? exception) => new(false, exception); 15 | 16 | public bool IsHealthy { get; } 17 | public Exception? LastException { get; } 18 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Diagnostics/SubscriptionGapMeasure.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Eventuous.Subscriptions.Diagnostics; 7 | 8 | public delegate ValueTask GetSubscriptionEndOfStream(CancellationToken cancellationToken); 9 | 10 | [PublicAPI] 11 | [StructLayout(LayoutKind.Auto)] 12 | public readonly record struct EndOfStream(string SubscriptionId, ulong Position, DateTime Timestamp) { 13 | public static readonly EndOfStream Invalid = new("error", 0, DateTime.MinValue); 14 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/DropReason.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Subscriptions; 2 | 3 | public enum DropReason { 4 | Stopped, 5 | ServerError, 6 | SubscriptionError 7 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Eventuous.Subscriptions.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Filters/ConsumePipeExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Filters; 5 | 6 | using Consumers; 7 | 8 | public static class ConsumePipeExtensions { 9 | public static ConsumePipe AddDefaultConsumer(this ConsumePipe consumePipe, params IEventHandler[] handlers) 10 | => consumePipe.AddFilterLast(new ConsumerFilter(new DefaultConsumer(handlers))); 11 | } 12 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Filters/ConsumerFilter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Filters; 5 | 6 | using Consumers; 7 | using Context; 8 | 9 | public class ConsumerFilter(IMessageConsumer consumer) : ConsumeFilter { 10 | protected override ValueTask Send(IMessageConsumeContext context, LinkedListNode? next) => consumer.Consume(context); 11 | } 12 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Filters/MessageFilter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Filters; 5 | 6 | using Context; 7 | 8 | public delegate bool FilterMessage(IMessageConsumeContext receivedEvent); 9 | 10 | public class MessageFilter(FilterMessage filter) : ConsumeFilter { 11 | readonly FilterMessage _filter = Ensure.NotNull(filter); 12 | 13 | protected override ValueTask Send(IMessageConsumeContext context, LinkedListNode? next) { 14 | if (next?.Value == null) return default; 15 | 16 | if (_filter(context)) return next.Value.Send(context, next.Next); 17 | 18 | context.Ignore(); 19 | return default; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Filters/Partitioning/Partitioner.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions.Filters.Partitioning; 5 | 6 | using Context; 7 | 8 | public static class Partitioner { 9 | /// 10 | /// Partition key hash calculator function 11 | /// 12 | public delegate uint GetPartitionHash(string partitionKey); 13 | 14 | /// 15 | /// Function to get a partition key from a message context 16 | /// 17 | public delegate string GetPartitionKey(IMessageConsumeContext context); 18 | } 19 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Handlers/BaseEventHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions; 5 | 6 | using Context; 7 | 8 | public abstract class BaseEventHandler : IEventHandler { 9 | protected BaseEventHandler() => DiagnosticName = GetType().Name; 10 | 11 | public string DiagnosticName { get; } 12 | 13 | public abstract ValueTask HandleEvent(IMessageConsumeContext context); 14 | } 15 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Handlers/EventHandlingStatus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions; 5 | 6 | [Flags] 7 | public enum EventHandlingStatus : short { 8 | Ignored = 0b_1000, 9 | Success = 0b_0001, 10 | Pending = 0b_0010, 11 | Failure = 0b_0011, 12 | Handled = 0b_0111 13 | // 0111 bitmask for Handled means that if any of the three lower bits is set, the message 14 | // hs been handled. 15 | } -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Handlers/IEventHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions; 5 | 6 | using Context; 7 | 8 | /// 9 | /// Subscription event handlers must implement this interface 10 | /// 11 | public interface IEventHandler { 12 | /// 13 | /// Event handler name that is used for diagnostics (logging, tracing, etc) 14 | /// 15 | string DiagnosticName { get; } 16 | 17 | /// 18 | /// Function that handles an event received from the subscription 19 | /// 20 | /// Message context with message payload and details 21 | /// 22 | ValueTask HandleEvent(IMessageConsumeContext context); 23 | } 24 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/IMessageSubscription.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions; 5 | 6 | using Diagnostics; 7 | 8 | public delegate void OnSubscribed(string subscriptionId); 9 | 10 | public delegate void OnDropped(string subscriptionId, DropReason dropReason, Exception? exception); 11 | 12 | public delegate void OnUnsubscribed(string subscriptionId); 13 | 14 | public interface IMessageSubscription { 15 | string SubscriptionId { get; } 16 | 17 | ValueTask Subscribe(OnSubscribed onSubscribed, OnDropped onDropped, CancellationToken cancellationToken); 18 | 19 | ValueTask Unsubscribe(OnUnsubscribed onUnsubscribed, CancellationToken cancellationToken); 20 | } 21 | 22 | public interface IMeasuredSubscription { 23 | GetSubscriptionEndOfStream GetMeasure(); 24 | } 25 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Logging/InternalLogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Microsoft.Extensions.Logging; 5 | 6 | // ReSharper disable TemplateIsNotCompileTimeConstantProblem 7 | 8 | namespace Eventuous.Subscriptions.Logging; 9 | 10 | public class InternalLogger(ILogger logger, LogLevel logLevel, string subscriptionId) { 11 | #pragma warning disable CA2254 12 | public void Log(string message, params object[] args) => logger.Log(logLevel, GetMessage(message), args); 13 | 14 | public void Log(Exception? exception, string message, params object[] args) => logger.Log(logLevel, exception, GetMessage(message), args); 15 | #pragma warning restore CA2254 16 | 17 | string GetMessage(string message) => $"[{subscriptionId}] {message}"; 18 | } 19 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/Properties/InternalVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Eventuous.Tests.Subscriptions")] 4 | [assembly: InternalsVisibleTo("Eventuous.TestHelpers")] 5 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous.Subscriptions/SubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Subscriptions; 5 | 6 | [PublicAPI] 7 | public abstract record SubscriptionOptions { 8 | /// 9 | /// Subscription id is used to match event handlers with one subscription 10 | /// 11 | public string SubscriptionId { get; set; } = null!; 12 | 13 | /// 14 | /// Set to true if you want the subscription to fail and stop if anything goes wrong. 15 | /// 16 | public bool ThrowOnError { get; set; } 17 | } 18 | 19 | public abstract record SubscriptionWithCheckpointOptions : SubscriptionOptions { 20 | public int CheckpointCommitBatchSize { get; set; } = 100; 21 | public int CheckpointCommitDelayMs { get; set; } = 5000; 22 | } 23 | -------------------------------------------------------------------------------- /src/Core/src/Eventuous/Eventuous.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | README.md 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests.Application/Helpers.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Sut.App; 2 | using NodaTime; 3 | 4 | namespace Eventuous.Tests.Application; 5 | 6 | public static class Helpers { 7 | public static Commands.BookRoom GetBookRoom(string bookingId = "123") { 8 | var checkIn = LocalDate.FromDateTime(DateTime.Today); 9 | var checkOut = checkIn.PlusDays(1); 10 | 11 | return new(bookingId, "234", checkIn, checkOut, 100); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/DomainFixture.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using Eventuous.Sut.App; 3 | 4 | namespace Eventuous.Tests.Persistence.Base.Fixtures; 5 | 6 | public static class DomainFixture { 7 | static DomainFixture() => TypeMap.RegisterKnownEventTypes(typeof(DomainFixture).Assembly); 8 | 9 | static Faker Faker => new Faker() 10 | .RuleFor(x => x.BookingId, _ => Guid.NewGuid().ToString("N")) 11 | .RuleFor(x => x.RoomId, _ => Guid.NewGuid().ToString("N")) 12 | .RuleFor(x => x.Price, f => f.Random.Number(50, 200)) 13 | .RuleFor(x => x.CheckIn, f => f.Noda().LocalDate.Soon()) 14 | .RuleFor(x => x.CheckOut, (f, c) => c.CheckIn.PlusDays(f.Random.Number(1, 5))); 15 | 16 | public static Commands.ImportBooking CreateImportBooking() => Faker.Generate(); 17 | } 18 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests.Subscriptions.Base/Eventuous.Tests.Subscriptions.Base.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/TracedHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Eventuous.Subscriptions; 3 | using Eventuous.Subscriptions.Context; 4 | using Eventuous.TestHelpers; 5 | 6 | namespace Eventuous.Tests.Subscriptions.Base; 7 | 8 | public class TracedHandler : BaseEventHandler { 9 | public List Contexts { get; } = []; 10 | 11 | static readonly ValueTask Success = new(EventHandlingStatus.Success); 12 | 13 | public override ValueTask HandleEvent(IMessageConsumeContext context) { 14 | Contexts.Add(new(Activity.Current?.TraceId, Activity.Current?.SpanId, Activity.Current?.ParentSpanId)); 15 | 16 | return Success; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscriptionTestBase.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.Persistence.Base.Fixtures; 2 | 3 | namespace Eventuous.Tests.Subscriptions.Base; 4 | 5 | public abstract class SubscriptionTestBase(IStartableFixture fixture) { 6 | [Before(Test)] 7 | public async Task Startup() { 8 | await fixture.InitializeAsync(); 9 | } 10 | 11 | [After(Test)] 12 | public async Task Shutdown() { 13 | await fixture.DisposeAsync(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests.Subscriptions/Eventuous.Tests.Subscriptions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests/AggregateWithId/OperateOnAggregateWithId.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Testing; 2 | 3 | namespace Eventuous.Tests.AggregateWithId; 4 | 5 | public class OperateOnAggregateWithId : AggregateWithIdSpec { 6 | protected override void When(TestAggregate aggregate) => aggregate.Process(); 7 | 8 | const string IdValue = "test"; 9 | 10 | protected override TestId? Id { get; } = new(IdValue); 11 | 12 | [Test] 13 | public void should_emit_event() => Emitted(new TestEvent()); 14 | 15 | [Test] 16 | public void should_set_id() => Then().State.Id.Value.Should().Be(IdValue); 17 | } 18 | 19 | public class TestAggregate : Aggregate { 20 | public void Process() => Apply(new TestEvent()); 21 | } 22 | 23 | public record TestState : State; 24 | 25 | public record TestId(string Value) : Id(Value); 26 | 27 | record TestEvent; 28 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests/Fixtures/IdGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Tests.Fixtures; 2 | 3 | public class IdGenerator { 4 | public static string GetId() => Guid.NewGuid().ToString("N"); 5 | } 6 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests/Fixtures/NaiveFixture.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | 3 | namespace Eventuous.Tests.Fixtures; 4 | 5 | using Sut.App; 6 | using Testing; 7 | 8 | public class NaiveFixture { 9 | protected IEventStore EventStore { get; } = new InMemoryEventStore(); 10 | 11 | static readonly Faker Faker = new Faker() 12 | .CustomInstantiator( 13 | f => { 14 | var checkin = f.Noda().LocalDate.Soon(); 15 | var checkout = checkin.PlusDays(f.Random.Number(1, 5)); 16 | 17 | return new(f.Random.Guid().ToString("N"), f.Random.Guid().ToString("N"), checkin, checkout, f.Random.Number(50, 200)); 18 | } 19 | ); 20 | 21 | protected static Commands.BookRoom CreateBookRoomCommand() => Faker.Generate(); 22 | } 23 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests/StreamNameMapTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Sut.Domain; 2 | 3 | namespace Eventuous.Tests; 4 | 5 | using static Fixtures.IdGenerator; 6 | 7 | public class StreamNameMapTests { 8 | readonly StreamNameMap _sut = new(); 9 | 10 | [Test] 11 | public async Task Should_get_stream_name_for_id() { 12 | var idString = GetId(); 13 | var id = new BookingId(idString); 14 | var streamName = StreamNameFactory.For(id); 15 | await Assert.That(streamName.ToString()).IsEqualTo($"Booking-{idString}"); 16 | } 17 | 18 | [Test] 19 | public async Task Should_get_default_stream_name_for_id() { 20 | var idString = GetId(); 21 | var id = new BookingId(idString); 22 | var streamName = _sut.GetStreamName(id); 23 | await Assert.That(streamName.ToString()).IsEqualTo($"Booking-{idString}"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Core/test/Eventuous.Tests/TypeRegistrationTests.cs: -------------------------------------------------------------------------------- 1 | using static Eventuous.Sut.Domain.BookingEvents; 2 | 3 | namespace Eventuous.Tests; 4 | 5 | public class TypeRegistrationTests { 6 | readonly TypeMapper _typeMapper = new(); 7 | 8 | public TypeRegistrationTests() => _typeMapper.RegisterKnownEventTypes(typeof(BookingCancelled).Assembly); 9 | 10 | [Test] 11 | public async Task ShouldResolveDecoratedEvent() { 12 | await Assert.That(_typeMapper.GetTypeName()).IsEqualTo(TypeNames.BookingCancelled); 13 | await Assert.That(_typeMapper.GetType(TypeNames.BookingCancelled)).IsEqualTo(typeof(BookingCancelled)); 14 | } 15 | } -------------------------------------------------------------------------------- /src/Diagnostics/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | zipkin: 5 | image: openzipkin/zipkin 6 | container_name: eventuous-zipkin 7 | ports: 8 | - "9411:9411" 9 | -------------------------------------------------------------------------------- /src/Diagnostics/src/Eventuous.Diagnostics.Logging/Eventuous.Diagnostics.Logging.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DiagnosticName.cs 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Diagnostics/src/Eventuous.Diagnostics.Logging/README.md: -------------------------------------------------------------------------------- 1 | # Eventuous.Diagnostics.Logging 2 | 3 | Eventuous has internal event sources that emit log messages. 4 | It's possible to expose those messages into the logs by using the logging event listener from this package. 5 | 6 | ## Usage 7 | 8 | Normally, you'd not need to use the logging listener directly. 9 | The `Eventuous.Extensions.DependencyInjection` package contains extensions for `IApplicationBuilder` and `IHost` to connect Eventuous diagnostic events to the logging system of .NET. -------------------------------------------------------------------------------- /src/Diagnostics/src/Eventuous.Diagnostics.OpenTelemetry/Eventuous.Diagnostics.OpenTelemetry.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Tools\Ensure.cs 12 | 13 | 14 | DiagnosticName.cs 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Diagnostics/src/Eventuous.Diagnostics/DiagnosticName.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Diagnostics; 5 | 6 | public static class DiagnosticName { 7 | public const string BaseName = "eventuous"; 8 | } 9 | -------------------------------------------------------------------------------- /src/Diagnostics/src/Eventuous.Diagnostics/Eventuous.Diagnostics.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Diagnostics/src/Eventuous.Diagnostics/Properties/InternalVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Eventuous")] 4 | -------------------------------------------------------------------------------- /src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fakes/MessageCounter.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Tests.OpenTelemetry.Fakes; 2 | 3 | public class MessageCounter { 4 | public int Count; 5 | public void Increment() => Interlocked.Increment(ref Count); 6 | } 7 | -------------------------------------------------------------------------------- /src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fakes/MetricValue.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Tests.OpenTelemetry.Fakes; 2 | 3 | public record MetricValue(string Name, string[] Keys, object[] Values, double Value); 4 | -------------------------------------------------------------------------------- /src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fakes/TestHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Eventuous.Tests.OpenTelemetry.Fakes; 4 | 5 | class TestHandler(MessageCounter counter, ILogger log) : BaseEventHandler { 6 | public override async ValueTask HandleEvent(IMessageConsumeContext context) { 7 | await Task.Delay(10, context.CancellationToken); 8 | counter.Increment(); 9 | log.LogDebug("Processed {@Message}", context.Message); 10 | 11 | return EventHandlingStatus.Success; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Directory.Testable.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | CA1816 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Directory.Untestable.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/EventStore/Eventuous.EventStore.slnf: -------------------------------------------------------------------------------- 1 | { 2 | "solution": { 3 | "path": "..\\..\\Eventuous.sln", 4 | "projects": [ 5 | "src\\Core\\src\\Eventuous\\Eventuous.csproj", 6 | "src\\Core\\src\\Eventuous.Subscriptions\\Eventuous.Subscriptions.csproj", 7 | "src\\Core\\src\\Eventuous.Producers\\Eventuous.Producers.csproj", 8 | "src\\EventStore\\src\\Eventuous.EventStore\\Eventuous.EventStore.csproj", 9 | "src\\EventStore\\test\\Eventuous.Tests.EventStore\\Eventuous.Tests.EventStore.csproj", 10 | "test\\Eventuous.Tests.SutDomain\\Eventuous.Tests.SutDomain.csproj", 11 | "test\\Eventuous.Tests.SutApp\\Eventuous.Tests.SutApp.csproj" 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /src/EventStore/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | esdb: 5 | container_name: eventuous-esdb 6 | image: ghcr.io/eventstore/eventstore:22.10.2-alpha-arm64v8 7 | ports: 8 | - '2113:2113' 9 | - '1113:1113' 10 | environment: 11 | EVENTSTORE_INSECURE: 'true' 12 | EVENTSTORE_CLUSTER_SIZE: 1 13 | EVENTSTORE_EXT_TCP_PORT: 1113 14 | EVENTSTORE_HTTP_PORT: 2113 15 | EVENTSTORE_ENABLE_EXTERNAL_TCP: 'true' 16 | EVENTSTORE_RUN_PROJECTIONS: all 17 | EVENTSTORE_START_STANDARD_PROJECTIONS: "true" 18 | EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP: "true" 19 | -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Eventuous.EventStore.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/README.md: -------------------------------------------------------------------------------- 1 | # Eventuous EventStoreDB support 2 | 3 | This package adds support for [EventStoreDB](https://eventstore.com) to applications built with Eventuous. 4 | It includes the following components: 5 | 6 | - `EsdbEventStore` - implementation of the `IEventStore` interface 7 | - Different types of subscriptions (catch-up and persistent) 8 | - Message producer -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/ConsumePipeExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Eventuous.Subscriptions.Filters; 5 | 6 | namespace Eventuous.EventStore.Subscriptions; 7 | 8 | /// 9 | /// Extensions for 10 | /// 11 | public static class ConsumePipeExtensions { 12 | /// 13 | /// Adds a filter to ignore EventStoreDB system events 14 | /// 15 | /// 16 | /// 17 | public static ConsumePipe AddSystemEventsFilter(this ConsumePipe pipe) => pipe.AddFilterLast(new MessageFilter(x => !x.MessageType.StartsWith("$"))); 18 | } 19 | -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/Diagnostics/AllStreamSubscriptionMeasure.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.EventStore.Subscriptions.Diagnostics; 5 | 6 | class AllStreamSubscriptionMeasure(string subscriptionId, EventStoreClient eventStoreClient) 7 | : BaseSubscriptionMeasure(subscriptionId, "$all", eventStoreClient) { 8 | protected override IAsyncEnumerable Read(CancellationToken cancellationToken) 9 | => EventStoreClient.ReadAllAsync(Direction.Backwards, Position.End, 1, cancellationToken: cancellationToken); 10 | 11 | protected override ulong GetLastPosition(ResolvedEvent resolvedEvent) => resolvedEvent.Event.Position.CommitPosition; 12 | } 13 | -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/Diagnostics/StreamSubscriptionMeasure.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.EventStore.Subscriptions.Diagnostics; 5 | 6 | class StreamSubscriptionMeasure(string subscriptionId, StreamName streamName, EventStoreClient eventStoreClient) 7 | : BaseSubscriptionMeasure(subscriptionId, streamName, eventStoreClient) { 8 | protected override IAsyncEnumerable Read(CancellationToken cancellationToken) 9 | => EventStoreClient.ReadStreamAsync(Direction.Backwards, streamName, StreamPosition.End, 1, cancellationToken: cancellationToken); 10 | 11 | protected override ulong GetLastPosition(ResolvedEvent resolvedEvent) => resolvedEvent.Event.EventNumber; 12 | } 13 | -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/EsdbMappings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.EventStore.Subscriptions; 5 | 6 | static class EsdbMappings { 7 | public static DropReason AsDropReason(SubscriptionDroppedReason reason) 8 | => reason switch { 9 | SubscriptionDroppedReason.Disposed => DropReason.Stopped, 10 | SubscriptionDroppedReason.ServerError => DropReason.ServerError, 11 | SubscriptionDroppedReason.SubscriberError => DropReason.SubscriptionError, 12 | _ => throw new ArgumentOutOfRangeException(nameof(reason), reason, null) 13 | }; 14 | 15 | } -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/Options/AllPersistentSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.EventStore.Subscriptions; 5 | 6 | /// 7 | /// Options for 8 | /// 9 | public record AllPersistentSubscriptionOptions : PersistentSubscriptionOptions { 10 | /// 11 | /// Server-side event filter. 12 | /// Warning: the filter is set when the subscription is created. 13 | /// Eventuous doesn't update the filter after that even if it changes in code. 14 | /// 15 | public IEventFilter? EventFilter { get; set; } 16 | } -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/Options/AllStreamSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.EventStore.Subscriptions; 5 | 6 | /// 7 | /// Options for 8 | /// 9 | [PublicAPI] 10 | public record AllStreamSubscriptionOptions : CatchUpSubscriptionOptions { 11 | /// 12 | /// Server-side event filter 13 | /// 14 | public IEventFilter? EventFilter { get; set; } 15 | 16 | /// 17 | /// When using the server-side , the clients still wants to persist the checkpoint 18 | /// from time to time, to avoid re-reading lots of filtered out events after the restart. Default is 10. 19 | /// 20 | public uint CheckpointInterval { get; set; } = 10; 21 | } -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/Options/CatchUpSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Eventuous.Subscriptions.Filters; 5 | 6 | namespace Eventuous.EventStore.Subscriptions; 7 | 8 | /// 9 | /// Base class for catch-up subscription options 10 | /// 11 | public record CatchUpSubscriptionOptions : EventStoreSubscriptionWithCheckpointOptions { 12 | /// 13 | /// Number of parallel consumers. Defaults to 1. 14 | /// Don't set this value if you use partitioned subscriptions with . 15 | /// 16 | public int ConcurrencyLimit { get; set; } = 1; 17 | } 18 | -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/Options/EventStoreSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.EventStore.Subscriptions; 5 | 6 | /// 7 | /// Base class for EventStoreDB subscription options 8 | /// 9 | public abstract record EventStoreSubscriptionOptions : SubscriptionOptions { 10 | /// 11 | /// User credentials 12 | /// 13 | public UserCredentials? Credentials { get; [PublicAPI] set; } 14 | 15 | /// 16 | /// Resolve link events 17 | /// 18 | public bool ResolveLinkTos { get; [PublicAPI] set; } 19 | } 20 | -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/Options/EventStoreSubscriptionWithCheckpointOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.EventStore.Subscriptions; 5 | 6 | /// 7 | /// Options base record for EventStoreDB checkpoint-based subscriptions 8 | /// 9 | public abstract record EventStoreSubscriptionWithCheckpointOptions : SubscriptionWithCheckpointOptions { 10 | /// 11 | /// User credentials 12 | /// 13 | public UserCredentials? Credentials { get; [PublicAPI] set; } 14 | 15 | /// 16 | /// Resolve link events 17 | /// 18 | public bool ResolveLinkTos { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/Options/StreamPersistentSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.EventStore.Subscriptions; 5 | 6 | /// 7 | /// Options for 8 | /// 9 | [PublicAPI] 10 | public record StreamPersistentSubscriptionOptions : PersistentSubscriptionOptions { 11 | /// 12 | /// Stream name to subscribe for 13 | /// 14 | public StreamName StreamName { get; set; } 15 | } -------------------------------------------------------------------------------- /src/EventStore/src/Eventuous.EventStore/Subscriptions/Options/StreamSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.EventStore.Subscriptions; 5 | 6 | /// 7 | /// Options for 8 | /// 9 | public record StreamSubscriptionOptions : CatchUpSubscriptionOptions { 10 | /// 11 | /// WHen set to true, all events of type that starts with '$' will be ignored. Default is true. 12 | /// 13 | public bool IgnoreSystemEvents { get; set; } = true; 14 | 15 | /// 16 | /// Stream name to subscribe for 17 | /// 18 | public StreamName StreamName { get; set; } 19 | } -------------------------------------------------------------------------------- /src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/DomainFixture.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using Eventuous.Sut.App; 3 | using Eventuous.Sut.Domain; 4 | 5 | namespace Eventuous.Tests.EventStore.Fixtures; 6 | 7 | public static class DomainFixture { 8 | static DomainFixture() => TypeMap.RegisterKnownEventTypes(typeof(BookingEvents.BookingImported).Assembly); 9 | 10 | static Faker Faker => new Faker() 11 | .RuleFor(x => x.BookingId, _ => Guid.NewGuid().ToString("N")) 12 | .RuleFor(x => x.RoomId, _ => Guid.NewGuid().ToString("N")) 13 | .RuleFor(x => x.Price, f => f.Random.Number(50, 200)) 14 | .RuleFor(x => x.CheckIn, f => f.Noda().LocalDate.Soon()) 15 | .RuleFor(x => x.CheckOut, (f, c) => c.CheckIn.PlusDays(f.Random.Number(1, 5))); 16 | 17 | public static Commands.ImportBooking CreateImportBooking() => Faker.Generate(); 18 | } 19 | -------------------------------------------------------------------------------- /src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/EsdbContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using Testcontainers.EventStoreDb; 3 | 4 | namespace Eventuous.Tests.EventStore.Fixtures; 5 | 6 | public static class EsdbContainer { 7 | public static EventStoreDbContainer Create() { 8 | var image = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 9 | ? "eventstore/eventstore:24.6.0-alpha-arm64v8" 10 | : "eventstore/eventstore:24.6"; 11 | 12 | return new EventStoreDbBuilder() 13 | .WithImage(image) 14 | .WithEnvironment("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true") 15 | .Build(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/EventStore/test/Eventuous.Tests.EventStore/Limiter.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.EventStore; 2 | using TUnit.Core.Interfaces; 3 | 4 | [assembly: ParallelLimiter] 5 | 6 | namespace Eventuous.Tests.EventStore; 7 | 8 | public class Limiter : IParallelLimit { 9 | public int Limit => 4; 10 | } 11 | -------------------------------------------------------------------------------- /src/EventStore/test/Eventuous.Tests.EventStore/Metrics/MetricsTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.OpenTelemetry; 2 | 3 | namespace Eventuous.Tests.EventStore.Metrics; 4 | 5 | [ClassDataSource] 6 | [NotInParallel] 7 | public class MetricsTests(MetricsFixture fixture) : MetricsTestsBase(fixture) { 8 | [Test] 9 | [Retry(3)] 10 | public async Task ShouldMeasureSubscriptionGapCountBase_Esdb() { 11 | await ShouldMeasureSubscriptionGapCountBase(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/EventStore/test/Eventuous.Tests.EventStore/Store/StoreTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.Persistence.Base.Store; 2 | 3 | // ReSharper disable UnusedType.Global 4 | 5 | namespace Eventuous.Tests.EventStore.Store; 6 | 7 | [InheritsTests] 8 | [ClassDataSource] 9 | public class Append(StoreFixture fixture) : StoreAppendTests(fixture); 10 | 11 | [InheritsTests] 12 | [ClassDataSource] 13 | public class Read(StoreFixture fixture) : StoreReadTests(fixture); 14 | 15 | [InheritsTests] 16 | [ClassDataSource] 17 | public class OtherMethods(StoreFixture fixture) : StoreOtherOpsTests(fixture); 18 | -------------------------------------------------------------------------------- /src/EventStore/test/Eventuous.Tests.EventStore/Store/TieredStoreTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.Persistence.Base.Store; 2 | using Testcontainers.EventStoreDb; 3 | 4 | namespace Eventuous.Tests.EventStore.Store; 5 | 6 | [ClassDataSource] 7 | public class TieredStoreTests(StoreFixture storeFixture) : TieredStoreTestsBase(storeFixture) { 8 | [Test] 9 | public async Task Esdb_should_load_hot_and_archive() { 10 | await Should_load_hot_and_archive(); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Experimental/src/ElasticPlayground/Generator.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using Eventuous.Sut.App; 3 | 4 | namespace ElasticPlayground; 5 | 6 | public class Generator{ 7 | public static string RandomString() => Guid.NewGuid().ToString(); 8 | 9 | static readonly Faker Faker = new Faker() 10 | .CustomInstantiator( 11 | f => { 12 | var checkin = f.Noda().LocalDate.Soon(); 13 | var checkout = checkin.PlusDays(f.Random.Number(1, 5)); 14 | 15 | return new(f.Random.Guid().ToString("N"), f.Random.Guid().ToString("N"), checkin, checkout, f.Random.Number(50, 200)); 16 | } 17 | ); 18 | 19 | public static Commands.BookRoom CreateBookRoomCommand() => Faker.Generate(); 20 | } -------------------------------------------------------------------------------- /src/Experimental/src/ElasticPlayground/MiscExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Eventuous.Sut.App; 5 | using Eventuous.Sut.Domain; 6 | 7 | namespace ElasticPlayground; 8 | 9 | public static class MiscExtensions { 10 | public static Commands.RecordPayment ToRecordPayment(this Commands.BookRoom command, string paymentId, float divider = 1) 11 | => new( 12 | new BookingId(command.BookingId), 13 | paymentId, 14 | new Money(command.Price / divider), 15 | DateTimeOffset.Now 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/Experimental/src/ElasticPlayground/ResultExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ElasticPlayground; 2 | 3 | static class ResultExtensions { 4 | public static void Dump(this Result r) where T : State, new() { 5 | Console.WriteLine(r.Success ? "Success" : "Failure"); 6 | 7 | r.Match( 8 | ok => { 9 | foreach (var change in ok.Changes!) { 10 | Console.WriteLine($"{change.EventType} {change.Event}"); 11 | } 12 | }, 13 | error => Console.WriteLine(error.ErrorMessage) 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Experimental/src/Eventuous.Spyglass/Eventuous.Spyglass.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Experimental/src/Eventuous.Spyglass/RegistrationExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Eventuous.Spyglass; 7 | 8 | public static class RegistrationExtensions { 9 | public static IServiceCollection AddEventuousSpyglass(this IServiceCollection services) 10 | => services.AddSingleton(); 11 | } 12 | -------------------------------------------------------------------------------- /src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/ContentTypes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Extensions.AspNetCore; 5 | 6 | public static class ContentTypes { 7 | public const string ProblemDetails = "application/problem+json"; 8 | public const string Json = "application/json"; 9 | } 10 | -------------------------------------------------------------------------------- /src/Extensions/src/Eventuous.Extensions.AspNetCore/Http/ControllerAttributes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Extensions.AspNetCore; 5 | 6 | public class ProducesResult() : ProducesResponseTypeAttribute(typeof(Result.Ok), 200) where TState : State, new(); 7 | 8 | public class ProducesConflict() : ProducesResponseTypeAttribute(typeof(ProblemDetails), 409); 9 | 10 | public class ProducesNotFound() : ProducesResponseTypeAttribute(typeof(ProblemDetails), 409); 11 | 12 | public class ProducesDomainError() : ProducesResponseTypeAttribute(typeof(ValidationProblemDetails), 400); 13 | -------------------------------------------------------------------------------- /src/Extensions/src/Eventuous.Extensions.DependencyInjection/README.md: -------------------------------------------------------------------------------- 1 | # Eventuous ASP.NET Core 2 | 3 | This package adds several DI extensions for `IServiceCollection`: 4 | 5 | - `AddCommandService` to register app services 6 | - `AddAggregateStore` to register the `AggregateStore` and a given `IEventStore` (Eventuous does not need aggregate store to be registered, only use it if you use the aggregate store in your application directly) 7 | - `AddAggregate` to register aggregate types that require dependencies 8 | 9 | Keep in mind that we don't recommend having dependencies in aggregates, so you'd normally not need to use `AddAggregate`. 10 | -------------------------------------------------------------------------------- /src/Extensions/src/Eventuous.Extensions.Logging/Eventuous.Extensions.Logging.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Extensions/src/Eventuous.Subscriptions.Polly/Eventuous.Subscriptions.Polly.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Tools\TaskExtensions.cs 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Sut.AspNetCore/Program.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Sut.AspNetCore; 2 | using Eventuous.Sut.Domain; 3 | using Eventuous.TestHelpers; 4 | using Eventuous.Testing; 5 | using Microsoft.AspNetCore.Http.Json; 6 | using BookingService = Eventuous.Sut.AspNetCore.BookingService; 7 | 8 | DefaultEventSerializer.SetDefaultSerializer(new DefaultEventSerializer(TestPrimitives.DefaultOptions)); 9 | 10 | var builder = WebApplication.CreateBuilder(args); 11 | builder.Services.AddCommandService(); 12 | builder.Services.AddEventStore(); 13 | builder.Services.Configure(options => options.SerializerOptions.ConfigureForTests()); 14 | 15 | var app = builder.Build(); 16 | 17 | var config = app.Services.GetService(); 18 | config?.Invoke(app); 19 | 20 | app.Run(); 21 | 22 | public partial class Program; 23 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Sut.AspNetCore/TestConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Sut.AspNetCore; 2 | 3 | public delegate void ConfigureWebApplication(WebApplication app); 4 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Sut.AspNetCore/TestData.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Sut.AspNetCore; 2 | 3 | public static class TestData { 4 | public const string GuestId = "test guest"; 5 | } 6 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Sut.AspNetCore/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.DependencyInjection/Eventuous.Tests.DependencyInjection.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | Eventuous.Tests.AspNetCore 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.DependencyInjection/Sut/TestAggregate.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Tests.AspNetCore.Sut; 2 | 3 | public class TestAggregate(TestDependency dependency) : Aggregate { 4 | public TestDependency Dependency { get; } = dependency; 5 | } 6 | 7 | public class AnotherTestAggregate(TestDependency dependency) : Aggregate { 8 | public TestDependency Dependency { get; } = dependency; 9 | } 10 | 11 | public record TestState : State; 12 | 13 | public record TestId(string Value) : Id(Value); 14 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.DependencyInjection/Sut/TestDependency.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Tests.AspNetCore.Sut; 2 | 3 | public class TestDependency; 4 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRouteWithGenericAttr_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | state: { 3 | price: { 4 | amount: 100, 5 | currency: EUR 6 | }, 7 | amountPaid: { 8 | amount: 0, 9 | currency: EUR 10 | }, 11 | id: { 12 | value: Guid_1 13 | } 14 | }, 15 | changes: [ 16 | { 17 | event: { 18 | roomId: Guid_2, 19 | price: 100, 20 | checkIn: 2023-10-01, 21 | checkOut: 2023-10-02 22 | }, 23 | eventType: V1.BookingImported 24 | } 25 | ], 26 | globalPosition: 0 27 | } -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRoute_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | state: { 3 | price: { 4 | amount: 100, 5 | currency: EUR 6 | }, 7 | amountPaid: { 8 | amount: 0, 9 | currency: EUR 10 | }, 11 | id: { 12 | value: Guid_1 13 | } 14 | }, 15 | changes: [ 16 | { 17 | event: { 18 | roomId: Guid_2, 19 | price: 100, 20 | checkIn: 2023-10-01, 21 | checkOut: 2023-10-02 22 | }, 23 | eventType: V1.BookingImported 24 | } 25 | ], 26 | globalPosition: 0 27 | } -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | state: { 3 | price: { 4 | amount: 100, 5 | currency: EUR 6 | }, 7 | amountPaid: { 8 | amount: 0, 9 | currency: EUR 10 | }, 11 | id: { 12 | value: Guid_1 13 | } 14 | }, 15 | changes: [ 16 | { 17 | event: { 18 | roomId: Guid_2, 19 | price: 100, 20 | checkIn: 2023-10-01, 21 | checkOut: 2023-10-02 22 | }, 23 | eventType: V1.BookingImported 24 | } 25 | ], 26 | globalPosition: 0 27 | } -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapEnrichedCommand_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | state: { 3 | price: { 4 | amount: 100, 5 | currency: EUR 6 | }, 7 | amountPaid: { 8 | amount: 0, 9 | currency: EUR 10 | }, 11 | id: { 12 | value: Guid_1 13 | } 14 | }, 15 | changes: [ 16 | { 17 | event: { 18 | roomId: Guid_2, 19 | checkIn: 2023-10-01, 20 | checkOut: 2023-10-02, 21 | price: 100, 22 | guestId: test guest 23 | }, 24 | eventType: V1.RoomBooked 25 | } 26 | ], 27 | globalPosition: 0 28 | } -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/DiscoveredCommandsTests.CallDiscoveredCommandRoute_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | state: { 3 | price: { 4 | amount: 100, 5 | currency: EUR 6 | }, 7 | amountPaid: { 8 | amount: 0, 9 | currency: EUR 10 | }, 11 | id: { 12 | value: Guid_1 13 | } 14 | }, 15 | changes: [ 16 | { 17 | event: { 18 | roomId: Guid_2, 19 | checkIn: 2023-10-01, 20 | checkOut: 2023-10-02, 21 | price: 100, 22 | guestId: guest 23 | }, 24 | eventType: V1.RoomBooked 25 | } 26 | ], 27 | globalPosition: 0 28 | } -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/Enricher.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Tests.Extensions.AspNetCore.Fixture; 2 | 3 | public static class Enricher { 4 | internal static SutBookingCommands.ImportBooking EnrichCommand(TestCommands.ImportBookingHttp command, HttpContext _) 5 | => new(new(command.BookingId), command.RoomId, new(command.CheckIn, command.CheckOut), new(command.Price)); 6 | } 7 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/Module.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using VerifyTests.DiffPlex; 3 | 4 | namespace Eventuous.Tests.Extensions.AspNetCore.Fixture; 5 | 6 | public static class ModuleInitializer { 7 | [ModuleInitializer] 8 | public static void Initialize() { 9 | TypeMap.RegisterKnownEventTypes(typeof(BookingEvents.RoomBooked).Assembly); 10 | VerifyDiffPlex.Initialize(OutputType.Compact); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/TestAggregate.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Tests.Extensions.AspNetCore.Fixture; 2 | 3 | record BrookingState : State; -------------------------------------------------------------------------------- /src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/TestBaseWithLogs.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.TestHelpers.TUnit; 2 | 3 | namespace Eventuous.Tests.Extensions.AspNetCore.Fixture; 4 | 5 | public abstract class TestBaseWithLogs : IDisposable { 6 | readonly TestEventListener _listener = new(); 7 | 8 | public void Dispose() => _listener.Dispose(); 9 | } 10 | -------------------------------------------------------------------------------- /src/Gateway/src/Eventuous.Gateway/Eventuous.Gateway.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Tools\TaskExtensions.cs 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Gateway/src/Eventuous.Gateway/GatewayHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Gateway; 5 | 6 | [PublicAPI] 7 | public static class GatewayHandlerFactory { 8 | public static IEventHandler Create(IProducer producer, RouteAndTransform routeAndTransform, bool awaitProduce) where T : class 9 | => new GatewayHandler(new GatewayProducer(producer), routeAndTransform, awaitProduce); 10 | } 11 | -------------------------------------------------------------------------------- /src/Gateway/src/Eventuous.Gateway/GatewayMessage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Gateway; 5 | 6 | public record GatewayMessage(StreamName TargetStream, object Message, Metadata? Metadata, TProduceOptions ProduceOptions); 7 | -------------------------------------------------------------------------------- /src/Gateway/src/Eventuous.Gateway/IGatewayTransform.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Eventuous.Subscriptions.Context; 5 | 6 | namespace Eventuous.Gateway; 7 | 8 | /// 9 | /// Interface for routing and transformation of messages with produce options. 10 | /// 11 | public interface IGatewayTransform { 12 | ValueTask[]> RouteAndTransform(IMessageConsumeContext context); 13 | } 14 | -------------------------------------------------------------------------------- /src/Gateway/test/Eventuous.Tests.Gateway/Eventuous.Tests.Gateway.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/GooglePubSub/src/Eventuous.GooglePubSub.CloudRun/CloudRunPubSubSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Eventuous.Subscriptions; 5 | 6 | namespace Eventuous.GooglePubSub.CloudRun; 7 | 8 | /// 9 | /// Cloud Run Pub/Sub subscription options 10 | /// 11 | public record CloudRunPubSubSubscriptionOptions : SubscriptionOptions { 12 | /// 13 | /// Pub/Sub topic ID, it will only be used for informational purposes 14 | /// 15 | public string TopicId { get; set; } = "unknown"; 16 | 17 | /// 18 | /// Message attribute keys for system values like content type and event type 19 | /// 20 | public PubSubAttributes Attributes { get; set; } = new(); 21 | } 22 | -------------------------------------------------------------------------------- /src/GooglePubSub/src/Eventuous.GooglePubSub.CloudRun/EndpointMappingExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Eventuous.GooglePubSub.CloudRun; 5 | 6 | // ReSharper disable CheckNamespace 7 | 8 | namespace Microsoft.AspNetCore.Builder; 9 | 10 | public static class EndpointMappingExtensions { 11 | public static WebApplication MapCloudRunPubSubSubscription(this WebApplication app, string path = "/") { 12 | CloudRunPubSubSubscription.MapSubscription(app, path); 13 | 14 | return app; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/GooglePubSub/src/Eventuous.GooglePubSub.CloudRun/Eventuous.GooglePubSub.CloudRun.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | PubSubAttributes.cs 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/GooglePubSub/src/Eventuous.GooglePubSub/Producers/PubSubProduceOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Google.Protobuf.Collections; 5 | 6 | namespace Eventuous.GooglePubSub.Producers; 7 | 8 | /// 9 | /// Google PubSub produce options, supplied per message or batch 10 | /// 11 | [PublicAPI] 12 | public class PubSubProduceOptions { 13 | /// 14 | /// Function, which can be used to add custom message attributes 15 | /// 16 | public Func>? AddAttributes { get; init; } 17 | 18 | /// 19 | /// Optional ordering key. It only works if the publishing client is configured to support ordering. 20 | /// 21 | public string? OrderingKey { get; init; } 22 | } 23 | -------------------------------------------------------------------------------- /src/GooglePubSub/src/Eventuous.GooglePubSub/PubSubAttributes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.GooglePubSub; 5 | 6 | [PublicAPI] 7 | public class PubSubAttributes { 8 | public string EventType { get; set; } = "eventType"; 9 | public string ContentType { get; set; } = "contentType"; 10 | public string MessageId { get; set; } = "messageId"; 11 | } 12 | -------------------------------------------------------------------------------- /src/GooglePubSub/src/Eventuous.GooglePubSub/README.md: -------------------------------------------------------------------------------- 1 | # Eventuous Google PubSub support 2 | 3 | This package adds support for [Google PubSub](https://cloud.google.com/pubsub) to applications built with Eventuous. 4 | It includes the following components: 5 | 6 | - Subscription (`GooglePubSubSubscription`) 7 | - Message producer (`GooglePubSubProducer`) 8 | 9 | Both subscription and producer support ordering keys. 10 | 11 | PubSub Lite is not supported at the moment due to lack of C# SDK. -------------------------------------------------------------------------------- /src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/Eventuous.Tests.GooglePubSub.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Kafka/src/Eventuous.Kafka/KafkaHeaderKeys.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Kafka; 5 | 6 | public static class KafkaHeaderKeys { 7 | public static string MessageTypeHeader { get; set; } = "message-type"; 8 | public static string ContentTypeHeader { get; set; } = "content-type"; 9 | } 10 | -------------------------------------------------------------------------------- /src/Kafka/src/Eventuous.Kafka/Producers/KafkaProduceOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Kafka.Producers; 5 | 6 | public record KafkaProduceOptions(string PartitionKey); 7 | -------------------------------------------------------------------------------- /src/Kafka/src/Eventuous.Kafka/Producers/KafkaProducerOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Kafka.Producers; 5 | 6 | public record KafkaProducerOptions(ProducerConfig ProducerConfig); 7 | -------------------------------------------------------------------------------- /src/Kafka/src/Eventuous.Kafka/README.md: -------------------------------------------------------------------------------- 1 | # Eventuous support for Apache Kafka 2 | 3 | Eventuous support producing messages to Apache Kafka using a basic producer that doesn't support schema registry, and, therefore, serialization-agnostic. 4 | 5 | Learn more in the [documentation](https://eventuous.dev/docs/infra/kafka/). -------------------------------------------------------------------------------- /src/Kafka/src/Eventuous.Kafka/Subscriptions/KafkaBasicSubscription.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Eventuous.Subscriptions; 5 | using Eventuous.Subscriptions.Filters; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Eventuous.Kafka.Subscriptions; 9 | 10 | public class KafkaBasicSubscription(KafkaSubscriptionOptions options, ConsumePipe consumePipe, ILoggerFactory? loggerFactory, IEventSerializer? eventSerializer) 11 | : EventSubscription(options, consumePipe, loggerFactory, eventSerializer) { 12 | protected override ValueTask Subscribe(CancellationToken cancellationToken) 13 | => throw new NotImplementedException(); 14 | 15 | protected override ValueTask Unsubscribe(CancellationToken cancellationToken) 16 | => throw new NotImplementedException(); 17 | } 18 | -------------------------------------------------------------------------------- /src/Kafka/src/Eventuous.Kafka/Subscriptions/KafkaSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Eventuous.Subscriptions; 5 | 6 | namespace Eventuous.Kafka.Subscriptions; 7 | 8 | public record KafkaSubscriptionOptions : SubscriptionOptions { 9 | public ConsumerConfig ConsumerConfig { get; init; } = null!; 10 | } 11 | -------------------------------------------------------------------------------- /src/Kafka/start.sh: -------------------------------------------------------------------------------- 1 | docker compose -f ../EventStore/docker-compose.yml -f ../Mongo/docker-compose.yml -f docker-compose.yml up -------------------------------------------------------------------------------- /src/Kafka/test/Eventuous.Tests.Kafka/Eventuous.Tests.Kafka.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | osx-arm64 5 | false 6 | Exe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Kafka/test/Eventuous.Tests.Kafka/KafkaFixture.cs: -------------------------------------------------------------------------------- 1 | using Testcontainers.Kafka; 2 | using TUnit.Core.Interfaces; 3 | 4 | namespace Eventuous.Tests.Kafka; 5 | 6 | public class KafkaFixture : IAsyncInitializer, IAsyncDisposable { 7 | KafkaContainer _kafkaContainer = null!; 8 | 9 | public async Task InitializeAsync() { 10 | _kafkaContainer = new KafkaBuilder() 11 | .WithImage("confluentinc/cp-kafka:7.2.6") 12 | .Build(); 13 | await _kafkaContainer.StartAsync(); 14 | } 15 | 16 | public string BootstrapServers => _kafkaContainer.GetBootstrapAddress(); 17 | 18 | public async ValueTask DisposeAsync() => await _kafkaContainer.DisposeAsync(); 19 | } 20 | -------------------------------------------------------------------------------- /src/Mongo/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | mongo: 5 | extends: 6 | file: ../../docker-compose.yml 7 | service: mongo 8 | esdb: 9 | extends: 10 | file: ../../docker-compose.yml 11 | service: esdb 12 | -------------------------------------------------------------------------------- /src/Mongo/src/Eventuous.Projections.MongoDB/Eventuous.Projections.MongoDB.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /src/Mongo/src/Eventuous.Projections.MongoDB/Tools/Document.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Projections.MongoDB.Tools; 5 | 6 | public abstract record Document(string Id); 7 | 8 | public abstract record ProjectedDocument(string Id) : Document(Id) { 9 | public ulong StreamPosition { get; init; } 10 | public ulong Position { get; init; } 11 | } -------------------------------------------------------------------------------- /src/Postgres/src/Eventuous.Postgresql/SchemaInitializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Microsoft.Extensions.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Eventuous.Postgresql; 8 | 9 | public class SchemaInitializer(PostgresStoreOptions options, ILoggerFactory? loggerFactory = null) : IHostedService { 10 | public Task StartAsync(CancellationToken cancellationToken) { 11 | if (!options.InitializeDatabase) return Task.CompletedTask; 12 | var dataSource = new NpgsqlDataSourceBuilder(options.ConnectionString).Build(); 13 | var schema = new Schema(options.Schema); 14 | return schema.CreateSchema(dataSource, loggerFactory?.CreateLogger(), cancellationToken); 15 | } 16 | 17 | public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; 18 | } 19 | -------------------------------------------------------------------------------- /src/Postgres/src/Eventuous.Postgresql/Scripts/7_ReadStreamSub.sql: -------------------------------------------------------------------------------- 1 | create or replace function __schema__.read_stream_sub( 2 | _stream_id integer, 3 | _stream_name varchar, 4 | _from_position integer, 5 | _count integer 6 | ) 7 | returns table ( 8 | message_id uuid, 9 | message_type varchar, 10 | stream_position integer, 11 | global_position bigint, 12 | json_data jsonb, 13 | json_metadata jsonb, 14 | created timestamp, 15 | stream_name varchar 16 | ) 17 | as $$ 18 | begin 19 | return query select m.message_id, m.message_type, m.stream_position, m.global_position, 20 | m.json_data, m.json_metadata, m.created, _stream_name 21 | from __schema__.messages m 22 | where m.stream_id = _stream_id and m.stream_position >= _from_position 23 | order by m.global_position 24 | limit _count; 25 | end; 26 | 27 | $$ language 'plpgsql'; -------------------------------------------------------------------------------- /src/Postgres/test/Eventuous.Tests.Postgres/Fixtures/PostgresContainer.cs: -------------------------------------------------------------------------------- 1 | using Testcontainers.PostgreSql; 2 | 3 | namespace Eventuous.Tests.Postgres.Fixtures; 4 | 5 | public static class PostgresContainer { 6 | public static PostgreSqlContainer Create() 7 | => new PostgreSqlBuilder() 8 | .WithUsername("postgres") 9 | .WithPassword("secret") 10 | .WithDatabase("eventuous") 11 | .Build(); 12 | } 13 | -------------------------------------------------------------------------------- /src/Postgres/test/Eventuous.Tests.Postgres/Metrics/MetricsTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.OpenTelemetry; 2 | 3 | namespace Eventuous.Tests.Postgres.Metrics; 4 | 5 | [ClassDataSource] 6 | [NotInParallel] 7 | public class MetricsTests(MetricsFixture fixture) : MetricsTestsBase(fixture) { 8 | [Test] 9 | [Retry(3)] 10 | public async Task ShouldMeasureSubscriptionGapCountBase_Postgres() { 11 | await ShouldMeasureSubscriptionGapCountBase(); 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/Postgres/test/Eventuous.Tests.Postgres/Store/StoreTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.Persistence.Base.Store; 2 | 3 | // ReSharper disable UnusedType.Global 4 | 5 | namespace Eventuous.Tests.Postgres.Store; 6 | 7 | [InheritsTests] 8 | [ClassDataSource] 9 | public class Append(StoreFixture fixture) : StoreAppendTests(fixture); 10 | 11 | [InheritsTests] 12 | [ClassDataSource] 13 | public class Read(StoreFixture fixture) : StoreReadTests(fixture); 14 | 15 | [InheritsTests] 16 | [ClassDataSource] 17 | public class OtherMethods(StoreFixture fixture) : StoreOtherOpsTests(fixture); -------------------------------------------------------------------------------- /src/Postgres/test/Eventuous.Tests.Postgres/Store/TieredStoreTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.Persistence.Base.Store; 2 | using Testcontainers.PostgreSql; 3 | 4 | namespace Eventuous.Tests.Postgres.Store; 5 | 6 | public class TieredStoreTests(StoreFixture storeFixture) : TieredStoreTestsBase(storeFixture); 7 | -------------------------------------------------------------------------------- /src/RabbitMq/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | rabbitmq: 5 | container_name: eventuous-rabbitmq 6 | hostname: rabbitmq 7 | image: rabbitmq:management-alpine 8 | ports: 9 | - '4369:4369' 10 | - '5672:5672' 11 | - '25672:25672' 12 | - '15672:15672' 13 | -------------------------------------------------------------------------------- /src/RabbitMq/src/Eventuous.RabbitMq/Diagnostics/RabbitMqTelemetryTags.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.RabbitMq.Diagnostics; 5 | 6 | static class RabbitMqTelemetryTags { 7 | public const string RoutingKey = "messaging.rabbitmq.routing_key"; 8 | } -------------------------------------------------------------------------------- /src/RabbitMq/src/Eventuous.RabbitMq/Producers/ExchangeCache.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Eventuous.RabbitMq.Producers; 7 | 8 | class ExchangeCache(ILogger? log) { 9 | public void EnsureExchange(string name, Action createExchange) { 10 | if (_exchanges.Contains(name)) return; 11 | 12 | try { 13 | log?.LogInformation("Ensuring exchange {ExchangeName}", name); 14 | createExchange(); 15 | } 16 | catch (Exception e) { 17 | log?.LogError(e, "Failed to ensure exchange {ExchangeName}: {ErrorMessage}", name, e.Message); 18 | throw; 19 | } 20 | 21 | _exchanges.Add(name); 22 | } 23 | 24 | readonly HashSet _exchanges = []; 25 | } 26 | -------------------------------------------------------------------------------- /src/RabbitMq/src/Eventuous.RabbitMq/README.md: -------------------------------------------------------------------------------- 1 | # Eventuous RabbitMQ support 2 | 3 | This package adds support for RabbitMQ to applications built with Eventuous. 4 | It includes the following components: 5 | 6 | - Subscription (`RabbitMqSubscription`) 7 | - Message producer (`RabbitMqProducer`) 8 | 9 | Remember that RabbitMQ doesn't support ordered message delivery. 10 | 11 | For each "stream," you will get an exchange, where the messages will be produced to. 12 | 13 | Creating a subscription will add a queue and an exchange. The queue and subscription exchange names will be the subscription id. 14 | The subscription queue binds to the subscription exchange, the subscription exchange binds to the exchange of the "stream." -------------------------------------------------------------------------------- /src/RabbitMq/src/Eventuous.RabbitMq/Shared/RabbitMqExchangeOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.RabbitMq.Shared; 5 | 6 | public class RabbitMqExchangeOptions { 7 | public string Type { get; init; } = ExchangeType.Fanout; 8 | public bool Durable { get; init; } = true; 9 | public bool AutoDelete { get; init; } 10 | 11 | public IDictionary? Arguments { get; init; } 12 | } -------------------------------------------------------------------------------- /src/RabbitMq/src/Eventuous.RabbitMq/Subscriptions/Timestamp.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.RabbitMq.Subscriptions; 5 | 6 | static class Timestamp { 7 | static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 8 | 9 | internal static AmqpTimestamp ToAmqpTimestamp(this DateTime datetime) { 10 | var unixTime = (datetime.ToUniversalTime() - Epoch).TotalSeconds; 11 | return new AmqpTimestamp((long) unixTime); 12 | } 13 | 14 | internal static DateTime ToDateTime(this AmqpTimestamp timestamp) => Epoch.AddSeconds(timestamp.UnixTime).ToLocalTime(); 15 | } -------------------------------------------------------------------------------- /src/RabbitMq/test/Eventuous.Tests.RabbitMq/Eventuous.Tests.RabbitMq.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/RabbitMq/test/Eventuous.Tests.RabbitMq/RabbitMqFixture.cs: -------------------------------------------------------------------------------- 1 | using Testcontainers.RabbitMq; 2 | using TUnit.Core.Interfaces; 3 | 4 | namespace Eventuous.Tests.RabbitMq; 5 | 6 | public class RabbitMqFixture : IAsyncInitializer, IAsyncDisposable { 7 | RabbitMqContainer _rabbitMq = null!; 8 | 9 | public ConnectionFactory ConnectionFactory { get; private set; } = null!; 10 | 11 | public async Task InitializeAsync() { 12 | _rabbitMq = new RabbitMqBuilder().Build(); 13 | await _rabbitMq.StartAsync(); 14 | ConnectionFactory = new() { Uri = new(_rabbitMq.GetConnectionString()), DispatchConsumersAsync = true }; 15 | } 16 | 17 | public async ValueTask DisposeAsync() => await _rabbitMq.DisposeAsync(); 18 | } 19 | -------------------------------------------------------------------------------- /src/Redis/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | volumes: 4 | redis-data: 5 | redis-insight: 6 | 7 | services: 8 | redis: 9 | image: redis:7.0.12-alpine 10 | ports: 11 | - '6379:6379' 12 | volumes: 13 | - redis-data:/data 14 | 15 | # redisinsight: 16 | # image: redislabs/redisinsight:latest 17 | # ports: 18 | # - '8001:8001' 19 | # volumes: 20 | # - type: volume 21 | # source: redis-insight 22 | # target: /db 23 | 24 | -------------------------------------------------------------------------------- /src/Redis/src/Eventuous.Redis/RedisKeys.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Redis; 5 | 6 | public static class EventuousRedisKeys { 7 | public const string JsonData = "json_data"; 8 | public const string JsonMetadata = "json_metadata"; 9 | public const string Created = "created"; 10 | public const string Stream = "stream"; 11 | public const string Position = "position"; 12 | public const string MessageId = "message_id"; 13 | public const string MessageType = "message_type"; 14 | } 15 | -------------------------------------------------------------------------------- /src/Redis/src/Eventuous.Redis/Subscriptions/ReceivedEvent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.Redis.Subscriptions; 5 | 6 | public record ReceivedEvent( 7 | Guid MessageId, 8 | string MessageType, 9 | long StreamPosition, 10 | long GlobalPosition, 11 | string JsonData, 12 | string? JsonMetadata, 13 | DateTime Created, 14 | string StreamName 15 | ); 16 | -------------------------------------------------------------------------------- /src/Redis/test/Eventuous.Tests.Redis/Eventuous.Tests.Redis.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Redis/test/Eventuous.Tests.Redis/Fixtures/DomainFixture.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using Eventuous.Sut.App; 3 | using Eventuous.Sut.Domain; 4 | 5 | namespace Eventuous.Tests.Redis.Fixtures; 6 | 7 | public static class DomainFixture { 8 | static DomainFixture() => TypeMap.RegisterKnownEventTypes(typeof(BookingEvents.BookingImported).Assembly); 9 | 10 | static Faker Faker => new Faker() 11 | .RuleFor(x => x.BookingId, _ => Guid.NewGuid().ToString("N")) 12 | .RuleFor(x => x.RoomId, _ => Guid.NewGuid().ToString("N")) 13 | .RuleFor(x => x.Price, f => f.Random.Number(50, 200)) 14 | .RuleFor(x => x.CheckIn, f => f.Noda().LocalDate.Soon()) 15 | .RuleFor(x => x.CheckOut, (f, c) => c.CheckIn.PlusDays(f.Random.Number(1, 5))); 16 | 17 | public static Commands.ImportBooking CreateImportBooking() => Faker.Generate(); 18 | } 19 | -------------------------------------------------------------------------------- /src/Relational/src/Eventuous.Sql.Base/PersistedEvent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ. All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | using System.ComponentModel; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace Eventuous.Sql.Base; 8 | 9 | /// 10 | /// Represents an event as stored in a relational database. 11 | /// 12 | [EditorBrowsable(EditorBrowsableState.Never)] 13 | [StructLayout(LayoutKind.Auto)] 14 | public readonly record struct PersistedEvent( 15 | Guid MessageId, 16 | string MessageType, 17 | int StreamPosition, 18 | long GlobalPosition, 19 | string JsonData, 20 | string? JsonMetadata, 21 | DateTime Created, 22 | string? StreamName 23 | ); 24 | -------------------------------------------------------------------------------- /src/SqlServer/src/Eventuous.SqlServer/ConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.SqlServer; 5 | 6 | delegate Task GetSqlServerConnection(CancellationToken cancellationToken); 7 | 8 | public static class ConnectionFactory { 9 | public static async Task GetConnection(string connectionString, CancellationToken cancellationToken) { 10 | var connection = new SqlConnection(connectionString); 11 | await connection.OpenAsync(cancellationToken).NoContext(); 12 | return connection; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerConnectionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.SqlServer.Projections; 5 | 6 | public record SqlServerConnectionOptions(string ConnectionString, string Schema); 7 | -------------------------------------------------------------------------------- /src/SqlServer/src/Eventuous.SqlServer/Scripts/4_ReadAllForwards.sql: -------------------------------------------------------------------------------- 1 | CREATE OR ALTER PROCEDURE __schema__.read_all_forwards 2 | @from_position bigint, 3 | @count int 4 | AS 5 | BEGIN 6 | 7 | SELECT TOP (@count) 8 | MessageId, MessageType, StreamPosition, GlobalPosition, 9 | JsonData, JsonMetadata, Created, StreamName 10 | FROM __schema__.Messages 11 | INNER JOIN __schema__.Streams ON Messages.StreamId = Streams.StreamId 12 | WHERE Messages.GlobalPosition >= @from_position 13 | ORDER BY Messages.GlobalPosition 14 | 15 | END -------------------------------------------------------------------------------- /src/SqlServer/src/Eventuous.SqlServer/Scripts/5_ReadStreamBackwards.sql: -------------------------------------------------------------------------------- 1 | CREATE OR ALTER PROCEDURE __schema__.read_stream_backwards 2 | @stream_name NVARCHAR(850), 3 | @from_position INT, 4 | @count INT 5 | AS 6 | BEGIN 7 | 8 | DECLARE @current_version int, @stream_id int 9 | 10 | SELECT @current_version = Version, @stream_id = StreamId 11 | FROM __schema__.Streams 12 | WHERE StreamName = @stream_name 13 | 14 | IF @stream_id IS NULL 15 | THROW 50001, 'StreamNotFound', 1; 16 | 17 | IF @current_version < @from_position + @count 18 | RETURN 19 | 20 | SELECT TOP (@count) 21 | MessageId, MessageType, StreamPosition, GlobalPosition, 22 | JsonData, JsonMetadata, Created 23 | FROM __schema__.Messages 24 | WHERE StreamId = @stream_id AND StreamPosition <= @from_position 25 | ORDER BY Messages.StreamPosition DESC 26 | 27 | END -------------------------------------------------------------------------------- /src/SqlServer/src/Eventuous.SqlServer/Scripts/6_ReadStreamForwards.sql: -------------------------------------------------------------------------------- 1 | CREATE OR ALTER PROCEDURE __schema__.read_stream_forwards 2 | @stream_name NVARCHAR(850), 3 | @from_position INT, 4 | @count INT 5 | AS 6 | BEGIN 7 | 8 | DECLARE @current_version int, @stream_id int 9 | 10 | SELECT @current_version = Version, @stream_id = StreamId 11 | FROM __schema__.Streams 12 | WHERE StreamName = @stream_name 13 | 14 | IF @stream_id IS NULL 15 | THROW 50001, 'StreamNotFound', 1; 16 | 17 | IF @current_version < @from_position 18 | RETURN 19 | 20 | SELECT TOP (@count) 21 | MessageId, MessageType, StreamPosition, GlobalPosition, 22 | JsonData, JsonMetadata, Created 23 | FROM __schema__.Messages 24 | WHERE StreamId = @stream_id AND StreamPosition >= @from_position 25 | ORDER BY Messages.GlobalPosition 26 | 27 | END -------------------------------------------------------------------------------- /src/SqlServer/src/Eventuous.SqlServer/Scripts/7_ReadStreamSub.sql: -------------------------------------------------------------------------------- 1 | CREATE OR ALTER PROCEDURE __schema__.read_stream_sub 2 | @stream_id int, 3 | @stream_name NVARCHAR(850), 4 | @from_position bigint, 5 | @count int 6 | AS 7 | BEGIN 8 | 9 | SELECT TOP (@count) 10 | MessageId, MessageType, StreamPosition, GlobalPosition, 11 | JsonData, JsonMetadata, Created, @stream_name AS StreamName 12 | FROM __schema__.Messages 13 | WHERE StreamId = @stream_id AND Messages.StreamPosition >= @from_position 14 | ORDER BY Messages.GlobalPosition 15 | 16 | END -------------------------------------------------------------------------------- /src/SqlServer/src/Eventuous.SqlServer/Scripts/8_TruncateStream.sql: -------------------------------------------------------------------------------- 1 | CREATE OR ALTER PROCEDURE __schema__.truncate_stream 2 | @stream_name NVARCHAR(850), 3 | @expected_version INT, 4 | @position INT 5 | AS 6 | BEGIN 7 | 8 | DECLARE @current_version int, @stream_id int 9 | 10 | SELECT @current_version = Version, @stream_id = StreamId 11 | FROM __schema__.Streams 12 | WHERE StreamName = @stream_name 13 | 14 | IF @stream_id IS NULL 15 | THROW 50001, 'StreamNotFound', 1; 16 | 17 | IF @current_version < @position 18 | RETURN 19 | 20 | IF @expected_version != -2 and @expected_version != @current_version 21 | THROW 50000, 'WrongExpectedVersion %, current version %', 1; 22 | 23 | DELETE FROM __schema__.Messages 24 | WHERE StreamId = @stream_id AND StreamPosition < @position 25 | 26 | END -------------------------------------------------------------------------------- /src/SqlServer/src/Eventuous.SqlServer/Subscriptions/SqlServerStreamSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Eventuous HQ OÜ.All rights reserved 2 | // Licensed under the Apache License, Version 2.0. 3 | 4 | namespace Eventuous.SqlServer.Subscriptions; 5 | 6 | public record SqlServerStreamSubscriptionOptions : SqlServerSubscriptionBaseOptions { 7 | /// 8 | /// Stream name to subscribe for 9 | /// 10 | public StreamName Stream { get; set; } 11 | } -------------------------------------------------------------------------------- /src/SqlServer/test/Eventuous.Tests.SqlServer/Fixtures/SqlContainer.cs: -------------------------------------------------------------------------------- 1 | using Testcontainers.MsSql; 2 | 3 | namespace Eventuous.Tests.SqlServer.Fixtures; 4 | 5 | public static class SqlContainer { 6 | public static MsSqlContainer Create() => new MsSqlBuilder().WithImage("mcr.microsoft.com/mssql/server:2022-latest").Build(); 7 | } 8 | -------------------------------------------------------------------------------- /src/SqlServer/test/Eventuous.Tests.SqlServer/Limiter.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.SqlServer; 2 | using TUnit.Core.Interfaces; 3 | 4 | [assembly: ParallelLimiter] 5 | 6 | namespace Eventuous.Tests.SqlServer; 7 | 8 | public class Limiter : IParallelLimit { 9 | public int Limit => 2; 10 | } 11 | -------------------------------------------------------------------------------- /src/SqlServer/test/Eventuous.Tests.SqlServer/Metrics/MetricsTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.OpenTelemetry; 2 | 3 | namespace Eventuous.Tests.SqlServer.Metrics; 4 | 5 | [ClassDataSource] 6 | [NotInParallel] 7 | public class MetricsTests(MetricsFixture fixture) : MetricsTestsBase(fixture) { 8 | [Test] 9 | [Retry(3)] 10 | public async Task ShouldMeasureSubscriptionGapCountBase_SqlServer() { 11 | await ShouldMeasureSubscriptionGapCountBase(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SqlServer/test/Eventuous.Tests.SqlServer/Store/StoreFixture.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.SqlServer; 2 | using Eventuous.Tests.Persistence.Base.Fixtures; 3 | using Eventuous.Tests.SqlServer.Fixtures; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Testcontainers.MsSql; 6 | 7 | namespace Eventuous.Tests.SqlServer.Store; 8 | 9 | public sealed class StoreFixture() : StoreFixtureBase(LogLevel.Information) { 10 | readonly string _schemaName = GetSchemaName(); 11 | 12 | protected override void SetupServices(IServiceCollection services) { 13 | services.AddEventuousSqlServer(Container.GetConnectionString(), _schemaName, true); 14 | services.AddEventStore(); 15 | } 16 | 17 | protected override MsSqlContainer CreateContainer() => SqlContainer.Create(); 18 | } 19 | -------------------------------------------------------------------------------- /src/SqlServer/test/Eventuous.Tests.SqlServer/Store/StoreTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.Persistence.Base.Store; 2 | 3 | // ReSharper disable UnusedType.Global 4 | 5 | namespace Eventuous.Tests.SqlServer.Store; 6 | 7 | [InheritsTests] 8 | [ClassDataSource] 9 | public class Append(StoreFixture fixture) : StoreAppendTests(fixture); 10 | 11 | [InheritsTests] 12 | [ClassDataSource] 13 | public class Read(StoreFixture fixture) : StoreReadTests(fixture); 14 | 15 | [InheritsTests] 16 | [ClassDataSource] 17 | public class OtherMethods(StoreFixture fixture) : StoreOtherOpsTests(fixture); 18 | -------------------------------------------------------------------------------- /src/SqlServer/test/Eventuous.Tests.SqlServer/Store/TieredStoreTests.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Tests.Persistence.Base.Store; 2 | using Testcontainers.MsSql; 3 | 4 | namespace Eventuous.Tests.SqlServer.Store; 5 | 6 | [ClassDataSource(Shared = SharedType.PerClass)] 7 | public class TieredStoreTests(StoreFixture storeFixture) : TieredStoreTestsBase(storeFixture) { 8 | [Test] 9 | public async Task Should_load_hot_and_archive_test() { 10 | await Should_load_hot_and_archive(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Testing/src/Eventuous.Testing/Eventuous.Testing.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Tools\Ensure.cs 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/Eventuous.Sut.App/Eventuous.Sut.App.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/Eventuous.Sut.Domain/Eventuous.Sut.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/Eventuous.Sut.Domain/Money.cs: -------------------------------------------------------------------------------- 1 | namespace Eventuous.Sut.Domain; 2 | 3 | public record Money(float Amount, string Currency = "EUR") { 4 | public static Money operator +(Money left, Money right) { 5 | if (left.Currency != right.Currency) throw new InvalidOperationException("Currencies must match"); 6 | return left with { Amount = left.Amount + right.Amount }; 7 | } 8 | 9 | public static Money operator -(Money left, Money right) { 10 | if (left.Currency != right.Currency) throw new InvalidOperationException("Currencies must match"); 11 | return left with { Amount = left.Amount - right.Amount }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/Eventuous.Sut.Domain/StayPeriod.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | 3 | namespace Eventuous.Sut.Domain; 4 | 5 | public record StayPeriod { 6 | public StayPeriod(LocalDate checkIn, LocalDate checkOut) { 7 | if (checkIn >= checkOut) 8 | throw new ArgumentOutOfRangeException(nameof(checkOut), "Check out should be after check in"); 9 | 10 | CheckIn = checkIn; 11 | CheckOut = checkOut; 12 | } 13 | 14 | public LocalDate CheckIn { get; } 15 | public LocalDate CheckOut { get; } 16 | } -------------------------------------------------------------------------------- /test/Eventuous.TestHelpers.TUnit/Eventuous.TestHelpers.TUnit.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | enable 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/Eventuous.TestHelpers/Eventuous.TestHelpers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | enable 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/Eventuous.TestHelpers/RecordedTrace.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Eventuous.TestHelpers; 4 | 5 | public record RecordedTrace(ActivityTraceId? TraceId, ActivitySpanId? SpanId, ActivitySpanId? ParentSpanId) { 6 | public const string DefaultTraceId = "00000000000000000000000000000000"; 7 | public const string DefaultSpanId = "0000000000000000"; 8 | 9 | public bool IsDefaultTraceId => TraceId == null || TraceId.ToString() == DefaultTraceId; 10 | 11 | public bool IsDefaultSpanId => SpanId == null || SpanId.ToString() == DefaultSpanId; 12 | } 13 | -------------------------------------------------------------------------------- /test/Eventuous.TestHelpers/TestPrimitives.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using NodaTime; 3 | using NodaTime.Serialization.SystemTextJson; 4 | 5 | namespace Eventuous.TestHelpers; 6 | 7 | public static class TestPrimitives { 8 | public static readonly JsonSerializerOptions DefaultOptions = 9 | new JsonSerializerOptions(JsonSerializerDefaults.Web).ConfigureForTests(); 10 | 11 | public static JsonSerializerOptions ConfigureForTests(this JsonSerializerOptions options) 12 | => options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); 13 | } 14 | -------------------------------------------------------------------------------- /test/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", 3 | "diagnosticMessages": true 4 | } --------------------------------------------------------------------------------