├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md ├── scripts │ ├── deploy-package.sh │ └── deploy-packages.sh └── workflows │ ├── build-and-tests.yaml │ ├── deployment-of-packages.yaml │ ├── documentation-deploy.yaml │ └── documentation-test-deploy.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── TransactionalBox.sln ├── architecture-decision-records ├── 2024-03.1-batch-processing-of-messages.md ├── 2024-03.2-mark-as-processed-instead-delete-messages.md ├── 2024-03.3-message-compression.md ├── 2024-04.1-adjusting-optimal-transport-message-size.md ├── 2024-05.1-hook-processisng-instead-of-interval-processing.md ├── 2024-05.2-main-namespace.md ├── 2024-06.1-change-structure-of-project.md ├── README.md └── assets │ └── 2024-06.1-structure-of-project.png ├── assets ├── diagrams │ ├── inbox.png │ └── outbox.png ├── logo.png ├── rounded-social-logo.png ├── samples │ └── web-api-sample.png └── small-logo.png ├── documentation ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── assets │ │ ├── AddToOutbox.png │ │ ├── AddToTransport.png │ │ ├── distributed-sample.png │ │ ├── inbox.png │ │ ├── outbox.png │ │ ├── rounded-social-logo.png │ │ └── simple-sample.png │ ├── getting-started.md │ ├── inbox.md │ ├── introduction.md │ ├── outbox.md │ ├── samples │ │ ├── _category_.json │ │ ├── distributed-sample.md │ │ └── simple-sample.md │ ├── storage │ │ ├── _category_.json │ │ ├── entity-framework-core.md │ │ └── in-memory.md │ └── transport │ │ ├── _category_.json │ │ ├── in-memory.md │ │ └── kafka.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ ├── index.module.css │ │ └── markdown-page.md └── static │ ├── .nojekyll │ └── img │ ├── logo.png │ ├── medium-logo.png │ ├── social-logo.png │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ └── undraw_docusaurus_tree.svg ├── samples ├── Bank │ ├── .dockerignore │ ├── Bank.dcproj │ ├── Internals │ │ └── BankLogger │ │ │ ├── BankLogger.csproj │ │ │ ├── LoggerExtension.cs │ │ │ └── Properties │ │ │ └── launchSettings.json │ ├── TransactionalBox.BankAccounts │ │ ├── Database │ │ │ ├── BankAccountEntityTypeConfiguration.cs │ │ │ └── BankAccountsDbContext.cs │ │ ├── Dockerfile │ │ ├── Messages │ │ │ ├── CreatedCustomerEventMessage.cs │ │ │ ├── CreatedCustomerEventMessageDefinition.cs │ │ │ └── CreatedCustomerEventMessageHandler.cs │ │ ├── Models │ │ │ └── BankAccount.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── TransactionalBox.BankAccounts.csproj │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── TransactionalBox.CustomerRegistrations │ │ ├── Database │ │ │ ├── CustomerRegistrationDbContext.cs │ │ │ └── CustomerRegistrationEntityTypeConfiguration.cs │ │ ├── Dockerfile │ │ ├── Messages │ │ │ ├── CreateCustomerCommandMessage.cs │ │ │ └── CreateCustomerCommandMessageDefinition.cs │ │ ├── Models │ │ │ └── CustomerRegistration.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Requests │ │ │ ├── ApproveCustomerRegistrationRequest.cs │ │ │ └── CreateCustomerRegistrationRequest.cs │ │ ├── TransactionalBox.CustomerRegistrations.csproj │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── TransactionalBox.Customers │ │ ├── Database │ │ │ ├── CustomerEntityTypeConfiguration.cs │ │ │ └── CustomersDbContext.cs │ │ ├── Dockerfile │ │ ├── Messages │ │ │ ├── CreateCustomerCommandMessage.cs │ │ │ ├── CreateCustomerCommandMessageHandler.cs │ │ │ └── CreatedCustomerEventMessage.cs │ │ ├── Models │ │ │ └── Customer.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── TransactionalBox.Customers.csproj │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── TransactionalBox.Loans │ │ ├── Database │ │ │ ├── LoanEntityTypeConfiguration.cs │ │ │ └── LoansDbContext.cs │ │ ├── Dockerfile │ │ ├── Messages │ │ │ ├── CreatedCustomerEventMessage.cs │ │ │ ├── CreatedCustomerEventMessageDefinition.cs │ │ │ └── CreatedCustomerEventMessageHandler.cs │ │ ├── Models │ │ │ └── Loan.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── TransactionalBox.Loans.csproj │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── docker-compose.override.yml │ ├── docker-compose.yml │ └── launchSettings.json ├── Directory.Packages.props ├── TransactionalBox.Sample.WebApi.InMemory │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── ServiceWithInbox │ │ ├── ExampleMessage.cs │ │ ├── ExampleMessageHandler.cs │ │ ├── PublishableMessage.cs │ │ ├── PublishableMessageDefinition.cs │ │ └── PublishableMessageHandler.cs │ ├── ServiceWithOutbox │ │ ├── ExampleMessage.cs │ │ ├── ExampleMessageDefinition.cs │ │ ├── ExampleServiceWithOutbox.cs │ │ └── PublishableMessage.cs │ ├── TransactionalBox.Sample.WebApi.InMemory.csproj │ ├── appsettings.Development.json │ └── appsettings.json └── TransactionalBox.Sample.WebApi │ ├── InboxMessages │ ├── ExampleMessage.cs │ ├── ExampleMessageHandler.cs │ ├── PublishableMessage.cs │ ├── PublishableMessageDefinition.cs │ └── PublishableMessageHandler.cs │ ├── OutboxMessages │ ├── ExampleMessage.cs │ └── ExampleMessageDefinition.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── SampleDbContext.cs │ ├── TransactionalBox.Sample.WebApi.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── source ├── Directory.Build.props ├── Directory.Packages.props ├── TransactionalBox.EntityFrameworkCore │ ├── Extensions │ │ ├── Inbox │ │ │ ├── InboxExtensionAddInbox.cs │ │ │ └── InboxExtensionUseEntityFrameworkCore.cs │ │ └── Outbox │ │ │ ├── OutboxExtensionAddOutbox.cs │ │ │ └── OutboxExtensionUseEntityFrameworkCore.cs │ ├── Internals │ │ ├── Inbox │ │ │ ├── EntityTypeConfigurations │ │ │ │ ├── IdempotentInboxKeyEntityTypeConfiguration.cs │ │ │ │ └── InboxMessageEntityTypeConfiguration.cs │ │ │ └── ImplementedContracts │ │ │ │ ├── EntityFrameworkCoreAddMessagesToInboxRepository.cs │ │ │ │ ├── EntityFrameworkCoreCleanUpIdempotencyKeysRepository.cs │ │ │ │ ├── EntityFrameworkCoreCleanUpInboxRepository.cs │ │ │ │ └── EntityFrameworkCoreProcessMessageRepository.cs │ │ ├── InternalPackages │ │ │ └── DistributedLock │ │ │ │ ├── EntityFrameworkDistributedLockStorage.cs │ │ │ │ ├── ExtensionAddEntityFrameworkCoreDistributedLock.cs │ │ │ │ ├── ExtensionUseEntityFrameworkCore.cs │ │ │ │ └── LockEntityTypeConfiguration.cs │ │ └── Outbox │ │ │ ├── EntityTypeConfigurations │ │ │ └── OutboxMessageEntityTypeConfiguration.cs │ │ │ └── ImplementedContracts │ │ │ ├── EntityFrameworkAddMessagesToTransportRepository.cs │ │ │ ├── EntityFrameworkCleanUpOutboxRepository.cs │ │ │ ├── EntityFrameworkOutboxStorage.cs │ │ │ └── EntityFrameworkStorageProvider.cs │ └── TransactionalBox.EntityFrameworkCore.csproj ├── TransactionalBox.Kafka │ ├── Exntesions │ │ ├── Inbox │ │ │ └── InboxExtensionUseKafka.cs │ │ └── Outbox │ │ │ └── OutboxExtensionUseKafka.cs │ ├── Internals │ │ ├── Inbox │ │ │ ├── IInboxKafkaSettings.cs │ │ │ ├── ImplementedContracts │ │ │ │ ├── KafkaInboxTransport.cs │ │ │ │ └── KafkaTransportTopicsCreator.cs │ │ │ └── KafkaConsumerConfigFactory.cs │ │ └── Outbox │ │ │ ├── IOutboxKafkaSettings.cs │ │ │ ├── ImplementedContracts │ │ │ └── KafkaOutboxTransport.cs │ │ │ └── KafkaConfigFactory.cs │ ├── Settings │ │ ├── Inbox │ │ │ └── InboxKafkaSettings.cs │ │ └── Outbox │ │ │ ├── KafkaTransportMessageSizeSettings.cs │ │ │ └── OutboxKafkaSettings.cs │ └── TransactionalBox.Kafka.csproj └── TransactionalBox │ ├── Builders │ └── ITransactionalBoxBuilder.cs │ ├── Configurators │ ├── IAssemblyConfigurator.cs │ ├── Inbox │ │ ├── IInboxDeserializationConfigurator.cs │ │ ├── IInboxStorageConfigurator.cs │ │ └── IInboxTransportConfigurator.cs │ └── Outbox │ │ ├── IOutboxCompressionConfigurator.cs │ │ ├── IOutboxSerializationConfigurator.cs │ │ ├── IOutboxStorageConfigurator.cs │ │ └── IOutboxTransportConfigurator.cs │ ├── Envelope.cs │ ├── Extensions │ ├── ExtensionAddTransactionalBox.cs │ ├── Inbox │ │ ├── InboxExtensionAddInbox.cs │ │ └── InboxExtensionUseSystemTextJson.cs │ └── Outbox │ │ ├── OutboxExtensionAddOutbox.cs │ │ ├── OutboxExtensionUseBrotli.cs │ │ ├── OutboxExtensionUseGZip.cs │ │ ├── OutboxExtensionUseNoCompression.cs │ │ └── OutboxExtensionUseSystemTextJson.cs │ ├── IExecutionContext.cs │ ├── IInboxHandler.cs │ ├── IOutbox.cs │ ├── InboxDefinition.cs │ ├── InboxMessage.cs │ ├── Internals │ ├── IServiceContext.cs │ ├── ISystemClock.cs │ ├── ITopicFactory.cs │ ├── Inbox │ │ ├── Assemblies │ │ │ ├── CompiledHandlers │ │ │ │ ├── CompiledInboxHandlers.cs │ │ │ │ └── ICompiledInboxHandlers.cs │ │ │ └── MessageTypes │ │ │ │ ├── IInboxMessageTypes.cs │ │ │ │ └── InboxMessageTypes.cs │ │ ├── BackgroundProcesses │ │ │ ├── AddMessagesToInbox │ │ │ │ ├── AddMessagesToInbox.cs │ │ │ │ ├── IAddMessagesToInboxSettings.cs │ │ │ │ └── Logger │ │ │ │ │ ├── AddMessagesToInboxLogger.cs │ │ │ │ │ └── IAddMessagesToInboxLogger.cs │ │ │ ├── Base │ │ │ │ ├── BackgroundProcessBase.cs │ │ │ │ └── Logger │ │ │ │ │ └── IBackgroundProcessBaseLogger.cs │ │ │ └── CleanUpIdempotencyKeys │ │ │ │ ├── CleanUpIdempotencyKeys.cs │ │ │ │ ├── ICleanUpIdempotencyKeysSettings.cs │ │ │ │ └── Logger │ │ │ │ ├── CleanUpIdempotencyKeysLogger.cs │ │ │ │ └── ICleanUpIdempotencyKeysLogger.cs │ │ ├── Configurators │ │ │ ├── InboxDeserializationConfigurator.cs │ │ │ ├── InboxStorageConfigurator.cs │ │ │ └── InboxTransportConfigurator.cs │ │ ├── Contexts │ │ │ └── ExecutionContext.cs │ │ ├── Decompression │ │ │ ├── BrotliDecompression.cs │ │ │ ├── DecompressionFactory.cs │ │ │ ├── GZipDecompression.cs │ │ │ ├── IDecompression.cs │ │ │ ├── IDecompressionFactory.cs │ │ │ └── NoDecompression.cs │ │ ├── Deserialization │ │ │ ├── IInboxDeserializer.cs │ │ │ ├── InboxDeserializer.cs │ │ │ └── Metadata.cs │ │ ├── Extensions │ │ │ ├── InboxExtensionUseInMemoryStorage.cs │ │ │ └── InboxExtensionUseInMemoryTransport.cs │ │ ├── Hooks │ │ │ ├── Events │ │ │ │ ├── AddedMessagesToInbox.cs │ │ │ │ └── ProcessedMessageFromInbox.cs │ │ │ └── Handlers │ │ │ │ ├── CleanUpInbox │ │ │ │ ├── CleanUpInbox.cs │ │ │ │ ├── ICleanUpInboxSettings.cs │ │ │ │ └── Logger │ │ │ │ │ ├── CleanUpInboxLogger.cs │ │ │ │ │ └── ICleanUpInboxLogger.cs │ │ │ │ └── ProcessMessage │ │ │ │ ├── Logger │ │ │ │ ├── IProcessMessageLogger.cs │ │ │ │ └── ProcessMessageLogger.cs │ │ │ │ └── ProcessMessage.cs │ │ ├── InboxDefinitions │ │ │ ├── DefaultInboxDefinition.cs │ │ │ └── IInboxDefinition.cs │ │ ├── InboxStartup.cs │ │ ├── Storage │ │ │ ├── AddRangeToInboxStorageResult.cs │ │ │ ├── ContractsToImplement │ │ │ │ ├── IAddMessagesToInboxRepository.cs │ │ │ │ ├── ICleanUpIdempotencyKeysRepository.cs │ │ │ │ ├── ICleanUpInboxRepository.cs │ │ │ │ └── IProcessMessageRepository.cs │ │ │ ├── DuplicatedInboxKey.cs │ │ │ ├── IdempotentInboxKey.cs │ │ │ ├── InMemory │ │ │ │ ├── IInboxStorageReadOnly.cs │ │ │ │ └── InMemoryInboxStorage.cs │ │ │ ├── InboxDistributedLock.cs │ │ │ └── InboxMessageStorage.cs │ │ └── Transport │ │ │ ├── ContractsToImplement │ │ │ ├── IInboxTransport.cs │ │ │ └── ITransportTopicsCreator.cs │ │ │ ├── InMemory │ │ │ ├── InMemoryInboxWorkerTransport.cs │ │ │ └── InMemoryTransportTopicsCreator.cs │ │ │ ├── Topics │ │ │ ├── ITopicsProvider.cs │ │ │ └── TopicsProvider.cs │ │ │ └── TransportMessage.cs │ ├── InternalPackages │ │ ├── AssemblyConfigurator │ │ │ └── AssemblyConfigurator.cs │ │ ├── DistributedLock │ │ │ ├── DistributedLockInstance.cs │ │ │ ├── DistributedLockStorageConfigurator.cs │ │ │ ├── ExtensionAddDistributedLock.cs │ │ │ ├── IDistributedLock.cs │ │ │ ├── IDistributedLockInstance.cs │ │ │ ├── IDistributedLockStorage.cs │ │ │ ├── IDistributedLockStorageConfigurator.cs │ │ │ ├── InternalDistributedLock.cs │ │ │ └── Lock.cs │ │ ├── EventHooks │ │ │ ├── EventHook.cs │ │ │ ├── EventHookHub.cs │ │ │ ├── EventHookLauncher.cs │ │ │ ├── EventHookPublisher.cs │ │ │ ├── EventHooksStartup.cs │ │ │ ├── ExtensionAddEventHookHandler.cs │ │ │ ├── HookExecutionContext.cs │ │ │ ├── HookListnerLogger.cs │ │ │ ├── IEventHookHandler.cs │ │ │ ├── IEventHookPublisher.cs │ │ │ ├── IHookExecutionContext.cs │ │ │ ├── IHookListnerLogger.cs │ │ │ └── IInternalHookListenersLauncher.cs │ │ ├── KeyedInMemoryLock │ │ │ ├── ExtensionAddKeyedInMemoryLock.cs │ │ │ ├── IKeyedInMemoryLock.cs │ │ │ ├── ILockInstance.cs │ │ │ ├── InternalKeyedInMemoryLock.cs │ │ │ └── LockInstance.cs │ │ ├── SequentialGuid │ │ │ ├── ISequentialGuidGenerator.cs │ │ │ ├── SequentialGuid.cs │ │ │ └── SequentialGuidType.cs │ │ └── Transport │ │ │ ├── ExtensionUseInternalInMemoryTransport.cs │ │ │ ├── IInMemoryTransport.cs │ │ │ ├── InternalTransport.cs │ │ │ └── TransportObject.cs │ ├── Outbox │ │ ├── Compression │ │ │ ├── Brotli │ │ │ │ ├── BrotliCompression.cs │ │ │ │ └── IBrotliCompressionSettings.cs │ │ │ ├── GZip │ │ │ │ ├── GZipCompression.cs │ │ │ │ └── IGZipCompressionSettings.cs │ │ │ ├── ICompression.cs │ │ │ └── NoCompression │ │ │ │ └── NoCompression.cs │ │ ├── Configurators │ │ │ ├── OutboxCompressionConfigurator.cs │ │ │ ├── OutboxSerializationConfigurator.cs │ │ │ ├── OutboxStorageConfigurator.cs │ │ │ └── OutboxTransportConfigurator.cs │ │ ├── Extensions │ │ │ ├── OutboxExtensionUseInMemoryStorage.cs │ │ │ └── OutboxExtensionUseInMemoryTransport.cs │ │ ├── Hooks │ │ │ ├── Events │ │ │ │ ├── AddedMessagesToOutbox.cs │ │ │ │ └── AddedMessagesToTransport.cs │ │ │ └── Handlers │ │ │ │ ├── AddMessagesToTransport │ │ │ │ ├── AddMessagesToTransport.cs │ │ │ │ ├── IAddMessagesToTransportSettings.cs │ │ │ │ ├── Logger │ │ │ │ │ ├── AddMessagesToTransportLogger.cs │ │ │ │ │ └── IAddMessagesToTransportLogger.cs │ │ │ │ └── TransportMessageFactories │ │ │ │ │ ├── GroupedOutboxMessagesWithTheSameTopic.cs │ │ │ │ │ ├── Policies │ │ │ │ │ ├── IPayloadCreationPolicy.cs │ │ │ │ │ ├── PayloadHasOptimalSizePolicy.cs │ │ │ │ │ └── PayloadIsLargerThanOptimalSizePolicy.cs │ │ │ │ │ ├── TransportEnvelope.cs │ │ │ │ │ ├── TransportEnvelopeFactory.cs │ │ │ │ │ └── TransportMessage.cs │ │ │ │ └── CleanUpOutbox │ │ │ │ ├── CleanUpOutbox.cs │ │ │ │ ├── ICleanUpOutboxSettings.cs │ │ │ │ └── Logger │ │ │ │ ├── CleanUpOutboxLogger.cs │ │ │ │ └── ICleanUpOutboxLogger.cs │ │ ├── Oubox │ │ │ ├── Metadata.cs │ │ │ ├── Outbox.cs │ │ │ └── OutboxMessagePayload.cs │ │ ├── OutboxDefinitions │ │ │ ├── DefaultOutboxMessageDefinition.cs │ │ │ └── IOutboxDefinition.cs │ │ ├── OutboxStartup.cs │ │ ├── Serialization │ │ │ ├── IOutboxMessagePayload.cs │ │ │ ├── IOutboxSerializer.cs │ │ │ └── OutboxSerializer.cs │ │ ├── Storage │ │ │ ├── ContractsToImplement │ │ │ │ ├── IAddMessagesToTransportRepository.cs │ │ │ │ ├── ICleanUpOutboxRepository.cs │ │ │ │ ├── IOutboxStorage.cs │ │ │ │ └── IStorageProvider.cs │ │ │ ├── InMemory │ │ │ │ ├── IOutboxStorageReadOnly.cs │ │ │ │ ├── InMemoryOutboxStorage.cs │ │ │ │ └── InMemoryStorageProvider.cs │ │ │ ├── OutboxDistributedLock.cs │ │ │ ├── OutboxMessageStorage.cs │ │ │ └── SequentialGuidConfigurator.cs │ │ └── Transport │ │ │ ├── ContractsToImplement │ │ │ ├── IOutboxTransport.cs │ │ │ └── ITransportMessageSizeSettings.cs │ │ │ ├── FailedAddMessagesToTransportException.cs │ │ │ └── InMemory │ │ │ ├── InMemoryOutboxTransport.cs │ │ │ └── InMemoryTransportMessageSizeSettings.cs │ ├── ServiceContext.cs │ ├── SystemClock.cs │ ├── TopicFactory.cs │ └── TransactionalBoxBuilder.cs │ ├── OutboxDefinition.cs │ ├── OutboxMessage.cs │ ├── Settings │ ├── Inbox │ │ ├── AddMessagesToInboxSettings.cs │ │ ├── CleanUpIdempotencyKeysSettings.cs │ │ ├── CleanUpInboxSettings.cs │ │ └── InboxSettings.cs │ ├── Outbox │ │ ├── AddMessagesToTransportSettings.cs │ │ ├── CleanUpOutboxSettings.cs │ │ ├── Compression │ │ │ ├── BrotliCompressionSettings.cs │ │ │ └── GZipCompressionSettings.cs │ │ └── OutboxSettings.cs │ └── TransactionalBoxSettings.cs │ └── TransactionalBox.csproj └── tests ├── Directory.Packages.props ├── TransactionalBox.End2EndTests ├── End2EndTests.cs ├── SeedWork │ ├── Inbox │ │ ├── InboxVerifier.cs │ │ ├── SendableMessage.cs │ │ └── SendableMessageInboxHandler.cs │ └── Outbox │ │ ├── SendableMessage.cs │ │ └── SendableMessageDefinition.cs ├── TestCases │ ├── Dependencies.cs │ ├── End2EndTestCase.cs │ └── Storage │ │ └── EntityFrameworkCore │ │ ├── DbContexts │ │ ├── InboxDbContext.cs │ │ └── OutboxDbContext.cs │ │ ├── EntityFrameworkCorePostgresSql.cs │ │ └── EntityFrameworkCoreSqlServer.cs ├── Tests.cs └── TransactionalBox.End2EndTests.csproj ├── TransactionalBox.EntityFrameworkCore.Tests ├── Internals │ └── InternalPackages │ │ └── DistributedLock │ │ ├── DistributedLockTests.cs │ │ └── SeedWork │ │ ├── DisabledKeyedInMemoryLock.cs │ │ ├── DisabledLockInstance.cs │ │ ├── TestDbContext.cs │ │ └── TestLock.cs └── TransactionalBox.EntityFrameworkCore.Tests.csproj └── TransactionalBox.UnitTests ├── Internals └── InternalPackages │ └── KeyedInMemoryLock │ └── KeyedInMemoryLockTest.cs ├── TopicFactoryTests.cs └── TransactionalBox.UnitTests.csproj /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 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 26 | !**/.gitignore 27 | !.git/HEAD 28 | !.git/config 29 | !.git/packed-refs 30 | !.git/refs/heads/** -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Versions:** 27 | - Pcakges: 28 | [PackageName] - [Version] 29 | 30 | - Dotnet: [Version] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.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 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | _Provide an overview..._ 3 | -------------------------------------------------------------------------------- /.github/scripts/deploy-package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd source/$1 3 | 4 | dotnet pack -c release /p:PackageVersion=$GITHUB_REF_NAME --no-restore -o . 5 | 6 | dotnet nuget push *.nupkg -k $NUGET_API_KEY -s https://api.nuget.org/v3/index.json -------------------------------------------------------------------------------- /.github/scripts/deploy-packages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | chmod +x ./.github/scripts/deploy-package.sh 3 | 4 | echo Started deploy. 5 | 6 | for dir in source/*/ 7 | do 8 | dir=${dir%*/} 9 | 10 | if [[ ${dir##*/} == 'Internals' ]] 11 | then 12 | continue 13 | fi 14 | 15 | echo Deploying package: ${dir##*/} 16 | 17 | exec ./.github/scripts/deploy-package.sh ${dir##*/} & 18 | wait 19 | done 20 | 21 | echo Finished deploy. -------------------------------------------------------------------------------- /.github/workflows/build-and-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Build & Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/build-and-tests.yaml' 9 | - 'samples/**' 10 | - 'source/**' 11 | - 'tests/**' 12 | pull_request: 13 | branches: 14 | - main 15 | paths: 16 | - '.github/workflows/build-and-tests.yaml' 17 | - 'samples/**' 18 | - 'source/**' 19 | - 'tests/**' 20 | 21 | jobs: 22 | 23 | build: 24 | 25 | name: Build & Tests 26 | 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | 31 | - uses: actions/checkout@v4 32 | 33 | - name: Setup dotnet 34 | uses: actions/setup-dotnet@v4 35 | with: 36 | dotnet-version: '8.x.x' 37 | 38 | - name: Setup Docker 39 | uses: docker/setup-buildx-action@v3.0.0 40 | 41 | - name: Cache 42 | uses: actions/cache@v3 43 | with: 44 | path: ~/.nuget/packages 45 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} 46 | restore-keys: | 47 | ${{ runner.os }}-nuget 48 | 49 | - name: Install dependencies 50 | run: dotnet restore 51 | 52 | - name: Build 53 | run: dotnet build --no-restore --configuration Release 54 | 55 | - name: Test 56 | run: dotnet test --no-restore --configuration Release 57 | -------------------------------------------------------------------------------- /.github/workflows/deployment-of-packages.yaml: -------------------------------------------------------------------------------- 1 | name: Deployment of packages 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup .NET SDK 17 | uses: actions/setup-dotnet@v4 18 | 19 | - name: Build 20 | run: dotnet build -c Release 21 | 22 | - name: Publish NuGet packacges 23 | env: 24 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 25 | run: chmod +x ./.github/scripts/deploy-packages.sh && ./.github/scripts/deploy-packages.sh 26 | shell: bash 27 | -------------------------------------------------------------------------------- /.github/workflows/documentation-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/documentation*.yaml' 9 | - 'documentation/**' 10 | 11 | jobs: 12 | deploy: 13 | 14 | name: Deploy documentation 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | defaults: 21 | run: 22 | working-directory: ./documentation 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | 29 | - uses: actions/setup-node@v4 30 | with: 31 | node-version: 18 32 | cache: npm 33 | cache-dependency-path: ./documentation/package-lock.json 34 | 35 | - name: Install dependencies 36 | run: npm ci 37 | 38 | - name: Build website 39 | run: npm run build 40 | 41 | - name: Deploy to GitHub Pages 42 | uses: peaceiris/actions-gh-pages@v3 43 | with: 44 | github_token: ${{ secrets.GITHUB_TOKEN }} 45 | publish_dir: ./documentation/build 46 | cname: transactionalbox.com -------------------------------------------------------------------------------- /.github/workflows/documentation-test-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Test documentation deployment 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/documentation*.yaml' 9 | - 'documentation/**' 10 | 11 | jobs: 12 | deploy: 13 | 14 | name: Test documentation deployment 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | defaults: 21 | run: 22 | working-directory: ./documentation 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | 29 | - uses: actions/setup-node@v4 30 | with: 31 | node-version: 18 32 | cache: npm 33 | cache-dependency-path: ./documentation/package-lock.json 34 | 35 | - name: Install dependencies 36 | run: npm ci 37 | 38 | - name: Test build website 39 | run: npm run build -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to TransactionalBox 2 | 3 | 1. Fork this repository. 4 | 2. Create feature branch in your fork repository. 5 | 3. Add changes to your feature branch. 6 | 4. Create pull request from your fork to main repository. 7 | 5. When pull reqeust approved will be merged to main branch. 8 | 9 | Thanks! 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Adrian Mikołajczyk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # :closed_lock_with_key: Security 2 | 3 | :radioactive: TransactionalBox is currently under development. 4 | -------------------------------------------------------------------------------- /architecture-decision-records/2024-03.1-batch-processing-of-messages.md: -------------------------------------------------------------------------------- 1 | 2 | # Title 3 | Batch processing of messages 4 | 5 | ## Description 6 | Getting of messages from storage should be processed in packages. 7 | 8 | Then these messages should be converted to transport messages (one transport message = collection of the same messages). 9 | 10 | Batch processing reduce number of call over the network to storage (database) and transport. 11 | 12 | The number of transactions performed will be lower, which is also important for performance. 13 | 14 | Batch processing is more efficient than one by one processing with an increasing number of messages. 15 | 16 | In contrast, this solution increases the complexity of the system. Deduplication of grouped messages is more difficult than one-by-one processing. 17 | Furthermore, if we have large batches then when an error occurs with a write to the storage on the outbox side, the number of duplicate messages added to the transport increases. 18 | 19 | The user must be able to set the batch size to adjust the solution to the needs of the project. -------------------------------------------------------------------------------- /architecture-decision-records/2024-03.2-mark-as-processed-instead-delete-messages.md: -------------------------------------------------------------------------------- 1 | # Title 2 | Mark as processed instead delete messages. 3 | 4 | ## Description 5 | Messages can be deleted after processing from Outbox or Inbox. 6 | 7 | However, this method does not allow the processed messages to be archived. 8 | In addition, mostly deletion operations are slower than updating, which can increase the processing time of messages. 9 | 10 | It is better to mark messages as processed than to delete them immediately, because you can delegate this process and delete messages in larger batches. -------------------------------------------------------------------------------- /architecture-decision-records/2024-03.3-message-compression.md: -------------------------------------------------------------------------------- 1 | # Title 2 | Message compression 3 | 4 | ## Description 5 | Transport messages is a collection of messages of the same type. 6 | The structure of serialized messages is very similar. 7 | These messages are ideal for compression, they have a lot of common parts. Compression costs some time, so it can increase latency with a small number of messages. 8 | 9 | In the case of a large number of messages, transport bandwidth can be significantly increased and processing time reduced than processing one by one (when there are many messages to process). 10 | 11 | The user must be able to choose the compression algorithm. 12 | 13 | By default, messages should not be compressed. -------------------------------------------------------------------------------- /architecture-decision-records/2024-04.1-adjusting-optimal-transport-message-size.md: -------------------------------------------------------------------------------- 1 | # Title 2 | Adjusting optimal transport message size. 3 | 4 | ## Description 5 | Transport message size can have impact on delivery time in transport. 6 | 7 | If the transport message is larger than the optimal size for the transport, such a message should be split. 8 | 9 | It may happen that the message will be larger than the optimal transport message. In this case, the message should be added to the transport. 10 | 11 | Very large files could theoretically be cut into small messages and assemble on the inbox side. Such a solution increases the complexity of the system and won't be supported. User can attach references to the message and then consumer download a large file. -------------------------------------------------------------------------------- /architecture-decision-records/2024-05.2-main-namespace.md: -------------------------------------------------------------------------------- 1 | # Title 2 | Main namespace 3 | 4 | ## Description 5 | All public classes and interfaces used by the user should be in the `TransactionalBox` namespace. The exception is classes or interfaces that are included as parameters in extension methods. 6 | 7 | Ensures the ergonomic use of packages. 8 | 9 | Example: 10 | If a user adds a package with Entity Framework, it not need to add new namespace to use the extension method. 11 | 12 | Sometimes IntelliSense does not suggest which namespace to use for extension methods. -------------------------------------------------------------------------------- /architecture-decision-records/2024-06.1-change-structure-of-project.md: -------------------------------------------------------------------------------- 1 | # Title 2 | Change structure of project. 3 | 4 | ## Description 5 | The current project architecture consists of many packages, this can be overwhelming for new users. 6 | 7 |
8 | 9 |
10 | 11 | The main components `TransactionalBox.Inbox` and `TransactionalBox.Outbox` should be moved to the main package `TransactionalBox`. When using the package via extension methods, the user will decide which components to register in container. 12 | 13 | Packages with external dependencies are also worth merging. e.g. `TransactionalBox.Outbox.Kafka` and `TransactionalBox.Inbox.Kafka` should be moved to the `TransactionalBox.Kafka` package. 14 | 15 | Internal packages from the `Internals` folder should be moved to the `TransactionalBox` package. 16 | 17 | In this way we can go from 7 to 3 public packages. -------------------------------------------------------------------------------- /architecture-decision-records/README.md: -------------------------------------------------------------------------------- 1 | # Architecture Decision Records 2 | 3 | ### Disclaimer 4 | *All ADRs up to and including 2024.06 were already created after the decision was taken. They are created to serialise knowledge about project. They are not perfect, but they contain the knowledge on which decisions have been taken.* -------------------------------------------------------------------------------- /architecture-decision-records/assets/2024-06.1-structure-of-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/architecture-decision-records/assets/2024-06.1-structure-of-project.png -------------------------------------------------------------------------------- /assets/diagrams/inbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/assets/diagrams/inbox.png -------------------------------------------------------------------------------- /assets/diagrams/outbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/assets/diagrams/outbox.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/assets/logo.png -------------------------------------------------------------------------------- /assets/rounded-social-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/assets/rounded-social-logo.png -------------------------------------------------------------------------------- /assets/samples/web-api-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/assets/samples/web-api-sample.png -------------------------------------------------------------------------------- /assets/small-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/assets/small-logo.png -------------------------------------------------------------------------------- /documentation/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /documentation/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /documentation/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /documentation/docs/assets/AddToOutbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/docs/assets/AddToOutbox.png -------------------------------------------------------------------------------- /documentation/docs/assets/AddToTransport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/docs/assets/AddToTransport.png -------------------------------------------------------------------------------- /documentation/docs/assets/distributed-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/docs/assets/distributed-sample.png -------------------------------------------------------------------------------- /documentation/docs/assets/inbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/docs/assets/inbox.png -------------------------------------------------------------------------------- /documentation/docs/assets/outbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/docs/assets/outbox.png -------------------------------------------------------------------------------- /documentation/docs/assets/rounded-social-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/docs/assets/rounded-social-logo.png -------------------------------------------------------------------------------- /documentation/docs/assets/simple-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/docs/assets/simple-sample.png -------------------------------------------------------------------------------- /documentation/docs/samples/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Samples", 3 | "position": 7 4 | } 5 | -------------------------------------------------------------------------------- /documentation/docs/samples/distributed-sample.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Distributed Sample 6 | Distributed sample consists of four modules with two instances each. Example shows a simplified banking domain 7 | :::info 8 | Docker is required. 9 | ::: 10 | 11 | Clone repo and open `TransactionalBox.sln` via Visual Studio. Set the `Bank` as startup and then run. 12 | 13 | Run browser and open below links: 14 | **CustomerRegistrations:** `https://localhost:1000/swagger/index.html` 15 | **Customers**: `https://localhost:2000/swagger/index.html` 16 | **BankAccounts:** `https://localhost:3000/swagger/index.html` 17 | **Loans:** `https://localhost:4000/swagger/index.html` 18 | 19 |
20 | ![distributed-sample](../assets/distributed-sample.png) 21 |
22 | 23 | 24 | Don't forget to use break points to learn. 25 | Have fun :smiley:! -------------------------------------------------------------------------------- /documentation/docs/samples/simple-sample.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Simple Sample 6 | Simple sample consists of one module to help understand how transactional box works. 7 | 8 | :::info 9 | Docker is required. 10 | ::: 11 | 12 | Clone repo and open `TransactionalBox.sln` via Visual Studio. Set the `TransactionalBox.Sample.WebApi` as startup and then run. You should see the following view. 13 | 14 |
15 | ![simple-sample](../assets/simple-sample.png) 16 |
17 | 18 | Don't forget to use break points to learn. 19 | Have fun :smiley:! -------------------------------------------------------------------------------- /documentation/docs/storage/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Storage", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /documentation/docs/storage/in-memory.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # InMemory 6 | :::info 7 | InMemory is registered as default storage. 8 | ::: 9 | :::warning 10 | Intended only for testing and learning. 11 | ::: 12 | 13 | ## Outbox 14 | ### Register 15 | ```csharp 16 | builder.Services.AddTransactionalBox(x => 17 | { 18 | x.AddOutbox(); 19 | }); 20 | 21 | ``` 22 | 23 | ## Inbox 24 | ### Register 25 | ```csharp 26 | builder.Services.AddTransactionalBox(x => 27 | { 28 | x.AddInbox(); 29 | }); 30 | 31 | ``` -------------------------------------------------------------------------------- /documentation/docs/transport/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Transport", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /documentation/docs/transport/in-memory.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # InMemory 6 | :::info 7 | InMemory is registered as default transport. 8 | ::: 9 | :::warning 10 | Intended only for testing and learning. 11 | ::: 12 | 13 | ## Outbox 14 | ### Register 15 | ```csharp 16 | builder.Services.AddTransactionalBox(x => 17 | { 18 | x.AddOutbox(); 19 | }); 20 | 21 | ``` 22 | 23 | ## Inbox 24 | ### Register 25 | ```csharp 26 | builder.Services.AddTransactionalBox(x => 27 | { 28 | x.AddInbox(); 29 | }); 30 | 31 | ``` -------------------------------------------------------------------------------- /documentation/docs/transport/kafka.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Apache Kafka 6 | 7 | ## Install package 8 | ```csharp 9 | dotnet add package TransactionalBox.Kafka 10 | ``` 11 | ## Outbox 12 | ### Register 13 | ```csharp 14 | builder.Services.AddTransactionalBox(x => 15 | { 16 | x.AddOutbox( 17 | storage => ..., 18 | transport => transport.UseKafka(settings => settings.BootstrapServers = bootstrapServers) 19 | ); 20 | }); 21 | 22 | ``` 23 | 24 | ## Inbox 25 | ### Register 26 | ```csharp 27 | builder.Services.AddTransactionalBox(x => 28 | { 29 | x.AddInbox( 30 | storage => ..., 31 | transport => transport.UseKafka(settings => settings.BootstrapServers = bootstrapServers) 32 | ); 33 | }); 34 | 35 | ``` -------------------------------------------------------------------------------- /documentation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentation", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "3.1.1", 18 | "@docusaurus/preset-classic": "3.1.1", 19 | "@mdx-js/react": "^3.0.0", 20 | "clsx": "^2.0.0", 21 | "prism-react-renderer": "^2.3.0", 22 | "react": "^18.0.0", 23 | "react-dom": "^18.0.0" 24 | }, 25 | "devDependencies": { 26 | "@docusaurus/module-type-aliases": "3.1.1", 27 | "@docusaurus/types": "3.1.1" 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.5%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 3 chrome version", 37 | "last 3 firefox version", 38 | "last 5 safari version" 39 | ] 40 | }, 41 | "engines": { 42 | "node": ">=18.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /documentation/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | 'intro', 23 | 'hello', 24 | { 25 | type: 'category', 26 | label: 'Tutorial', 27 | items: ['tutorial-basics/create-a-document'], 28 | }, 29 | ], 30 | */ 31 | }; 32 | 33 | export default sidebars; 34 | -------------------------------------------------------------------------------- /documentation/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 | -------------------------------------------------------------------------------- /documentation/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #5575be; 23 | --ifm-color-primary-dark: #4466b3; 24 | --ifm-color-primary-darker: #4160a9; 25 | --ifm-color-primary-darkest: #354f8b; 26 | --ifm-color-primary-light: #6985c6; 27 | --ifm-color-primary-lighter: #738dc9; 28 | --ifm-color-primary-lightest: #91a5d5; 29 | } 30 | -------------------------------------------------------------------------------- /documentation/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 | -------------------------------------------------------------------------------- /documentation/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /documentation/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/static/.nojekyll -------------------------------------------------------------------------------- /documentation/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/static/img/logo.png -------------------------------------------------------------------------------- /documentation/static/img/medium-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/static/img/medium-logo.png -------------------------------------------------------------------------------- /documentation/static/img/social-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adimiko/TransactionalBox/54d8d42a29d116a0ef11a98097305154d0af30fd/documentation/static/img/social-logo.png -------------------------------------------------------------------------------- /samples/Bank/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 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 26 | !**/.gitignore 27 | !.git/HEAD 28 | !.git/config 29 | !.git/packed-refs 30 | !.git/refs/heads/** -------------------------------------------------------------------------------- /samples/Bank/Bank.dcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2.1 5 | Linux 6 | False 7 | af9aebe9-ceb7-490a-828e-2e9ba6b4400b 8 | LaunchBrowser 9 | {Scheme}://localhost:{ServicePort}/swagger 10 | transactionalbox.customerregistrations 11 | 12 | 13 | 14 | docker-compose.yml 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/Bank/Internals/BankLogger/BankLogger.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/Bank/Internals/BankLogger/LoggerExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Serilog.Events; 3 | using Serilog; 4 | using Serilog.Sinks.Elasticsearch; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | namespace BankLogger 9 | { 10 | public static class LoggerExtension 11 | { 12 | public static void AddBankLogger(this WebApplicationBuilder builder, IConfiguration configuration, Assembly assembly) 13 | { 14 | Log.Logger = new LoggerConfiguration() 15 | .MinimumLevel.Override("Microsoft", LogEventLevel.Information) 16 | .Enrich.FromLogContext() 17 | .WriteTo.Console() 18 | .WriteTo.Debug() 19 | .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://elasticsearch:9200")) 20 | { 21 | AutoRegisterTemplate = true, 22 | //AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6, 23 | IndexFormat = $"{assembly.GetName().Name!.ToLower().Replace(".", "-")}-{DateTime.UtcNow:yyyy-MM}", 24 | NumberOfReplicas = 1, 25 | NumberOfShards = 2 26 | }) 27 | .ReadFrom.Configuration(configuration) 28 | .CreateLogger(); 29 | 30 | builder.Host.UseSerilog(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/Bank/Internals/BankLogger/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "BankLogger": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:52594;http://localhost:52595" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.BankAccounts/Database/BankAccountEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using TransactionalBox.BankAccounts.Models; 4 | 5 | namespace TransactionalBox.BankAccounts.Database 6 | { 7 | public class BankAccountEntityTypeConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(x => x.Id); 12 | builder.Property(x => x.CustomerId); 13 | builder.Property(x => x.Balance); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.BankAccounts/Database/BankAccountsDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using TransactionalBox.BankAccounts.Models; 3 | 4 | namespace TransactionalBox.BankAccounts.Database 5 | { 6 | public class BankAccountsDbContext : DbContext 7 | { 8 | public DbSet BankAccounts { get; set; } 9 | 10 | public BankAccountsDbContext() : base() { } 11 | 12 | public BankAccountsDbContext(DbContextOptions options) 13 | : base(options) { } 14 | 15 | protected override void OnModelCreating(ModelBuilder modelBuilder) 16 | { 17 | modelBuilder.AddInbox(); 18 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(BankAccountsDbContext).Assembly); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.BankAccounts/Messages/CreatedCustomerEventMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.BankAccounts.Messages 2 | { 3 | public class CreatedCustomerEventMessage : InboxMessage 4 | { 5 | public Guid Id { get; init; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.BankAccounts/Messages/CreatedCustomerEventMessageDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.BankAccounts.Messages 2 | { 3 | public class CreatedCustomerEventMessageDefinition : InboxDefinition 4 | { 5 | public CreatedCustomerEventMessageDefinition() 6 | { 7 | PublishedBy = "Customers"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.BankAccounts/Messages/CreatedCustomerEventMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.BankAccounts.Database; 2 | using TransactionalBox.BankAccounts.Models; 3 | 4 | namespace TransactionalBox.BankAccounts.Messages 5 | { 6 | public sealed class CreatedCustomerEventMessageHandler : IInboxHandler 7 | { 8 | private readonly BankAccountsDbContext _bankAccountsDbContext; 9 | 10 | public CreatedCustomerEventMessageHandler(BankAccountsDbContext bankAccountsDbContext) 11 | { 12 | _bankAccountsDbContext = bankAccountsDbContext; 13 | } 14 | 15 | public async Task Handle(CreatedCustomerEventMessage message, IExecutionContext executionContext) 16 | { 17 | var bankAccount = new BankAccount() 18 | { 19 | Id = Guid.NewGuid(), 20 | CustomerId = message.Id, 21 | Balance = 100, 22 | }; 23 | 24 | await _bankAccountsDbContext.BankAccounts.AddAsync(bankAccount); 25 | await _bankAccountsDbContext.SaveChangesAsync(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.BankAccounts/Models/BankAccount.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.BankAccounts.Models 2 | { 3 | public class BankAccount 4 | { 5 | public Guid Id { get; set; } 6 | 7 | public Guid CustomerId { get; set; } 8 | 9 | public decimal Balance { get; set; } = 100; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.BankAccounts/TransactionalBox.BankAccounts.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 061812cd-fafc-4ebb-9ae9-3339594f2d33 9 | Linux 10 | ..\..\.. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.BankAccounts/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.BankAccounts/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | 9 | "Serilog": { 10 | "MinimumLevel": { 11 | "Default": "Information", 12 | "Override": { 13 | "Microsoft": "Information", 14 | "System": "Warning", 15 | "Microsoft.EntityFrameworkCore.Database.Command": "Error", 16 | "TransactionalBox.*": "Information" 17 | } 18 | } 19 | }, 20 | 21 | "ElasticSearch": { 22 | "Url": "http://localhost:9200" 23 | }, 24 | 25 | "AllowedHosts": "*" 26 | } 27 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/Database/CustomerRegistrationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using TransactionalBox.CustomerRegistrations.Models; 3 | 4 | namespace TransactionalBox.CustomerRegistrations.Database 5 | { 6 | public sealed class CustomerRegistrationDbContext : DbContext 7 | { 8 | public DbSet CustomerRegistrations { get; set; } 9 | 10 | public CustomerRegistrationDbContext() : base() { } 11 | 12 | public CustomerRegistrationDbContext(DbContextOptions options) 13 | : base(options) { } 14 | 15 | protected override void OnModelCreating(ModelBuilder modelBuilder) 16 | { 17 | modelBuilder.AddOutbox(); 18 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(CustomerRegistrationDbContext).Assembly); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/Database/CustomerRegistrationEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using TransactionalBox.CustomerRegistrations.Models; 4 | 5 | namespace TransactionalBox.CustomerRegistrations.Database 6 | { 7 | public class CustomerRegistrationEntityTypeConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(x => x.Id); 12 | builder.Property(x => x.FirstName); 13 | builder.Property(x => x.LastName); 14 | builder.Property(x => x.Age); 15 | builder.Property(x => x.IsApproved); 16 | builder.Property(x => x.CreatedAtUtc); 17 | builder.Property(x => x.UpdatedAtUtc); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/Messages/CreateCustomerCommandMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.CustomerRegistrations.Messages 2 | { 3 | public sealed class CreateCustomerCommandMessage : OutboxMessage 4 | { 5 | public Guid Id { get; init; } 6 | 7 | public string FirstName { get; init; } 8 | 9 | public string LastName { get; init; } 10 | 11 | public int Age { get; init; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/Messages/CreateCustomerCommandMessageDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.CustomerRegistrations.Messages 2 | { 3 | internal sealed class CreateCustomerCommandMessageDefinition : OutboxDefinition 4 | { 5 | public CreateCustomerCommandMessageDefinition() 6 | { 7 | Receiver = "Customers"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/Models/CustomerRegistration.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.CustomerRegistrations.Models 2 | { 3 | public class CustomerRegistration 4 | { 5 | public required Guid Id { get; set; } 6 | 7 | public required string FirstName { get; set; } 8 | 9 | public required string LastName { get; set; } 10 | 11 | public required int Age { get; set; } 12 | 13 | public bool IsApproved { get; set; } = false; 14 | 15 | public required DateTime CreatedAtUtc { get; init; } 16 | 17 | public required DateTime UpdatedAtUtc { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/Requests/ApproveCustomerRegistrationRequest.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.CustomerRegistrations.Requests 2 | { 3 | public sealed class ApproveCustomerRegistrationRequest 4 | { 5 | public Guid Id { get; init; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/Requests/CreateCustomerRegistrationRequest.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.CustomerRegistrations.Requests 2 | { 3 | public class CreateCustomerRegistrationRequest 4 | { 5 | public string FirstName { get; init; } 6 | 7 | public string LastName { get; init; } 8 | 9 | public int Age { get; init; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/TransactionalBox.CustomerRegistrations.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | de7ed6e4-a1eb-483b-9909-be15dca480ca 9 | Linux 10 | ..\..\.. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.CustomerRegistrations/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Customers/Database/CustomerEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using TransactionalBox.Customers.Models; 4 | 5 | namespace TransactionalBox.Customers.Database 6 | { 7 | public class CustomerEntityTypeConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(x => x.Id); 12 | builder.Property(x => x.FirstName); 13 | builder.Property(x => x.LastName); 14 | builder.Property(x => x.Age); 15 | builder.Property(x => x.CreatedAtUtc); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Customers/Database/CustomersDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using TransactionalBox.Customers.Models; 3 | 4 | namespace TransactionalBox.Customers.Database 5 | { 6 | public class CustomersDbContext : DbContext 7 | { 8 | public DbSet Customers { get; set; } 9 | 10 | public CustomersDbContext() : base() { } 11 | 12 | public CustomersDbContext(DbContextOptions options) 13 | : base(options) { } 14 | 15 | protected override void OnModelCreating(ModelBuilder modelBuilder) 16 | { 17 | modelBuilder.AddOutbox(); 18 | modelBuilder.AddInbox(); 19 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(CustomersDbContext).Assembly); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Customers/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base 4 | USER app 5 | WORKDIR /app 6 | EXPOSE 8080 7 | EXPOSE 8081 8 | 9 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build 10 | ARG BUILD_CONFIGURATION=Release 11 | WORKDIR /src 12 | COPY ["samples/Directory.Packages.props", "samples/"] 13 | COPY ["samples/Bank/TransactionalBox.Customers/TransactionalBox.Customers.csproj", "samples/Bank/TransactionalBox.Customers/"] 14 | RUN dotnet restore "./samples/Bank/TransactionalBox.Customers/./TransactionalBox.Customers.csproj" 15 | COPY . . 16 | WORKDIR "/src/samples/Bank/TransactionalBox.Customers" 17 | RUN dotnet build "./TransactionalBox.Customers.csproj" -c $BUILD_CONFIGURATION -o /app/build 18 | 19 | FROM build AS publish 20 | ARG BUILD_CONFIGURATION=Release 21 | RUN dotnet publish "./TransactionalBox.Customers.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false 22 | 23 | FROM base AS final 24 | WORKDIR /app 25 | COPY --from=publish /app/publish . 26 | ENTRYPOINT ["dotnet", "TransactionalBox.Customers.dll"] -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Customers/Messages/CreateCustomerCommandMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Customers.Messages 2 | { 3 | public sealed class CreateCustomerCommandMessage : InboxMessage 4 | { 5 | public Guid Id { get; init; } 6 | 7 | public string FirstName { get; init; } 8 | 9 | public string LastName { get; init; } 10 | 11 | public int Age { get; init; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Customers/Messages/CreatedCustomerEventMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Customers.Messages 2 | { 3 | public class CreatedCustomerEventMessage : OutboxMessage 4 | { 5 | public Guid Id { get; init; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Customers/Models/Customer.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Customers.Models 2 | { 3 | public class Customer 4 | { 5 | public Guid Id { get; init; } 6 | 7 | public string FirstName { get; init; } 8 | 9 | public string LastName { get; init; } 10 | 11 | public int Age { get; init; } 12 | 13 | public required DateTime CreatedAtUtc { get; init; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Customers/TransactionalBox.Customers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | false 8 | 6b456489-9b3e-4df4-9cd4-d04b03067250 9 | Linux 10 | ..\..\.. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Customers/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Customers/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Loans/Database/LoanEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using TransactionalBox.Loans.Models; 4 | 5 | namespace TransactionalBox.Loans.Database 6 | { 7 | public class LoanEntityTypeConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(x => x.Id); 12 | builder.Property(x => x.CustomerId); 13 | builder.Property(x => x.Amount); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Loans/Database/LoansDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using TransactionalBox.Loans.Models; 3 | 4 | namespace TransactionalBox.Loans.Database 5 | { 6 | public class LoansDbContext : DbContext 7 | { 8 | public DbSet Loans { get; set; } 9 | 10 | public LoansDbContext() : base() { } 11 | 12 | public LoansDbContext(DbContextOptions options) 13 | : base(options) { } 14 | 15 | protected override void OnModelCreating(ModelBuilder modelBuilder) 16 | { 17 | modelBuilder.AddInbox(); 18 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(LoansDbContext).Assembly); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Loans/Messages/CreatedCustomerEventMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Loans.Messages 2 | { 3 | public class CreatedCustomerEventMessage : InboxMessage 4 | { 5 | public Guid Id { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Loans/Messages/CreatedCustomerEventMessageDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Loans.Messages 2 | { 3 | public class CreatedCustomerEventMessageDefinition : InboxDefinition 4 | { 5 | public CreatedCustomerEventMessageDefinition() 6 | { 7 | PublishedBy = "Customers"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Loans/Messages/CreatedCustomerEventMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Loans.Database; 2 | using TransactionalBox.Loans.Models; 3 | 4 | namespace TransactionalBox.Loans.Messages 5 | { 6 | public sealed class CreatedCustomerEventMessageHandler : IInboxHandler 7 | { 8 | private readonly LoansDbContext _loansDbContext; 9 | 10 | public CreatedCustomerEventMessageHandler(LoansDbContext loansDbContext) 11 | { 12 | _loansDbContext = loansDbContext; 13 | } 14 | 15 | public async Task Handle(CreatedCustomerEventMessage message, IExecutionContext executionContext) 16 | { 17 | var loan = new Loan() 18 | { 19 | Id = Guid.NewGuid(), 20 | CustomerId = message.Id, 21 | Amount = 0, 22 | }; 23 | 24 | await _loansDbContext.Loans.AddAsync(loan); 25 | await _loansDbContext.SaveChangesAsync(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Loans/Models/Loan.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Loans.Models 2 | { 3 | public class Loan 4 | { 5 | public Guid Id { get; set; } 6 | 7 | public Guid CustomerId { get; set; } 8 | 9 | public decimal Amount { get; set; } = 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Loans/TransactionalBox.Loans.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | fadd0aae-2531-4163-9e3b-e9c117fa776b 9 | Linux 10 | ..\..\.. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Loans/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/Bank/TransactionalBox.Loans/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /samples/Bank/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Docker Compose": { 4 | "commandName": "DockerCompose", 5 | "commandVersion": "1.0", 6 | "serviceActions": { 7 | "transactionalbox.customerregistrations": "StartDebugging", 8 | "transactionalbox.customers": "StartDebugging", 9 | "transactionalbox.bankaccounts": "StartDebugging", 10 | "transactionalbox.loans": "StartDebugging" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/Program.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox; 2 | using TransactionalBox.Sample.WebApi.InMemory.ServiceWithOutbox; 3 | using TransactionalBox.Internals.Outbox.Storage.InMemory; 4 | using TransactionalBox.Internals.Inbox.Storage.InMemory; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | builder.Services.AddEndpointsApiExplorer(); 9 | builder.Services.AddSwaggerGen(); 10 | builder.Services.AddScoped(); 11 | 12 | builder.Services.AddTransactionalBox(x => 13 | { 14 | x.AddOutbox(); 15 | 16 | x.AddInbox(); 17 | }, 18 | configuration: builder.Configuration); 19 | 20 | var app = builder.Build(); 21 | 22 | // Configure the HTTP request pipeline. 23 | if (app.Environment.IsDevelopment()) 24 | { 25 | app.UseSwagger(); 26 | app.UseSwaggerUI(); 27 | } 28 | 29 | app.UseHttpsRedirection(); 30 | 31 | app.MapPost("/add-message-to-outbox", async (ExampleServiceWithOutbox exampleServiceWithOutbox) => await exampleServiceWithOutbox.Execute()); 32 | 33 | app.MapGet("/get-messages-from-outbox", (IOutboxStorageReadOnly outboxStorageReadOnly) => outboxStorageReadOnly.OutboxMessages); 34 | 35 | app.MapGet("/get-messages-from-inbox", (IInboxStorageReadOnly inboxStorage) => inboxStorage.InboxMessages); 36 | 37 | app.MapGet("/get-idempotent-messages-from-inbox", (IInboxStorageReadOnly inboxStorage) => inboxStorage.IdempotentInboxKeys); 38 | 39 | app.Run(); 40 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:1454", 8 | "sslPort": 44334 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5152", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7070;http://localhost:5152", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/ServiceWithInbox/ExampleMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InMemory.ServiceWithInbox 2 | { 3 | public sealed class ExampleMessage : InboxMessage 4 | { 5 | public string Name { get; init; } 6 | 7 | public int Age { get; init; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/ServiceWithInbox/ExampleMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InMemory.ServiceWithInbox 2 | { 3 | internal sealed class ExampleMessageHandler : IInboxHandler 4 | { 5 | public async Task Handle(ExampleMessage message, IExecutionContext executionContext) 6 | { 7 | // Your Logic 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/ServiceWithInbox/PublishableMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InMemory.ServiceWithInbox 2 | { 3 | public class PublishableMessage : InboxMessage 4 | { 5 | public string Name { get; init; } 6 | 7 | public int Age { get; init; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/ServiceWithInbox/PublishableMessageDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InMemory.ServiceWithInbox 2 | { 3 | public sealed class PublishableMessageDefinition : InboxDefinition 4 | { 5 | public PublishableMessageDefinition() 6 | { 7 | PublishedBy = "ExampleServiceId"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/ServiceWithInbox/PublishableMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InMemory.ServiceWithInbox 2 | { 3 | internal sealed class PublishableMessageHandler : IInboxHandler 4 | { 5 | public Task Handle(PublishableMessage message, IExecutionContext executionContext) 6 | { 7 | //Logic 8 | //throw new NotImplementedException(); 9 | return Task.CompletedTask; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/ServiceWithOutbox/ExampleMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InMemory.ServiceWithOutbox 2 | { 3 | public sealed class ExampleMessage : OutboxMessage 4 | { 5 | public string Name { get; init; } 6 | 7 | public int Age { get; init; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/ServiceWithOutbox/ExampleMessageDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InMemory.ServiceWithOutbox 2 | { 3 | public sealed class ExampleMessageDefinition : OutboxDefinition 4 | { 5 | public ExampleMessageDefinition() 6 | { 7 | Receiver = "ExampleServiceId"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/ServiceWithOutbox/ExampleServiceWithOutbox.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox; 2 | 3 | namespace TransactionalBox.Sample.WebApi.InMemory.ServiceWithOutbox 4 | { 5 | public sealed class ExampleServiceWithOutbox 6 | { 7 | private readonly IOutbox _outbox; 8 | 9 | public ExampleServiceWithOutbox(IOutbox outbox) 10 | { 11 | _outbox = outbox; 12 | } 13 | 14 | public async Task Execute() 15 | { 16 | var message = new ExampleMessage() 17 | { 18 | Name = "Name", 19 | Age = 25, 20 | }; 21 | 22 | await _outbox.Add(message); 23 | await _outbox.TransactionCommited(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/ServiceWithOutbox/PublishableMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InMemory.ServiceWithOutbox 2 | { 3 | public class PublishableMessage : OutboxMessage 4 | { 5 | public string Name { get; set; } = "Adrian"; 6 | 7 | public int Age { get; set; } = 25; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/TransactionalBox.Sample.WebApi.InMemory.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi.InMemory/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | 9 | "AllowedHosts": "*", 10 | 11 | "TransactionalBox": { 12 | "ServiceId": "ExampleServiceId" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/InboxMessages/ExampleMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InboxMessages 2 | { 3 | public sealed class ExampleMessage : InboxMessage 4 | { 5 | public string Name { get; set; } 6 | 7 | public int Age { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/InboxMessages/ExampleMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InboxMessages 2 | { 3 | internal sealed class ExampleMessageHandler : IInboxHandler 4 | { 5 | 6 | private readonly SampleDbContext _context; 7 | 8 | public ExampleMessageHandler(SampleDbContext dbContext) 9 | { 10 | _context = dbContext; 11 | } 12 | 13 | 14 | public async Task Handle(ExampleMessage message, IExecutionContext executionContext) 15 | { 16 | // Logic 17 | // TODO config AutoSaveChanges = false (default) 18 | await _context.SaveChangesAsync(executionContext.CancellationToken); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/InboxMessages/PublishableMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InboxMessages 2 | { 3 | public class PublishableMessage : InboxMessage 4 | { 5 | public string Name { get; init; } 6 | 7 | public int Age { get; init; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/InboxMessages/PublishableMessageDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InboxMessages 2 | { 3 | public sealed class PublishableMessageDefinition : InboxDefinition 4 | { 5 | public PublishableMessageDefinition() 6 | { 7 | PublishedBy = "ExampleServiceId"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/InboxMessages/PublishableMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.InboxMessages 2 | { 3 | internal sealed class PublishableMessageHandler : IInboxHandler 4 | { 5 | public Task Handle(PublishableMessage message, IExecutionContext executionContext) 6 | { 7 | //Logic 8 | //throw new NotImplementedException(); 9 | return Task.CompletedTask; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/OutboxMessages/ExampleMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.OutboxMessages 2 | { 3 | public sealed class ExampleMessage : OutboxMessage 4 | { 5 | public string Name { get; set; } 6 | 7 | public int Age { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/OutboxMessages/ExampleMessageDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Sample.WebApi.OutboxMessages 2 | { 3 | internal sealed class ExampleMessageDefinition : OutboxDefinition 4 | { 5 | public ExampleMessageDefinition() 6 | { 7 | Receiver = "Registrations"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:63254", 8 | "sslPort": 44313 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5036", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7108;http://localhost:5036", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/SampleDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace TransactionalBox.Sample.WebApi 4 | { 5 | public class SampleDbContext : DbContext 6 | { 7 | public SampleDbContext() : base() { } 8 | 9 | public SampleDbContext(DbContextOptions options) 10 | : base(options) { } 11 | 12 | protected override void OnModelCreating(ModelBuilder modelBuilder) 13 | { 14 | modelBuilder.AddOutbox(); 15 | modelBuilder.AddInbox(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/TransactionalBox.Sample.WebApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Microsoft.EntityFrameworkCore.Database.Command": "Error", 7 | "TransactionalBox.*": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/TransactionalBox.Sample.WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /source/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | net8.0 9 | enable 10 | enable 11 | https://transactionalbox.com 12 | https://github.com/adimiko/TransactionalBox 13 | git 14 | $(AssemblyName) 15 | $(AssemblyName) 16 | small-logo.png 17 | Outbox and Inbox Pattern in .NET 18 | True 19 | MIT 20 | 21 | 22 | 23 | 24 | True 25 | \ 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Extensions/Inbox/InboxExtensionAddInbox.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using TransactionalBox.EntityFrameworkCore.Internals.Inbox.EntityTypeConfigurations; 3 | using TransactionalBox.EntityFrameworkCore.Internals.InternalPackages.DistributedLock; 4 | using TransactionalBox.Internals.Inbox.Storage; 5 | 6 | namespace TransactionalBox 7 | { 8 | public static class InboxExtensionAddInbox 9 | { 10 | public static void AddInbox(this ModelBuilder modelBuilder) 11 | { 12 | modelBuilder.ApplyConfiguration(new InboxMessageEntityTypeConfiguration()); 13 | modelBuilder.ApplyConfiguration(new IdempotentInboxKeyEntityTypeConfiguration()); 14 | modelBuilder.AddEntityFrameworkCoreDistributedLock(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Extensions/Outbox/OutboxExtensionAddOutbox.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using TransactionalBox.EntityFrameworkCore.Internals.InternalPackages.DistributedLock; 3 | using TransactionalBox.EntityFrameworkCore.Internals.Outbox.EntityTypeConfigurations; 4 | using TransactionalBox.Internals.Outbox.Storage; 5 | 6 | namespace TransactionalBox 7 | { 8 | public static class OutboxExtensionAddOutbox 9 | { 10 | public static void AddOutbox(this ModelBuilder modelBuilder) 11 | { 12 | modelBuilder.ApplyConfiguration(new OutboxMessageEntityTypeConfiguration()); 13 | modelBuilder.AddEntityFrameworkCoreDistributedLock(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Internals/Inbox/EntityTypeConfigurations/IdempotentInboxKeyEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using TransactionalBox.Internals.Inbox.Storage; 4 | 5 | namespace TransactionalBox.EntityFrameworkCore.Internals.Inbox.EntityTypeConfigurations 6 | { 7 | internal sealed class IdempotentInboxKeyEntityTypeConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(x => x.Id); 12 | builder.Property(x => x.ExpirationUtc); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Internals/Inbox/EntityTypeConfigurations/InboxMessageEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 2 | using Microsoft.EntityFrameworkCore; 3 | using TransactionalBox.Internals.Inbox.Storage; 4 | 5 | namespace TransactionalBox.EntityFrameworkCore.Internals.Inbox.EntityTypeConfigurations 6 | { 7 | internal sealed class InboxMessageEntityTypeConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(x => x.Id); 12 | builder.Property(x => x.OccurredUtc); 13 | builder.Property(x => x.IsProcessed).IsConcurrencyToken(); 14 | builder.Property(x => x.Topic); 15 | builder.Property(x => x.Payload); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Internals/InternalPackages/DistributedLock/ExtensionAddEntityFrameworkCoreDistributedLock.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using TransactionalBox.Internals.InternalPackages.DistributedLock; 3 | 4 | namespace TransactionalBox.EntityFrameworkCore.Internals.InternalPackages.DistributedLock 5 | { 6 | internal static class ExtensionAddEntityFrameworkCoreDistributedLock 7 | { 8 | internal static void AddEntityFrameworkCoreDistributedLock(this ModelBuilder modelBuilder) 9 | where T : Lock, new() 10 | { 11 | modelBuilder.ApplyConfiguration(new LockEntityTypeConfiguration()); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Internals/InternalPackages/DistributedLock/ExtensionUseEntityFrameworkCore.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using TransactionalBox.Internals.InternalPackages.DistributedLock; 4 | 5 | namespace TransactionalBox.EntityFrameworkCore.Internals.InternalPackages.DistributedLock 6 | { 7 | internal static class ExtensionUseEntityFrameworkCore 8 | { 9 | internal static IServiceCollection UseEntityFrameworkCore( 10 | this IDistributedLockStorageConfigurator storageConfigurator) 11 | where TDbContext : DbContext 12 | { 13 | var services = storageConfigurator.Services; 14 | 15 | services.AddScoped(sp => sp.GetRequiredService()); 16 | services.AddScoped(); 17 | 18 | return services; 19 | } 20 | 21 | internal static IServiceCollection UseEntityFrameworkCore( 22 | this IDistributedLockStorageConfigurator storageConfigurator) 23 | { 24 | var services = storageConfigurator.Services; 25 | 26 | services.AddScoped(); 27 | 28 | return services; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Internals/InternalPackages/DistributedLock/LockEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using TransactionalBox.Internals.InternalPackages.DistributedLock; 4 | 5 | namespace TransactionalBox.EntityFrameworkCore.Internals.InternalPackages.DistributedLock 6 | { 7 | internal sealed class LockEntityTypeConfiguration : IEntityTypeConfiguration 8 | where T : Lock 9 | { 10 | public void Configure(EntityTypeBuilder builder) 11 | { 12 | builder.HasKey(x => x.Key); 13 | builder.Property(x => x.ExpirationUtc); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Internals/Outbox/EntityTypeConfigurations/OutboxMessageEntityTypeConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | using TransactionalBox.Internals.Outbox.Storage; 4 | 5 | namespace TransactionalBox.EntityFrameworkCore.Internals.Outbox.EntityTypeConfigurations 6 | { 7 | internal sealed class OutboxMessageEntityTypeConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(x => x.Id); 12 | builder.Property(x => x.OccurredUtc).IsRequired(); 13 | builder.Property(x => x.Topic).IsRequired(); 14 | builder.Property(x => x.Payload).IsRequired(); 15 | builder.Property(x => x.LockUtc); 16 | builder.Property(x => x.IsProcessed).IsConcurrencyToken(); 17 | builder.Property(x => x.JobId).HasMaxLength(254); //TODO 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Internals/Outbox/ImplementedContracts/EntityFrameworkOutboxStorage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using TransactionalBox.Internals.Outbox.Storage; 3 | using TransactionalBox.Internals.Outbox.Storage.ContractsToImplement; 4 | 5 | namespace TransactionalBox.EntityFrameworkCore.Internals.Outbox.ImplementedContracts 6 | { 7 | internal sealed class EntityFrameworkOutboxStorage : IOutboxStorage 8 | { 9 | private readonly DbSet _outbox; 10 | 11 | public EntityFrameworkOutboxStorage(DbContext dbContext) 12 | { 13 | _outbox = dbContext.Set(); 14 | } 15 | 16 | public Task Add(OutboxMessageStorage message) 17 | { 18 | return _outbox.AddAsync(message).AsTask(); 19 | } 20 | 21 | public Task AddRange(IEnumerable messages) 22 | { 23 | return _outbox.AddRangeAsync(messages); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/Internals/Outbox/ImplementedContracts/EntityFrameworkStorageProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using TransactionalBox.Internals.Outbox.Storage.ContractsToImplement; 4 | 5 | namespace TransactionalBox.EntityFrameworkCore.Internals.Outbox.ImplementedContracts 6 | { 7 | internal sealed class EntityFrameworkStorageProvider : IStorageProvider 8 | { 9 | public string? ProviderName { get; } 10 | 11 | public EntityFrameworkStorageProvider(IServiceProvider serviceProvider) 12 | { 13 | using (var scope = serviceProvider.CreateScope()) 14 | { 15 | ProviderName = scope.ServiceProvider.GetRequiredService().Database.ProviderName; 16 | } 17 | } 18 | 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/TransactionalBox.EntityFrameworkCore/TransactionalBox.EntityFrameworkCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /source/TransactionalBox.Kafka/Exntesions/Inbox/InboxExtensionUseKafka.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Kafka.Settings.Inbox; 3 | using TransactionalBox.Kafka.Internals.Inbox; 4 | using TransactionalBox.Kafka.Internals.Inbox.ImplementedContracts; 5 | using TransactionalBox.Configurators.Inbox; 6 | using TransactionalBox.Internals.Inbox.Transport.ContractsToImplement; 7 | 8 | namespace TransactionalBox 9 | { 10 | public static class InboxExtensionUseKafka 11 | { 12 | public static void UseKafka( 13 | this IInboxTransportConfigurator inboxWorkerTransportConfigurator, 14 | Action settingsConfiguration = null) 15 | { 16 | var services = inboxWorkerTransportConfigurator.Services; 17 | var settings = new InboxKafkaSettings(); 18 | 19 | if (settingsConfiguration is not null) 20 | { 21 | settingsConfiguration(settings); 22 | } 23 | 24 | services.AddSingleton(settings); 25 | services.AddSingleton(); 26 | services.AddSingleton(); 27 | services.AddSingleton(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/TransactionalBox.Kafka/Exntesions/Outbox/OutboxExtensionUseKafka.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Outbox; 3 | using TransactionalBox.Internals.Outbox.Transport.ContractsToImplement; 4 | using TransactionalBox.Kafka.Internals.Outbox; 5 | using TransactionalBox.Kafka.Internals.Outbox.ImplementedContracts; 6 | using TransactionalBox.Kafka.Settings.Outbox; 7 | 8 | namespace TransactionalBox 9 | { 10 | public static class OutboxExtensionUseKafka 11 | { 12 | public static void UseKafka( 13 | this IOutboxTransportConfigurator outboxWorkerTransportConfigurator, 14 | Action settingsConfiguration) 15 | { 16 | var services = outboxWorkerTransportConfigurator.Services; 17 | var settings = new OutboxKafkaSettings(); 18 | 19 | settingsConfiguration(settings); 20 | 21 | services.AddSingleton(settings); 22 | services.AddSingleton(settings.TransportMessageSizeSettings); 23 | 24 | services.AddSingleton(); 25 | services.AddScoped(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/TransactionalBox.Kafka/Internals/Inbox/IInboxKafkaSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Kafka.Internals.Inbox 2 | { 3 | internal interface IInboxKafkaSettings 4 | { 5 | string BootstrapServers { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox.Kafka/Internals/Outbox/IOutboxKafkaSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Kafka.Internals.Outbox 2 | { 3 | internal interface IOutboxKafkaSettings 4 | { 5 | string BootstrapServers { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox.Kafka/Internals/Outbox/KafkaConfigFactory.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | 3 | namespace TransactionalBox.Kafka.Internals.Outbox 4 | { 5 | internal sealed class KafkaConfigFactory 6 | { 7 | private readonly IOutboxKafkaSettings _outboxWorkerKafkaSettings; 8 | 9 | private ProducerConfig? _config = null; 10 | 11 | public KafkaConfigFactory( 12 | IOutboxKafkaSettings outboxWorkerKafkaSettings) 13 | { 14 | _outboxWorkerKafkaSettings = outboxWorkerKafkaSettings; 15 | } 16 | 17 | internal ProducerConfig Create() 18 | { 19 | if (_config is not null) 20 | { 21 | return _config; 22 | } 23 | 24 | _config = new ProducerConfig() 25 | { 26 | BootstrapServers = _outboxWorkerKafkaSettings.BootstrapServers, 27 | }; 28 | 29 | return _config; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/TransactionalBox.Kafka/Settings/Inbox/InboxKafkaSettings.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Kafka.Internals.Inbox; 2 | 3 | namespace TransactionalBox.Kafka.Settings.Inbox 4 | { 5 | public sealed class InboxKafkaSettings : IInboxKafkaSettings 6 | { 7 | public string BootstrapServers { get; set; } 8 | 9 | internal InboxKafkaSettings() { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox.Kafka/Settings/Outbox/KafkaTransportMessageSizeSettings.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Transport.ContractsToImplement; 2 | 3 | namespace TransactionalBox.Kafka.Settings.Outbox 4 | { 5 | public sealed class KafkaTransportMessageSizeSettings : ITransportMessageSizeSettings 6 | { 7 | public int OptimalTransportMessageSize { get; set; } = 10240; 8 | 9 | internal KafkaTransportMessageSizeSettings() { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox.Kafka/Settings/Outbox/OutboxKafkaSettings.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Kafka.Internals.Outbox; 2 | 3 | namespace TransactionalBox.Kafka.Settings.Outbox 4 | { 5 | public sealed class OutboxKafkaSettings : IOutboxKafkaSettings 6 | { 7 | public string BootstrapServers { get; set; } 8 | 9 | public KafkaTransportMessageSizeSettings TransportMessageSizeSettings { get; } = new KafkaTransportMessageSizeSettings(); 10 | 11 | internal OutboxKafkaSettings() { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/TransactionalBox.Kafka/TransactionalBox.Kafka.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /source/TransactionalBox/Builders/ITransactionalBoxBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace TransactionalBox.Builders 5 | { 6 | public interface ITransactionalBoxBuilder 7 | { 8 | internal IServiceCollection Services { get; } 9 | 10 | internal IConfiguration Configuration { get; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/TransactionalBox/Configurators/IAssemblyConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace TransactionalBox.Configurators 4 | { 5 | public interface IAssemblyConfigurator 6 | { 7 | void RegisterFromAssemblies(Assembly assembly); 8 | 9 | void RegisterFromAssemblies(params Assembly[] assemblies); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Configurators/Inbox/IInboxDeserializationConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Configurators.Inbox 4 | { 5 | public interface IInboxDeserializationConfigurator 6 | { 7 | internal IServiceCollection Services { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Configurators/Inbox/IInboxStorageConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Configurators.Inbox 4 | { 5 | public interface IInboxStorageConfigurator 6 | { 7 | internal IServiceCollection Services { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Configurators/Inbox/IInboxTransportConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Configurators.Inbox 4 | { 5 | public interface IInboxTransportConfigurator 6 | { 7 | internal IServiceCollection Services { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Configurators/Outbox/IOutboxCompressionConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Configurators.Outbox 4 | { 5 | public interface IOutboxCompressionConfigurator 6 | { 7 | internal IServiceCollection Services { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Configurators/Outbox/IOutboxSerializationConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Configurators.Outbox 4 | { 5 | public interface IOutboxSerializationConfigurator 6 | { 7 | internal IServiceCollection Services { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Configurators/Outbox/IOutboxStorageConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Configurators.Outbox 4 | { 5 | public interface IOutboxStorageConfigurator 6 | { 7 | internal IServiceCollection Services { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Configurators/Outbox/IOutboxTransportConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Configurators.Outbox 4 | { 5 | public interface IOutboxTransportConfigurator 6 | { 7 | internal IServiceCollection Services { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Envelope.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox 2 | { 3 | public sealed class Envelope 4 | { 5 | public string CorrelationId { get; set; } = Guid.NewGuid().ToString(); 6 | 7 | internal Envelope() { } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Extensions/Inbox/InboxExtensionUseSystemTextJson.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Inbox; 3 | using TransactionalBox.Internals.Inbox.Deserialization; 4 | 5 | namespace TransactionalBox 6 | { 7 | internal static class InboxExtensionUseSystemTextJson 8 | { 9 | internal static void UseSystemTextJson(this IInboxDeserializationConfigurator configurator) 10 | { 11 | var services = configurator.Services; 12 | 13 | services.AddSingleton(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/TransactionalBox/Extensions/Outbox/OutboxExtensionUseBrotli.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IO; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using TransactionalBox.Settings.Outbox.Compression; 4 | using TransactionalBox.Internals.Outbox.Compression; 5 | using TransactionalBox.Internals.Outbox.Compression.Brotli; 6 | using TransactionalBox.Configurators.Outbox; 7 | 8 | namespace TransactionalBox 9 | { 10 | public static class OutboxExtensionUseBrotli 11 | { 12 | public static void UseBrotli( 13 | this IOutboxCompressionConfigurator configurator, 14 | Action? settingsConfiguration = null) 15 | { 16 | var services = configurator.Services; 17 | 18 | var settings = new BrotliCompressionSettings(); 19 | 20 | if (settingsConfiguration is not null) 21 | { 22 | settingsConfiguration(settings); 23 | } 24 | 25 | services.AddSingleton(new RecyclableMemoryStreamManager()); 26 | 27 | services.AddSingleton(settings); 28 | 29 | services.AddSingleton(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/TransactionalBox/Extensions/Outbox/OutboxExtensionUseGZip.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.IO; 3 | using TransactionalBox.Settings.Outbox.Compression; 4 | using TransactionalBox.Internals.Outbox.Compression; 5 | using TransactionalBox.Internals.Outbox.Compression.GZip; 6 | using TransactionalBox.Configurators.Outbox; 7 | 8 | namespace TransactionalBox 9 | { 10 | public static class OutboxExtensionUseGZip 11 | { 12 | public static void UseGZip( 13 | this IOutboxCompressionConfigurator configurator, 14 | Action? settingsConfiguration = null) 15 | { 16 | var services = configurator.Services; 17 | 18 | var settings = new GZipCompressionSettings(); 19 | 20 | if (settingsConfiguration is not null) 21 | { 22 | settingsConfiguration(settings); 23 | } 24 | 25 | services.AddSingleton(new RecyclableMemoryStreamManager()); 26 | 27 | services.AddSingleton(settings); 28 | 29 | services.AddSingleton(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/TransactionalBox/Extensions/Outbox/OutboxExtensionUseNoCompression.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Outbox; 3 | using TransactionalBox.Internals.Outbox.Compression; 4 | using TransactionalBox.Internals.Outbox.Compression.NoCompression; 5 | 6 | namespace TransactionalBox 7 | { 8 | public static class OutboxExtensionUseNoCompression 9 | { 10 | public static void UseNoCompression(this IOutboxCompressionConfigurator configurator) 11 | { 12 | var services = configurator.Services; 13 | 14 | services.AddSingleton(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/TransactionalBox/Extensions/Outbox/OutboxExtensionUseSystemTextJson.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Internals.Outbox.Serialization; 3 | using TransactionalBox.Configurators.Outbox; 4 | 5 | namespace TransactionalBox 6 | { 7 | public static class OutboxExtensionUseSystemTextJson 8 | { 9 | public static void UseSystemTextJson(this IOutboxSerializationConfigurator configurator) 10 | { 11 | var services = configurator.Services; 12 | 13 | services.AddSingleton(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/TransactionalBox/IExecutionContext.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox 2 | { 3 | public interface IExecutionContext 4 | { 5 | string Source { get; } 6 | 7 | DateTime OccurredUtc { get; } 8 | 9 | string CorrelationId { get; } 10 | 11 | CancellationToken CancellationToken { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/TransactionalBox/IInboxHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox 2 | { 3 | public interface IInboxHandler 4 | where TInboxMessage : InboxMessage, new() 5 | { 6 | /// 7 | /// Handle a message from inbox. 8 | /// 9 | Task Handle(TInboxMessage message, IExecutionContext executionContext); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/IOutbox.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox 2 | { 3 | public interface IOutbox 4 | { 5 | /// 6 | /// Add a message to the outbox with the transaction. 7 | /// 8 | Task Add(TOutboxMessage message, Action? envelopeConfiguration = null) 9 | where TOutboxMessage : OutboxMessage; 10 | 11 | Task TransactionCommited(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/TransactionalBox/InboxDefinition.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Inbox.InboxDefinitions; 2 | 3 | namespace TransactionalBox 4 | { 5 | /// 6 | /// Define the inbox message. 7 | /// 8 | public abstract class InboxDefinition : IInboxDefinition 9 | where TInboxMessage : InboxMessage, new() 10 | { 11 | protected internal string? PublishedBy { get; protected set; } = null; 12 | 13 | string? IInboxDefinition.PublishedBy => PublishedBy; 14 | 15 | //TODO RetryConfiguration (poisoned message) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/TransactionalBox/InboxMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox 2 | { 3 | /// 4 | /// Base class of inbox messagese. 5 | /// 6 | public abstract class InboxMessage; 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/IServiceContext.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals 2 | { 3 | internal interface IServiceContext 4 | { 5 | string Id { get; } 6 | 7 | string InstanceId { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/ISystemClock.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals 2 | { 3 | internal interface ISystemClock 4 | { 5 | DateTime UtcNow { get; } 6 | 7 | TimeProvider TimeProvider { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/ITopicFactory.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals 2 | { 3 | internal interface ITopicFactory 4 | { 5 | string Create(string serviceName, string messageName); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Assemblies/CompiledHandlers/ICompiledInboxHandlers.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Assemblies.CompiledHandlers 2 | { 3 | internal interface ICompiledInboxHandlers 4 | { 5 | Func? GetCompiledInboxHandler(Type messageType); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Assemblies/MessageTypes/IInboxMessageTypes.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Assemblies.MessageTypes 2 | { 3 | internal interface IInboxMessageTypes 4 | { 5 | IReadOnlyDictionary DictionaryMessageTypes { get; } 6 | 7 | IEnumerable MessageTypes { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Assemblies/MessageTypes/InboxMessageTypes.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Assemblies.MessageTypes 2 | { 3 | internal sealed class InboxMessageTypes : IInboxMessageTypes 4 | { 5 | private readonly Dictionary _dictionaryMessageTypes = new Dictionary(); 6 | 7 | private readonly HashSet _messageTypes = new HashSet(); 8 | 9 | public IReadOnlyDictionary DictionaryMessageTypes => _dictionaryMessageTypes; 10 | 11 | public IEnumerable MessageTypes => _messageTypes; 12 | 13 | internal InboxMessageTypes(IEnumerable inboxMessageHandlerTypes, Type handlerGenericType) 14 | { 15 | foreach (Type type in inboxMessageHandlerTypes) 16 | { 17 | var handlerTypes = type 18 | .GetInterfaces() 19 | .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == handlerGenericType); 20 | 21 | foreach (Type handlerType in handlerTypes) 22 | { 23 | var messageType = handlerType.GetGenericArguments()[0]; 24 | 25 | _dictionaryMessageTypes.Add(messageType.Name, messageType); 26 | _messageTypes.Add(messageType); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/BackgroundProcesses/AddMessagesToInbox/IAddMessagesToInboxSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.BackgroundProcesses.AddMessagesToInbox 2 | { 3 | internal interface IAddMessagesToInboxSettings 4 | { 5 | TimeSpan DefaultTimeToLiveIdempotencyKey { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/BackgroundProcesses/AddMessagesToInbox/Logger/AddMessagesToInboxLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace TransactionalBox.Internals.Inbox.BackgroundProcesses.AddMessagesToInbox.Logger 4 | { 5 | internal sealed partial class AddMessagesToInboxLogger : IAddMessagesToInboxLogger 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public AddMessagesToInboxLogger(ILogger logger) => _logger = logger; 10 | 11 | [LoggerMessage(0, LogLevel.Error, "{name} (Attempt: {attempt} Delay: {msDelay}ms) unexpected exception", SkipEnabledCheck = true)] 12 | public partial void UnexpectedException(string name, long attempt, long msDelay, Exception exception); 13 | 14 | [LoggerMessage(0, LogLevel.Warning, "Detected duplicated messages with ids '{ids}'", SkipEnabledCheck = true)] 15 | public partial void DetectedDuplicatedMessages(string ids); 16 | 17 | [LoggerMessage(0, LogLevel.Information, "AddMessagesToInbox (NumberOfMessagesAdded: {numberOfMessages})", SkipEnabledCheck = true)] 18 | public partial void AddedMessagesToInbox(int numberOfMessages); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/BackgroundProcesses/AddMessagesToInbox/Logger/IAddMessagesToInboxLogger.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Inbox.BackgroundProcesses.Base.Logger; 2 | 3 | namespace TransactionalBox.Internals.Inbox.BackgroundProcesses.AddMessagesToInbox.Logger 4 | { 5 | internal interface IAddMessagesToInboxLogger : IBackgroundProcessBaseLogger 6 | { 7 | void AddedMessagesToInbox(int numberOfMessages); 8 | 9 | void DetectedDuplicatedMessages(string ids); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/BackgroundProcesses/Base/Logger/IBackgroundProcessBaseLogger.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.BackgroundProcesses.Base.Logger 2 | { 3 | internal interface IBackgroundProcessBaseLogger 4 | { 5 | void UnexpectedException(string name, long attempt, long msDelay, Exception exception); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/BackgroundProcesses/CleanUpIdempotencyKeys/ICleanUpIdempotencyKeysSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.BackgroundProcesses.CleanUpIdempotencyKeys 2 | { 3 | internal interface ICleanUpIdempotencyKeysSettings 4 | { 5 | int MaxBatchSize { get; } 6 | 7 | TimeSpan Period { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/BackgroundProcesses/CleanUpIdempotencyKeys/Logger/CleanUpIdempotencyKeysLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace TransactionalBox.Internals.Inbox.BackgroundProcesses.CleanUpIdempotencyKeys.Logger 4 | { 5 | internal sealed partial class CleanUpIdempotencyKeysLogger : ICleanUpIdempotencyKeysLogger 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public CleanUpIdempotencyKeysLogger(ILogger logger) => _logger = logger; 10 | 11 | [LoggerMessage(0, LogLevel.Information, "{name} '{id}' (Iteration: {iteration} NumberOfMessages: {numberOfMessages})", SkipEnabledCheck = true)] 12 | public partial void CleanedUp(string name, Guid id, long iteration, int numberOfMessages); 13 | 14 | [LoggerMessage(0, LogLevel.Error, "{name} (Attempt: {attempt} Delay: {msDelay}ms) unexpected exception", SkipEnabledCheck = true)] 15 | public partial void UnexpectedException(string name, long attempt, long msDelay, Exception exception); 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/BackgroundProcesses/CleanUpIdempotencyKeys/Logger/ICleanUpIdempotencyKeysLogger.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Inbox.BackgroundProcesses.Base.Logger; 2 | 3 | namespace TransactionalBox.Internals.Inbox.BackgroundProcesses.CleanUpIdempotencyKeys.Logger 4 | { 5 | internal interface ICleanUpIdempotencyKeysLogger : IBackgroundProcessBaseLogger 6 | { 7 | void CleanedUp(string name, Guid id, long iteration, int numberOfMessages); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Configurators/InboxDeserializationConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Inbox; 3 | 4 | namespace TransactionalBox.Internals.Inbox.Configurators 5 | { 6 | internal sealed class InboxDeserializationConfigurator : IInboxDeserializationConfigurator 7 | { 8 | public IServiceCollection Services { get; } 9 | 10 | public InboxDeserializationConfigurator(IServiceCollection services) 11 | { 12 | Services = services; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Configurators/InboxStorageConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Inbox; 3 | 4 | namespace TransactionalBox.Internals.Inbox.Configurators 5 | { 6 | internal sealed class InboxStorageConfigurator : IInboxStorageConfigurator 7 | { 8 | public IServiceCollection Services { get; } 9 | 10 | internal InboxStorageConfigurator(IServiceCollection services) 11 | { 12 | Services = services; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Configurators/InboxTransportConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Inbox; 3 | 4 | namespace TransactionalBox.Internals.Inbox.Configurators 5 | { 6 | internal sealed class InboxTransportConfigurator : IInboxTransportConfigurator 7 | { 8 | public IServiceCollection Services { get; } 9 | 10 | internal InboxTransportConfigurator(IServiceCollection services) 11 | { 12 | Services = services; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Contexts/ExecutionContext.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Inbox.Deserialization; 2 | 3 | namespace TransactionalBox.Internals.Inbox.Contexts 4 | { 5 | internal sealed class ExecutionContext : IExecutionContext 6 | { 7 | public string Source { get; } 8 | 9 | public DateTime OccurredUtc { get; } 10 | 11 | public string CorrelationId { get; } 12 | 13 | public CancellationToken CancellationToken { get; } 14 | 15 | public ExecutionContext(Metadata metadata, CancellationToken cancellationToken) 16 | { 17 | Source = metadata.Source; 18 | OccurredUtc = metadata.OccurredUtc; 19 | CorrelationId = metadata.CorrelationId; 20 | CancellationToken = cancellationToken; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Decompression/BrotliDecompression.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IO; 2 | using System.IO.Compression; 3 | 4 | namespace TransactionalBox.Internals.Inbox.Decompression 5 | { 6 | internal sealed class BrotliDecompression : IDecompression 7 | { 8 | public string Name { get; } = "brotli"; 9 | 10 | private readonly RecyclableMemoryStreamManager _streamManager; 11 | 12 | public BrotliDecompression( 13 | RecyclableMemoryStreamManager streamManager) 14 | { 15 | _streamManager = streamManager; 16 | } 17 | 18 | public async Task Decompress(byte[] data) 19 | { 20 | using (var memoryStreamInput = _streamManager.GetStream(data)) 21 | using (var memoryStreamOutput = _streamManager.GetStream()) 22 | using (var brotliStream = new BrotliStream(memoryStreamInput, CompressionMode.Decompress)) 23 | { 24 | await brotliStream.CopyToAsync(memoryStreamOutput).ConfigureAwait(false); 25 | 26 | await brotliStream.FlushAsync().ConfigureAwait(false); 27 | 28 | return memoryStreamOutput.ToArray(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Decompression/DecompressionFactory.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Decompression 2 | { 3 | internal sealed class DecompressionFactory : IDecompressionFactory 4 | { 5 | private readonly IEnumerable _decompressions; 6 | 7 | public DecompressionFactory(IEnumerable decompressions) 8 | { 9 | _decompressions = decompressions; 10 | } 11 | 12 | public IDecompression GetDecompression(string compressionName) 13 | { 14 | //TODO own exception 15 | var decompression = _decompressions.Single(x => x.Name == compressionName); 16 | 17 | return decompression; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Decompression/GZipDecompression.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IO; 2 | using System.IO.Compression; 3 | 4 | namespace TransactionalBox.Internals.Inbox.Decompression 5 | { 6 | internal sealed class GZipDecompression : IDecompression 7 | { 8 | public string Name { get; } = "gzip"; 9 | 10 | private readonly RecyclableMemoryStreamManager _streamManager; 11 | 12 | public GZipDecompression(RecyclableMemoryStreamManager streamManager) 13 | { 14 | _streamManager = streamManager; 15 | } 16 | 17 | public async Task Decompress(byte[] data) 18 | { 19 | using (var memoryStreamInput = _streamManager.GetStream(data)) 20 | using (var memoryStreamOutput = _streamManager.GetStream()) 21 | using (var gZipStream = new GZipStream(memoryStreamInput, CompressionMode.Decompress)) 22 | { 23 | await gZipStream.CopyToAsync(memoryStreamOutput).ConfigureAwait(false); 24 | 25 | await gZipStream.FlushAsync().ConfigureAwait(false); 26 | 27 | return memoryStreamOutput.ToArray(); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Decompression/IDecompression.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Decompression 2 | { 3 | internal interface IDecompression 4 | { 5 | string Name { get; } 6 | 7 | Task Decompress(byte[] data); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Decompression/IDecompressionFactory.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Decompression 2 | { 3 | internal interface IDecompressionFactory 4 | { 5 | IDecompression GetDecompression(string compressionName); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Decompression/NoDecompression.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Decompression 2 | { 3 | internal sealed class NoDecompression : IDecompression 4 | { 5 | public string Name { get; } = "no_compression"; 6 | 7 | public Task Decompress(byte[] data) => Task.FromResult(data); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Deserialization/IInboxDeserializer.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Deserialization 2 | { 3 | internal interface IInboxDeserializer 4 | { 5 | InboxMessage DeserializeMessage(string message, Type type); 6 | 7 | Metadata DeserializeMetadata(string metadata); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Deserialization/InboxDeserializer.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace TransactionalBox.Internals.Inbox.Deserialization 4 | { 5 | internal sealed class InboxDeserializer : IInboxDeserializer 6 | { 7 | public InboxMessage DeserializeMessage(string message, Type type) 8 | { 9 | return JsonSerializer.Deserialize(message, type) as InboxMessage; 10 | } 11 | 12 | public Metadata DeserializeMetadata(string metadata) 13 | { 14 | return JsonSerializer.Deserialize(metadata); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Deserialization/Metadata.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Deserialization 2 | { 3 | internal sealed class Metadata 4 | { 5 | public string Source { get; init; } 6 | 7 | public DateTime OccurredUtc { get; init; } 8 | 9 | public string CorrelationId { get; init; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Extensions/InboxExtensionUseInMemoryStorage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Internals.Inbox.Storage.ContractsToImplement; 3 | using TransactionalBox.Internals.Inbox.Storage.InMemory; 4 | using TransactionalBox.Configurators.Inbox; 5 | using TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock; 6 | 7 | namespace TransactionalBox.Internals.Inbox.Extensions 8 | { 9 | internal static class InboxExtensionUseInMemoryStorage 10 | { 11 | internal static void UseInMemoryStorage(this IInboxStorageConfigurator configurator) 12 | { 13 | var services = configurator.Services; 14 | 15 | services.AddKeyedInMemoryLock(); 16 | services.AddSingleton(); 17 | services.AddSingleton(); 18 | services.AddSingleton(); 19 | services.AddSingleton(); 20 | services.AddSingleton(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Extensions/InboxExtensionUseInMemoryTransport.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Internals.Inbox.Transport.ContractsToImplement; 3 | using TransactionalBox.Internals.Inbox.Transport.InMemory; 4 | using TransactionalBox.Configurators.Inbox; 5 | using TransactionalBox.Internals.InternalPackages.Transport; 6 | 7 | namespace TransactionalBox.Internals.Inbox.Extensions 8 | { 9 | internal static class InboxExtensionUseInMemoryTransport 10 | { 11 | internal static void UseInMemoryTransport(this IInboxTransportConfigurator configurator) 12 | { 13 | var services = configurator.Services; 14 | 15 | services.UseInternalInMemoryTransport(); 16 | services.AddSingleton(); 17 | services.AddSingleton(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Hooks/Events/AddedMessagesToInbox.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.EventHooks; 2 | 3 | namespace TransactionalBox.Internals.Inbox.Hooks.Events 4 | { 5 | internal sealed class AddedMessagesToInbox : EventHook; 6 | } 7 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Hooks/Events/ProcessedMessageFromInbox.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.EventHooks; 2 | 3 | namespace TransactionalBox.Internals.Inbox.Hooks.Events 4 | { 5 | internal sealed class ProcessedMessageFromInbox : EventHook; 6 | } 7 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Hooks/Handlers/CleanUpInbox/ICleanUpInboxSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Hooks.Handlers.CleanUpInbox 2 | { 3 | internal interface ICleanUpInboxSettings 4 | { 5 | int MaxBatchSize { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Hooks/Handlers/CleanUpInbox/Logger/CleanUpInboxLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace TransactionalBox.Internals.Inbox.Hooks.Handlers.CleanUpInbox.Logger 4 | { 5 | internal sealed partial class CleanUpInboxLogger : ICleanUpInboxLogger 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public CleanUpInboxLogger(ILogger logger) => _logger = logger; 10 | 11 | 12 | [LoggerMessage(0, LogLevel.Information, "{eventHookHandlerName} '{hookId}' (Iteration: {iteration} NumberOfMessages: {numberOfMessages})", SkipEnabledCheck = true)] 13 | public partial void CleanedUp(string eventHookHandlerName, Guid hookId, long iteration, int numberOfMessages); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Hooks/Handlers/CleanUpInbox/Logger/ICleanUpInboxLogger.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Hooks.Handlers.CleanUpInbox.Logger 2 | { 3 | internal interface ICleanUpInboxLogger 4 | { 5 | void CleanedUp(string eventHookHandlerName, Guid hookId, long iteration, int numberOfMessages); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Hooks/Handlers/ProcessMessage/Logger/IProcessMessageLogger.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Hooks.Handlers.ProcessMessage.Logger 2 | { 3 | internal interface IProcessMessageLogger 4 | { 5 | void Processed(string eventHookHandlerName, Guid hookId, Guid messageId); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Hooks/Handlers/ProcessMessage/Logger/ProcessMessageLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace TransactionalBox.Internals.Inbox.Hooks.Handlers.ProcessMessage.Logger 4 | { 5 | internal sealed partial class ProcessMessageLogger : IProcessMessageLogger 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public ProcessMessageLogger(ILogger logger) => _logger = logger; 10 | 11 | [LoggerMessage(0, LogLevel.Information, "{eventHookHandlerName} '{hookId}' processed message with id '{messageId}'", SkipEnabledCheck = true)] 12 | public partial void Processed(string eventHookHandlerName, Guid hookId, Guid messageId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/InboxDefinitions/DefaultInboxDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.InboxDefinitions 2 | { 3 | internal sealed class DefaultInboxDefinition : IInboxDefinition 4 | { 5 | public string? PublishedBy { get; } = null; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/InboxDefinitions/IInboxDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.InboxDefinitions 2 | { 3 | internal interface IInboxDefinition 4 | { 5 | string? PublishedBy { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/AddRangeToInboxStorageResult.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Storage 2 | { 3 | internal enum AddRangeToInboxStorageResult 4 | { 5 | Success, 6 | Failure 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/ContractsToImplement/IAddMessagesToInboxRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Storage.ContractsToImplement 2 | { 3 | internal interface IAddMessagesToInboxRepository 4 | { 5 | Task> GetExistIdempotentInboxKeysBasedOn(IEnumerable messages); 6 | 7 | Task AddRange(IEnumerable messages, IEnumerable idempotentInboxKeys); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/ContractsToImplement/ICleanUpIdempotencyKeysRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Storage.ContractsToImplement 2 | { 3 | internal interface ICleanUpIdempotencyKeysRepository 4 | { 5 | Task RemoveExpiredIdempotencyKeys(int batchSize, DateTime nowUtc); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/ContractsToImplement/ICleanUpInboxRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Storage.ContractsToImplement 2 | { 3 | internal interface ICleanUpInboxRepository 4 | { 5 | Task RemoveProcessedMessages(int batchSize); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/ContractsToImplement/IProcessMessageRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Storage.ContractsToImplement 2 | { 3 | internal interface IProcessMessageRepository 4 | { 5 | Task GetMessage(Guid hookId, string hookName, TimeProvider timeProvider, TimeSpan lockTimeout); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/DuplicatedInboxKey.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Storage 2 | { 3 | internal sealed record DuplicatedInboxKey 4 | { 5 | internal Guid Id { get; } 6 | 7 | internal DuplicatedInboxKey(IdempotentInboxKey inboxMessage) 8 | { 9 | Id = inboxMessage.Id; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/IdempotentInboxKey.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Storage 2 | { 3 | internal sealed class IdempotentInboxKey 4 | { 5 | public Guid Id { get; } 6 | 7 | public DateTime ExpirationUtc { get; } 8 | 9 | private IdempotentInboxKey() { } 10 | 11 | public IdempotentInboxKey(Guid id, TimeSpan timeToLive, TimeProvider timeProvider) 12 | { 13 | var nowUtc = timeProvider.GetUtcNow().UtcDateTime; 14 | 15 | Id = id; 16 | ExpirationUtc = nowUtc + timeToLive; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/InMemory/IInboxStorageReadOnly.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Storage.InMemory 2 | { 3 | internal interface IInboxStorageReadOnly 4 | { 5 | IReadOnlyCollection InboxMessages { get; } 6 | 7 | IReadOnlyCollection IdempotentInboxKeys { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/InboxDistributedLock.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.DistributedLock; 2 | 3 | namespace TransactionalBox.Internals.Inbox.Storage 4 | { 5 | internal sealed class InboxDistributedLock : Lock; 6 | } 7 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Storage/InboxMessageStorage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Storage 2 | { 3 | internal sealed class InboxMessageStorage 4 | { 5 | public required Guid Id { get; set; } 6 | 7 | public required DateTime OccurredUtc { get; set; } 8 | 9 | public bool IsProcessed { get; set; } 10 | 11 | public required string Topic { get; set; } 12 | 13 | public required string Payload { get; set; } 14 | 15 | public DateTime? LockUtc { get; set; } 16 | 17 | public string? JobId { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Transport/ContractsToImplement/IInboxTransport.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Transport.ContractsToImplement 2 | { 3 | internal interface IInboxTransport 4 | { 5 | IAsyncEnumerable GetMessages(IEnumerable topics, CancellationToken cancellationToken); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Transport/ContractsToImplement/ITransportTopicsCreator.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Transport.ContractsToImplement 2 | { 3 | internal interface ITransportTopicsCreator 4 | { 5 | Task Create(IEnumerable topics); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Transport/InMemory/InMemoryTransportTopicsCreator.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Inbox.Transport.ContractsToImplement; 2 | 3 | namespace TransactionalBox.Internals.Inbox.Transport.InMemory 4 | { 5 | internal sealed class InMemoryTransportTopicsCreator : ITransportTopicsCreator 6 | { 7 | public Task Create(IEnumerable topics) => Task.CompletedTask; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Transport/Topics/ITopicsProvider.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Transport.Topics 2 | { 3 | internal interface ITopicsProvider 4 | { 5 | IEnumerable Topics { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Inbox/Transport/TransportMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Inbox.Transport 2 | { 3 | internal sealed class TransportMessage 4 | { 5 | internal required byte[] Payload { get; init; } 6 | 7 | internal required string Compression { get; init; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/AssemblyConfigurator/AssemblyConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using TransactionalBox.Configurators; 3 | 4 | namespace TransactionalBox.Internals.InternalPackages.AssemblyConfigurator 5 | { 6 | internal sealed class AssemblyConfigurator : IAssemblyConfigurator 7 | { 8 | private readonly ISet _assemblies = new HashSet(); 9 | 10 | internal IEnumerable Assemblies 11 | { 12 | get 13 | { 14 | if (_assemblies.Any()) 15 | { 16 | return _assemblies; 17 | } 18 | 19 | var assemblies = AppDomain.CurrentDomain.GetAssemblies(); 20 | 21 | foreach (var assembly in assemblies) 22 | { 23 | _assemblies.Add(assembly); 24 | } 25 | 26 | return _assemblies; 27 | } 28 | } 29 | 30 | public void RegisterFromAssemblies(Assembly assembly) 31 | { 32 | _assemblies.Add(assembly); 33 | } 34 | 35 | public void RegisterFromAssemblies(params Assembly[] assemblies) 36 | { 37 | foreach (Assembly assembly in assemblies) 38 | { 39 | _assemblies.Add(assembly); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/DistributedLock/DistributedLockStorageConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Internals.InternalPackages.DistributedLock 4 | { 5 | internal sealed class DistributedLockStorageConfigurator : IDistributedLockStorageConfigurator 6 | { 7 | public IServiceCollection Services { get; } 8 | 9 | public DistributedLockStorageConfigurator(IServiceCollection services) 10 | { 11 | Services = services; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/DistributedLock/ExtensionAddDistributedLock.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock; 3 | 4 | namespace TransactionalBox.Internals.InternalPackages.DistributedLock 5 | { 6 | internal static class ExtensionAddDistributedLock 7 | { 8 | internal static void AddDistributedLock( 9 | this IServiceCollection services, 10 | Action storageConfiguration) 11 | where T : Lock, new() 12 | { 13 | var storage = new DistributedLockStorageConfigurator(services); 14 | 15 | storageConfiguration(storage); 16 | 17 | services.AddKeyedInMemoryLock(); 18 | services.AddSingleton, InternalDistributedLock>(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/DistributedLock/IDistributedLock.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.DistributedLock 2 | { 3 | internal interface IDistributedLock 4 | where T : Lock, new() 5 | { 6 | Task Acquire(string key, TimeProvider timeProvider, TimeSpan lockTimeout, TimeSpan checkingIntervalWhenLockIsNotReleased, CancellationToken cancellationToken = default); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/DistributedLock/IDistributedLockInstance.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.DistributedLock 2 | { 3 | internal interface IDistributedLockInstance : IAsyncDisposable; 4 | } 5 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/DistributedLock/IDistributedLockStorage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.DistributedLock 2 | { 3 | internal interface IDistributedLockStorage 4 | { 5 | Task AddFirstLock(T @lock) where T : Lock, new(); 6 | 7 | Task TryAcquire(string key, DateTime nowUtc, DateTime newExpirationUtc) where T : Lock, new(); 8 | 9 | Task Release(string key, DateTime nowUtc, DateTime expirationUtc) where T : Lock, new(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/DistributedLock/IDistributedLockStorageConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Internals.InternalPackages.DistributedLock 4 | { 5 | internal interface IDistributedLockStorageConfigurator 6 | { 7 | internal IServiceCollection Services { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/DistributedLock/Lock.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.DistributedLock 2 | { 3 | internal abstract class Lock 4 | { 5 | public string Key { get; init; } 6 | 7 | public DateTime ExpirationUtc { get; init; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/EventHook.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 2 | { 3 | internal abstract class EventHook; 4 | } 5 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/EventHookHub.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Channels; 2 | 3 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 4 | { 5 | internal sealed class EventHookHub where TEventHook : EventHook, new() 6 | { 7 | private static Channel _channel = Channel.CreateBounded(new BoundedChannelOptions(1) 8 | { 9 | FullMode = BoundedChannelFullMode.DropOldest, 10 | SingleReader = true, 11 | SingleWriter = false, 12 | AllowSynchronousContinuations = false, 13 | }); 14 | 15 | private ChannelWriter _writer => _channel.Writer; 16 | 17 | private ChannelReader _reader => _channel.Reader; 18 | 19 | private readonly TimeProvider _timeProvider; 20 | 21 | public EventHookHub(TimeProvider timeProvider) => _timeProvider = timeProvider; 22 | 23 | public ValueTask PublishAsync() => _writer.WriteAsync(_timeProvider.GetUtcNow().UtcDateTime); 24 | 25 | public IAsyncEnumerable ListenAsync(CancellationToken cancellationToken) => _reader.ReadAllAsync(cancellationToken); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/EventHookPublisher.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 4 | { 5 | internal sealed class EventHookPublisher : IEventHookPublisher 6 | { 7 | private readonly IServiceProvider _serviceProvider; 8 | 9 | public EventHookPublisher(IServiceProvider serviceProvider) 10 | { 11 | _serviceProvider = serviceProvider; 12 | } 13 | 14 | public async Task PublishAsync() 15 | where TEventHook : EventHook, new() 16 | { 17 | var eventHookHub = _serviceProvider.GetService>(); 18 | 19 | if (eventHookHub is not null) 20 | { 21 | await eventHookHub.PublishAsync().AsTask().ConfigureAwait(false); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/EventHooksStartup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | 3 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 4 | { 5 | internal sealed class EventHooksStartup : BackgroundService 6 | { 7 | private readonly IEnumerable _hookListenersLauncher; 8 | 9 | public EventHooksStartup( 10 | IEnumerable hookListenersLauncher) 11 | { 12 | _hookListenersLauncher = hookListenersLauncher; 13 | } 14 | 15 | protected override Task ExecuteAsync(CancellationToken stoppingToken) 16 | { 17 | foreach (var hookListenersLauncher in _hookListenersLauncher) 18 | { 19 | hookListenersLauncher.LaunchAsync(stoppingToken); 20 | } 21 | 22 | return Task.CompletedTask; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/ExtensionAddEventHookHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Internals.EventHooks.Internals.Loggers; 3 | 4 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 5 | { 6 | internal static class ExtensionAddEventHookHandler 7 | { 8 | internal static void AddEventHookHandler(this IServiceCollection services) 9 | where THookListener : class, IEventHookHandler 10 | where THook : EventHook, new() 11 | { 12 | services.AddSingleton>(); 13 | 14 | services.AddSingleton>(); 15 | 16 | services.AddSingleton(); 17 | 18 | services.AddScoped, THookListener>(); 19 | 20 | services.AddSingleton(typeof(IHookListnerLogger), typeof(HookListnerLogger)); 21 | 22 | services.AddHostedService(); 23 | } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/HookExecutionContext.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 2 | { 3 | internal sealed record HookExecutionContext(Guid Id, string Name, DateTime LastOccurredUtc, bool IsError, long Attempt) : IHookExecutionContext; 4 | } 5 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/IEventHookHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 2 | { 3 | internal interface IEventHookHandler 4 | where T : EventHook, new() 5 | { 6 | Task HandleAsync(IHookExecutionContext context, CancellationToken cancellationToken); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/IEventHookPublisher.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 2 | { 3 | internal interface IEventHookPublisher 4 | { 5 | Task PublishAsync() where TEventHook : EventHook, new(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/IHookExecutionContext.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 2 | { 3 | internal interface IHookExecutionContext 4 | { 5 | Guid Id { get; } 6 | 7 | string Name { get; } 8 | 9 | DateTime LastOccurredUtc { get; } 10 | 11 | bool IsError { get; } 12 | 13 | long Attempt { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/IHookListnerLogger.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 2 | { 3 | internal interface IHookListnerLogger 4 | where THook : EventHook, new() 5 | { 6 | void Started(string eventHookHandlerName, Guid hookId); 7 | 8 | void Ended(string eventHookHandlerName, Guid hookId); 9 | 10 | void UnexpectedException(string eventHookHandlerName, Guid hookId, long attempt, long msDelay, Exception exception); 11 | 12 | void UnexpectedException(Exception exception); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/EventHooks/IInternalHookListenersLauncher.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.EventHooks 2 | { 3 | internal interface IInternalHookListenersLauncher 4 | { 5 | Task LaunchAsync(CancellationToken cancellationToken); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/KeyedInMemoryLock/ExtensionAddKeyedInMemoryLock.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | 4 | namespace TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock 5 | { 6 | internal static class ExtensionAddKeyedInMemoryLock 7 | { 8 | internal static IServiceCollection AddKeyedInMemoryLock(this IServiceCollection services) 9 | { 10 | services.AddSingleton(); 11 | 12 | return services; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/KeyedInMemoryLock/IKeyedInMemoryLock.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock 2 | { 3 | internal interface IKeyedInMemoryLock 4 | { 5 | Task Acquire(string key, CancellationToken cancellationToken = default); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/KeyedInMemoryLock/ILockInstance.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock 2 | { 3 | internal interface ILockInstance : IDisposable; 4 | } 5 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/KeyedInMemoryLock/InternalKeyedInMemoryLock.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | 3 | namespace TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock 4 | { 5 | internal sealed class InternalKeyedInMemoryLock : IKeyedInMemoryLock 6 | { 7 | private static ConcurrentDictionary _locks = new ConcurrentDictionary(); 8 | 9 | public async Task Acquire(string key, CancellationToken cancellationToken = default) 10 | { 11 | var @lock = _locks.GetOrAdd(key, x => new SemaphoreSlim(1, 1)); 12 | 13 | await @lock.WaitAsync(cancellationToken); 14 | 15 | return new LockInstance(@lock); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/KeyedInMemoryLock/LockInstance.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock 2 | { 3 | internal sealed class LockInstance : ILockInstance 4 | { 5 | private readonly SemaphoreSlim _semaphoreSlim; 6 | 7 | internal LockInstance(SemaphoreSlim semaphoreSlim) => _semaphoreSlim = semaphoreSlim; 8 | 9 | public void Dispose() => _semaphoreSlim.Release(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/SequentialGuid/ISequentialGuidGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.SequentialGuid 2 | { 3 | internal interface ISequentialGuidGenerator 4 | { 5 | Guid Create(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/SequentialGuid/SequentialGuidType.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.SequentialGuid 2 | { 3 | public enum SequentialGuidType 4 | { 5 | SequentialAsString, 6 | SequentialAsBinary, 7 | SequentialAtEnd 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/Transport/ExtensionUseInternalInMemoryTransport.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace TransactionalBox.Internals.InternalPackages.Transport 4 | { 5 | internal static class ExtensionUseInternalInMemoryTransport 6 | { 7 | internal static void UseInternalInMemoryTransport(this IServiceCollection services) 8 | { 9 | services.AddSingleton(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/Transport/IInMemoryTransport.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Channels; 2 | 3 | namespace TransactionalBox.Internals.InternalPackages.Transport 4 | { 5 | internal interface IInMemoryTransport 6 | { 7 | ChannelWriter Writer { get; } 8 | 9 | ChannelReader Reader { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/Transport/InternalTransport.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Channels; 2 | 3 | namespace TransactionalBox.Internals.InternalPackages.Transport 4 | { 5 | internal sealed class InternalTransport : IInMemoryTransport 6 | { 7 | private static readonly Channel _channel = Channel.CreateUnbounded(); 8 | 9 | public ChannelWriter Writer { get; } = _channel.Writer; 10 | 11 | public ChannelReader Reader { get; } = _channel.Reader; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/InternalPackages/Transport/TransportObject.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.InternalPackages.Transport 2 | { 3 | internal sealed class TransportObject 4 | { 5 | internal string Topic { get; init; } 6 | 7 | internal byte[] Payload { get; init; } 8 | 9 | internal string Compression { get; init; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Compression/Brotli/BrotliCompression.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IO; 2 | using System.IO.Compression; 3 | 4 | namespace TransactionalBox.Internals.Outbox.Compression.Brotli 5 | { 6 | internal sealed class BrotliCompression : ICompression 7 | { 8 | public string Name { get; } = "brotli"; 9 | 10 | private readonly IBrotliCompressionSettings _settings; 11 | 12 | private readonly RecyclableMemoryStreamManager _streamManager; 13 | 14 | public BrotliCompression( 15 | IBrotliCompressionSettings settings, 16 | RecyclableMemoryStreamManager streamManager) 17 | { 18 | _settings = settings; 19 | _streamManager = streamManager; 20 | } 21 | 22 | public async Task Compress(byte[] data) 23 | { 24 | using (var memoryStreamOutput = _streamManager.GetStream()) 25 | using (var brotliStream = new BrotliStream(memoryStreamOutput, _settings.CompressionLevel)) 26 | { 27 | await brotliStream.WriteAsync(data, 0, data.Length); 28 | await brotliStream.FlushAsync(); 29 | 30 | return memoryStreamOutput.ToArray(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Compression/Brotli/IBrotliCompressionSettings.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Compression.Brotli 4 | { 5 | internal interface IBrotliCompressionSettings 6 | { 7 | CompressionLevel CompressionLevel { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Compression/GZip/GZipCompression.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IO; 2 | using System.IO.Compression; 3 | 4 | namespace TransactionalBox.Internals.Outbox.Compression.GZip 5 | { 6 | internal sealed class GZipCompression : ICompression 7 | { 8 | public string Name { get; } = "gzip"; 9 | 10 | private readonly IGZipCompressionSettings _settings; 11 | 12 | private readonly RecyclableMemoryStreamManager _streamManager; 13 | 14 | public GZipCompression( 15 | IGZipCompressionSettings settings, 16 | RecyclableMemoryStreamManager streamManager) 17 | { 18 | _settings = settings; 19 | _streamManager = streamManager; 20 | } 21 | 22 | public async Task Compress(byte[] data) 23 | { 24 | using (var memoryStreamOutput = _streamManager.GetStream()) 25 | using (var gZipStream = new GZipStream(memoryStreamOutput, _settings.CompressionLevel)) 26 | { 27 | await gZipStream.WriteAsync(data, 0, data.Length); 28 | await gZipStream.FlushAsync(); 29 | 30 | return memoryStreamOutput.ToArray(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Compression/GZip/IGZipCompressionSettings.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Compression.GZip 4 | { 5 | internal interface IGZipCompressionSettings 6 | { 7 | CompressionLevel CompressionLevel { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Compression/ICompression.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Compression 2 | { 3 | internal interface ICompression 4 | { 5 | string Name { get; } 6 | 7 | Task Compress(byte[] data); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Compression/NoCompression/NoCompression.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Compression.NoCompression 2 | { 3 | internal sealed class NoCompression : ICompression 4 | { 5 | public string Name { get; } = "no_compression"; 6 | 7 | public Task Compress(byte[] data) => Task.FromResult(data); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Configurators/OutboxCompressionConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Outbox; 3 | 4 | namespace TransactionalBox.Internals.Outbox.Configurators 5 | { 6 | internal sealed class OutboxCompressionConfigurator : IOutboxCompressionConfigurator 7 | { 8 | public IServiceCollection Services { get; } 9 | 10 | internal OutboxCompressionConfigurator(IServiceCollection services) 11 | { 12 | Services = services; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Configurators/OutboxSerializationConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Outbox; 3 | 4 | namespace TransactionalBox.Internals.Outbox.Configurators 5 | { 6 | internal sealed class OutboxSerializationConfigurator : IOutboxSerializationConfigurator 7 | { 8 | public IServiceCollection Services { get; } 9 | 10 | public OutboxSerializationConfigurator(IServiceCollection services) 11 | { 12 | Services = services; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Configurators/OutboxStorageConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Outbox; 3 | 4 | namespace TransactionalBox.Internals.Outbox.Configurators 5 | { 6 | internal sealed class OutboxStorageConfigurator : IOutboxStorageConfigurator 7 | { 8 | public IServiceCollection Services { get; } 9 | 10 | internal OutboxStorageConfigurator(IServiceCollection services) 11 | { 12 | Services = services; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Configurators/OutboxTransportConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Outbox; 3 | 4 | namespace TransactionalBox.Internals.Outbox.Configurators 5 | { 6 | internal sealed class OutboxTransportConfigurator : IOutboxTransportConfigurator 7 | { 8 | public IServiceCollection Services { get; } 9 | 10 | internal OutboxTransportConfigurator(IServiceCollection services) 11 | { 12 | Services = services; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Extensions/OutboxExtensionUseInMemoryStorage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Internals.Outbox.Storage.InMemory; 3 | using TransactionalBox.Internals.Outbox.Storage.ContractsToImplement; 4 | using TransactionalBox.Configurators.Outbox; 5 | using TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock; 6 | 7 | namespace TransactionalBox.Internals.Outbox.Extensions 8 | { 9 | internal static class OutboxExtensionUseInMemoryStorage 10 | { 11 | internal static void UseInMemoryStorage(this IOutboxStorageConfigurator configurator) 12 | { 13 | var services = configurator.Services; 14 | 15 | services.AddKeyedInMemoryLock(); 16 | services.AddSingleton(); 17 | services.AddSingleton(); 18 | services.AddSingleton(); 19 | services.AddSingleton(); 20 | services.AddSingleton(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Extensions/OutboxExtensionUseInMemoryTransport.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Internals.Outbox.Transport.ContractsToImplement; 3 | using TransactionalBox.Internals.Outbox.Transport.InMemory; 4 | using TransactionalBox.Configurators.Outbox; 5 | using TransactionalBox.Internals.InternalPackages.Transport; 6 | 7 | namespace TransactionalBox.Internals.Outbox.Extensions 8 | { 9 | internal static class OutboxExtensionUseInMemoryTransport 10 | { 11 | internal static void UseInMemoryTransport(this IOutboxTransportConfigurator configurator) 12 | { 13 | var services = configurator.Services; 14 | 15 | services.UseInternalInMemoryTransport(); 16 | services.AddSingleton(); 17 | services.AddSingleton(new InMemoryTransportMessageSizeSettings()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Events/AddedMessagesToOutbox.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.EventHooks; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Hooks.Events 4 | { 5 | internal sealed class AddedMessagesToOutbox : EventHook; 6 | } 7 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Events/AddedMessagesToTransport.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.EventHooks; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Hooks.Events 4 | { 5 | internal sealed class AddedMessagesToTransport : EventHook; 6 | } 7 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/AddMessagesToTransport/IAddMessagesToTransportSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport 2 | { 3 | internal interface IAddMessagesToTransportSettings 4 | { 5 | int MaxBatchSize { get; } 6 | 7 | TimeSpan LockTimeout { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/AddMessagesToTransport/Logger/AddMessagesToTransportLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport.Logger 4 | { 5 | internal sealed partial class AddMessagesToTransportLogger : IAddMessagesToTransportLogger 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public AddMessagesToTransportLogger(ILogger logger) => _logger = logger; 10 | 11 | [LoggerMessage(0, LogLevel.Information, "{eventHookHandlerName} '{hookId}' (Iteration: {iteration} NumberOfMessages: {numberOfMessages} MaxBatchSize: {maxbatchSize})", SkipEnabledCheck = true)] 12 | public partial void Added(string eventHookHandlerName, Guid hookId, long iteration, int numberOfMessages, int maxBatchSize); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/AddMessagesToTransport/Logger/IAddMessagesToTransportLogger.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport.Logger 2 | { 3 | internal interface IAddMessagesToTransportLogger 4 | { 5 | void Added(string eventHookHandlerName, Guid hookId, long iteration, int numberOfMessages, int maxBatchSize); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/AddMessagesToTransport/TransportMessageFactories/GroupedOutboxMessagesWithTheSameTopic.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Storage; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport.TransportMessageFactories 4 | { 5 | internal sealed class GroupedOutboxMessagesWithTheSameTopic 6 | { 7 | internal required string Topic { get; init; } 8 | 9 | internal required IEnumerable Messages { get; init; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/AddMessagesToTransport/TransportMessageFactories/Policies/IPayloadCreationPolicy.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Storage; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport.TransportMessageFactories.Policies 4 | { 5 | internal interface IPayloadCreationPolicy 6 | { 7 | bool IsApplicable(int compressedPayloadSize); 8 | 9 | Task> Execute(byte[] compressedPayload, IEnumerable outboxMessages); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/AddMessagesToTransport/TransportMessageFactories/Policies/PayloadHasOptimalSizePolicy.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Storage; 2 | using TransactionalBox.Internals.Outbox.Transport.ContractsToImplement; 3 | 4 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport.TransportMessageFactories.Policies 5 | { 6 | internal sealed class PayloadHasOptimalSizePolicy : IPayloadCreationPolicy 7 | { 8 | private readonly ITransportMessageSizeSettings _settings; 9 | 10 | public PayloadHasOptimalSizePolicy(ITransportMessageSizeSettings settings) 11 | { 12 | _settings = settings; 13 | } 14 | 15 | public Task> Execute(byte[] compressedPayload, IEnumerable outboxMessages) 16 | { 17 | return Task.FromResult>(new List { compressedPayload }); 18 | } 19 | 20 | public bool IsApplicable(int compressedPayloadSize) 21 | { 22 | return compressedPayloadSize <= _settings.OptimalTransportMessageSize; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/AddMessagesToTransport/TransportMessageFactories/TransportEnvelope.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport.TransportMessageFactories 2 | { 3 | internal sealed class TransportEnvelope 4 | { 5 | internal required string Topic { get; init; } 6 | 7 | internal required byte[] Payload { get; init; } 8 | 9 | internal required string Compression { get; init; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/AddMessagesToTransport/TransportMessageFactories/TransportMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport.TransportMessageFactories 2 | { 3 | internal sealed record TransportMessage(Guid Id, string Topic, DateTime OccurredUtc, string Payload); 4 | } 5 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/CleanUpOutbox/ICleanUpOutboxSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.CleanUpOutbox 2 | { 3 | internal interface ICleanUpOutboxSettings 4 | { 5 | int MaxBatchSize { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/CleanUpOutbox/Logger/CleanUpOutboxLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.CleanUpOutbox.Logger 4 | { 5 | internal sealed partial class CleanUpOutboxLogger : ICleanUpOutboxLogger 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public CleanUpOutboxLogger(ILogger logger) => _logger = logger; 10 | 11 | [LoggerMessage(0, LogLevel.Information, "{eventHookHandlerName} '{hookId}' (Iteration: {iteration} NumberOfMessages: {numberOfMessages})", SkipEnabledCheck = true)] 12 | public partial void CleanedUp(string eventHookHandlerName, Guid hookId, long iteration, int numberOfMessages); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Hooks/Handlers/CleanUpOutbox/Logger/ICleanUpOutboxLogger.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Hooks.Handlers.CleanUpOutbox.Logger 2 | { 3 | internal interface ICleanUpOutboxLogger 4 | { 5 | void CleanedUp(string eventHookHandlerName, Guid hookId, long iteration, int numberOfMessages); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Oubox/Metadata.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Oubox 2 | { 3 | internal sealed class Metadata 4 | { 5 | public string Source { get; } 6 | 7 | public DateTime OccurredUtc { get; } 8 | 9 | public string CorrelationId { get; } 10 | 11 | internal Metadata(string correlationId, string serviceName, DateTime nowUtc) 12 | { 13 | Source = serviceName; 14 | OccurredUtc = nowUtc; 15 | CorrelationId = correlationId; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Oubox/OutboxMessagePayload.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Serialization; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Oubox 4 | { 5 | internal sealed class OutboxMessagePayload : IOutboxMessagePayload 6 | where T : OutboxMessage 7 | { 8 | public Metadata Metadata { get; } 9 | 10 | public T Message { get; } 11 | 12 | internal OutboxMessagePayload( 13 | Metadata metadata, 14 | T message) 15 | { 16 | Metadata = metadata; 17 | Message = message; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/OutboxDefinitions/DefaultOutboxMessageDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.OutboxDefinitions 2 | { 3 | internal sealed class DefaultOutboxMessageDefinition : IOutboxDefinition 4 | { 5 | public string? Receiver { get; private set; } = null; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/OutboxDefinitions/IOutboxDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.OutboxDefinitions 2 | { 3 | internal interface IOutboxDefinition 4 | { 5 | string? Receiver { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/OutboxStartup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using TransactionalBox.Internals.InternalPackages.EventHooks; 3 | using TransactionalBox.Internals.Outbox.Hooks.Events; 4 | 5 | namespace TransactionalBox.Internals.Outbox 6 | { 7 | internal sealed class OutboxStartup : BackgroundService 8 | { 9 | private readonly IEventHookPublisher _eventHookPublisher; 10 | 11 | public OutboxStartup(IEventHookPublisher eventHookPublisher) 12 | { 13 | _eventHookPublisher = eventHookPublisher; 14 | } 15 | 16 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 17 | { 18 | await _eventHookPublisher.PublishAsync().ConfigureAwait(false); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Serialization/IOutboxMessagePayload.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Serialization 2 | { 3 | internal interface IOutboxMessagePayload; 4 | } 5 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Serialization/IOutboxSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Serialization 2 | { 3 | internal interface IOutboxSerializer 4 | { 5 | string Serialize(T outboxMessagePayload) where T : class, IOutboxMessagePayload; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Serialization/OutboxSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace TransactionalBox.Internals.Outbox.Serialization 5 | { 6 | internal sealed class OutboxSerializer : IOutboxSerializer 7 | { 8 | private static JsonSerializerOptions _options = new JsonSerializerOptions() 9 | { 10 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, 11 | }; 12 | 13 | public string Serialize(T outboxMessage) 14 | where T : class, IOutboxMessagePayload 15 | { 16 | return JsonSerializer.Serialize(outboxMessage, _options); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Storage/ContractsToImplement/IAddMessagesToTransportRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Storage.ContractsToImplement 2 | { 3 | internal interface IAddMessagesToTransportRepository 4 | { 5 | Task MarkMessages(Guid hookId, string hookName, int batchSize, TimeProvider timeProvider, TimeSpan lockTimeout); 6 | 7 | Task> GetMarkedMessages(Guid hookId); 8 | 9 | Task MarkAsProcessed(Guid hookId, DateTime processedUtc); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Storage/ContractsToImplement/ICleanUpOutboxRepository.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Storage.ContractsToImplement 2 | { 3 | internal interface ICleanUpOutboxRepository 4 | { 5 | Task RemoveProcessedMessages(int batchSize); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Storage/ContractsToImplement/IOutboxStorage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Storage.ContractsToImplement 2 | { 3 | internal interface IOutboxStorage 4 | { 5 | Task Add(OutboxMessageStorage message); 6 | 7 | Task AddRange(IEnumerable messages); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Storage/ContractsToImplement/IStorageProvider.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Storage.ContractsToImplement 2 | { 3 | internal interface IStorageProvider 4 | { 5 | string? ProviderName { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Storage/InMemory/IOutboxStorageReadOnly.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Storage.InMemory 2 | { 3 | internal interface IOutboxStorageReadOnly 4 | { 5 | IReadOnlyCollection OutboxMessages { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Storage/InMemory/InMemoryStorageProvider.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Storage.ContractsToImplement; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Storage.InMemory 4 | { 5 | internal sealed class InMemoryStorageProvider : IStorageProvider 6 | { 7 | public string? ProviderName { get; } = "InMemory"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Storage/OutboxDistributedLock.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.DistributedLock; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Storage 4 | { 5 | internal sealed class OutboxDistributedLock : Lock; 6 | } 7 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Storage/OutboxMessageStorage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Storage 2 | { 3 | internal sealed class OutboxMessageStorage 4 | { 5 | public required Guid Id { get; set; } 6 | 7 | public required DateTime OccurredUtc { get; set; } 8 | 9 | public bool IsProcessed { get; set; } 10 | 11 | public required string Topic { get; set; } 12 | 13 | public required string Payload { get; set; } 14 | 15 | public DateTime? LockUtc { get; set; } 16 | 17 | public string? JobId { get; set; } //TODO sequence based on timestamp + machineName + processId 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Storage/SequentialGuidConfigurator.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.SequentialGuid; 2 | using TransactionalBox.Internals.Outbox.Storage.ContractsToImplement; 3 | 4 | namespace TransactionalBox.Internals.Outbox.Storage 5 | { 6 | internal sealed class SequentialGuidConfigurator 7 | { 8 | private readonly IStorageProvider _storageProvider; 9 | 10 | public SequentialGuidConfigurator(IStorageProvider storageProvider) 11 | { 12 | _storageProvider = storageProvider; 13 | } 14 | 15 | 16 | public SequentialGuidType Create() 17 | { 18 | var providerName = _storageProvider.ProviderName; 19 | 20 | return providerName switch 21 | { 22 | // TODO Check provider name 23 | // SequentialAtEnd SQL Server 24 | // SequentialAsBinar Oracle 25 | _ => SequentialGuidType.SequentialAsString, // MySQL, PostgreSQL 26 | }; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Transport/ContractsToImplement/IOutboxTransport.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport.TransportMessageFactories; 2 | namespace TransactionalBox.Internals.Outbox.Transport.ContractsToImplement 3 | { 4 | internal interface IOutboxTransport 5 | { 6 | Task Add(TransportEnvelope transportEnvelope); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Transport/ContractsToImplement/ITransportMessageSizeSettings.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Transport.ContractsToImplement 2 | { 3 | internal interface ITransportMessageSizeSettings 4 | { 5 | public int OptimalTransportMessageSize { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Transport/FailedAddMessagesToTransportException.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals.Outbox.Transport 2 | { 3 | internal sealed class FailedAddMessagesToTransportException : Exception 4 | { 5 | internal FailedAddMessagesToTransportException() { } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Transport/InMemory/InMemoryOutboxTransport.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.Transport; 2 | using TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport.TransportMessageFactories; 3 | using TransactionalBox.Internals.Outbox.Transport.ContractsToImplement; 4 | 5 | namespace TransactionalBox.Internals.Outbox.Transport.InMemory 6 | { 7 | internal sealed class InMemoryOutboxTransport : IOutboxTransport 8 | { 9 | private readonly IInMemoryTransport _inMemoryTransport; 10 | 11 | public InMemoryOutboxTransport(IInMemoryTransport inMemoryTransport) 12 | { 13 | _inMemoryTransport = inMemoryTransport; 14 | } 15 | 16 | public async Task Add(TransportEnvelope transportEnvelope) 17 | { 18 | var transportObject = new TransportObject() 19 | { 20 | Topic = transportEnvelope.Topic, 21 | Payload = transportEnvelope.Payload, 22 | Compression = transportEnvelope.Compression, 23 | }; 24 | 25 | await _inMemoryTransport.Writer.WriteAsync(transportObject); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/Outbox/Transport/InMemory/InMemoryTransportMessageSizeSettings.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Transport.ContractsToImplement; 2 | 3 | namespace TransactionalBox.Internals.Outbox.Transport.InMemory 4 | { 5 | internal sealed class InMemoryTransportMessageSizeSettings : ITransportMessageSizeSettings 6 | { 7 | public int OptimalTransportMessageSize { get; } = 1073741824; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/ServiceContext.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals 2 | { 3 | internal sealed class ServiceContext : IServiceContext 4 | { 5 | public string Id { get; } 6 | 7 | public string InstanceId { get; } 8 | 9 | public ServiceContext( 10 | string id, 11 | string instanceId) 12 | { 13 | Id = id; 14 | InstanceId = instanceId; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/SystemClock.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals 2 | { 3 | internal sealed class SystemClock : ISystemClock 4 | { 5 | public DateTime UtcNow => TimeProvider.GetUtcNow().UtcDateTime; 6 | 7 | public TimeProvider TimeProvider { get; } 8 | 9 | public SystemClock(TimeProvider timeProvider) 10 | { 11 | TimeProvider = timeProvider; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/TopicFactory.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.Internals 2 | { 3 | internal sealed class TopicFactory : ITopicFactory 4 | { 5 | private const char _separator = '.'; 6 | 7 | public string Create(string serviceName, string messageName) 8 | { 9 | var topic = serviceName + _separator + messageName; 10 | 11 | return topic; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/TransactionalBox/Internals/TransactionalBoxBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using TransactionalBox.Builders; 4 | 5 | namespace TransactionalBox.Internals 6 | { 7 | internal sealed class TransactionalBoxBuilder : ITransactionalBoxBuilder 8 | { 9 | public IServiceCollection Services { get; } 10 | 11 | public IConfiguration Configuration { get; } 12 | 13 | internal TransactionalBoxBuilder( 14 | IServiceCollection services, 15 | IConfiguration configuration) 16 | { 17 | Services = services; 18 | 19 | if (configuration is not null) 20 | { 21 | Configuration = configuration; 22 | } 23 | else 24 | { 25 | Configuration = new ConfigurationBuilder().Build(); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/TransactionalBox/OutboxDefinition.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.OutboxDefinitions; 2 | 3 | namespace TransactionalBox 4 | { 5 | /// 6 | /// Define the outbox message. 7 | /// 8 | public abstract class OutboxDefinition : IOutboxDefinition 9 | where TOutboxMessage : OutboxMessage 10 | { 11 | protected internal string? Receiver { get; protected set; } = null; 12 | 13 | string? IOutboxDefinition.Receiver => Receiver; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/TransactionalBox/OutboxMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox 2 | { 3 | /// 4 | /// Base class of outbox messagese. 5 | /// 6 | public abstract class OutboxMessage; 7 | } -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/Inbox/AddMessagesToInboxSettings.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Inbox.BackgroundProcesses.AddMessagesToInbox; 2 | 3 | namespace TransactionalBox.Settings.Inbox 4 | { 5 | public sealed class AddMessagesToInboxSettings : IAddMessagesToInboxSettings 6 | { 7 | public TimeSpan DefaultTimeToLiveIdempotencyKey { get; set; } = TimeSpan.FromDays(7); 8 | 9 | internal AddMessagesToInboxSettings() { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/Inbox/CleanUpIdempotencyKeysSettings.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Inbox.BackgroundProcesses.CleanUpIdempotencyKeys; 2 | 3 | namespace TransactionalBox.Settings.Inbox 4 | { 5 | public sealed class CleanUpIdempotencyKeysSettings : ICleanUpIdempotencyKeysSettings 6 | { 7 | public int MaxBatchSize { get; set; } = 10000; 8 | 9 | public TimeSpan Period { get; set; } = TimeSpan.FromHours(1); 10 | 11 | internal CleanUpIdempotencyKeysSettings() { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/Inbox/CleanUpInboxSettings.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Inbox.Hooks.Handlers.CleanUpInbox; 2 | 3 | namespace TransactionalBox.Settings.Inbox 4 | { 5 | public sealed class CleanUpInboxSettings : ICleanUpInboxSettings 6 | { 7 | public int MaxBatchSize { get; set; } = 10000; 8 | 9 | public bool IsEnabled { get; set; } = true; 10 | 11 | internal CleanUpInboxSettings() { } 12 | } 13 | } -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/Inbox/InboxSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox; 3 | using TransactionalBox.Configurators.Inbox; 4 | using TransactionalBox.Internals.Inbox.Configurators; 5 | 6 | namespace TransactionalBox.Settings.Inbox 7 | { 8 | public sealed class InboxSettings 9 | { 10 | public AddMessagesToInboxSettings AddMessagesToInboxSettings { get; } = new AddMessagesToInboxSettings(); 11 | 12 | public CleanUpInboxSettings CleanUpInboxSettings { get; } = new CleanUpInboxSettings(); 13 | 14 | public CleanUpIdempotencyKeysSettings CleanUpIdempotencyKeysSettings { get; } = new CleanUpIdempotencyKeysSettings(); 15 | 16 | public Action ConfigureDeserialization { get; set; } = x => x.UseSystemTextJson(); 17 | 18 | internal InboxSettings() { } 19 | 20 | internal void ConfigureDelegates(IServiceCollection services) 21 | { 22 | ConfigureDeserialization(new InboxDeserializationConfigurator(services)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/Outbox/AddMessagesToTransportSettings.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Hooks.Handlers.AddMessagesToTransport; 2 | 3 | namespace TransactionalBox.Settings.Outbox 4 | { 5 | public sealed class AddMessagesToTransportSettings : IAddMessagesToTransportSettings 6 | { 7 | public int MaxBatchSize { get; set; } = 5000; 8 | 9 | public TimeSpan LockTimeout { get; set; } = TimeSpan.FromSeconds(10); 10 | 11 | internal AddMessagesToTransportSettings() { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/Outbox/CleanUpOutboxSettings.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.Outbox.Hooks.Handlers.CleanUpOutbox; 2 | 3 | namespace TransactionalBox.Settings.Outbox 4 | { 5 | public sealed class CleanUpOutboxSettings : ICleanUpOutboxSettings 6 | { 7 | public int MaxBatchSize { get; set; } = 10000; 8 | 9 | public bool IsEnabled { get; set; } = true; 10 | 11 | internal CleanUpOutboxSettings() { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/Outbox/Compression/BrotliCompressionSettings.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | using TransactionalBox.Internals.Outbox.Compression.Brotli; 3 | 4 | namespace TransactionalBox.Settings.Outbox.Compression 5 | { 6 | public sealed class BrotliCompressionSettings : IBrotliCompressionSettings 7 | { 8 | public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Fastest; 9 | 10 | internal BrotliCompressionSettings() { } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/Outbox/Compression/GZipCompressionSettings.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | using TransactionalBox.Internals.Outbox.Compression.GZip; 3 | 4 | namespace TransactionalBox.Settings.Outbox.Compression 5 | { 6 | public sealed class GZipCompressionSettings : IGZipCompressionSettings 7 | { 8 | public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Fastest; 9 | 10 | internal GZipCompressionSettings() { } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/Outbox/OutboxSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using TransactionalBox.Configurators.Outbox; 3 | using TransactionalBox.Internals.Outbox.Configurators; 4 | 5 | namespace TransactionalBox.Settings.Outbox 6 | { 7 | public sealed class OutboxSettings 8 | { 9 | public AddMessagesToTransportSettings AddMessagesToTransportSettings { get; } = new AddMessagesToTransportSettings(); 10 | 11 | public CleanUpOutboxSettings CleanUpOutboxSettings { get; } = new CleanUpOutboxSettings(); 12 | 13 | public Action ConfigureSerialization { get; set; } = x => x.UseSystemTextJson(); 14 | 15 | public Action ConfigureCompression { get; set; } = x => x.UseNoCompression(); 16 | 17 | internal OutboxSettings() { } 18 | 19 | internal void ConfigureDelegates(IServiceCollection services) 20 | { 21 | ConfigureSerialization(new OutboxSerializationConfigurator(services)); 22 | ConfigureCompression(new OutboxCompressionConfigurator(services)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/TransactionalBox/Settings/TransactionalBoxSettings.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace TransactionalBox.Settings 4 | { 5 | public sealed class TransactionalBoxSettings 6 | { 7 | [Required] 8 | public string ServiceId { get; set; } 9 | 10 | internal TransactionalBoxSettings() { } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/TransactionalBox/TransactionalBox.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/SeedWork/Inbox/InboxVerifier.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.End2EndTests.SeedWork.Inbox 2 | { 3 | internal sealed class InboxVerifier 4 | { 5 | internal bool IsExecuted { get; set; } = false; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/SeedWork/Inbox/SendableMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.End2EndTests.SeedWork.Inbox 2 | { 3 | internal sealed class SendableMessage : InboxMessage 4 | { 5 | public string Message { get; init; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/SeedWork/Inbox/SendableMessageInboxHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.End2EndTests.SeedWork.Inbox 2 | { 3 | internal sealed class SendableMessageInboxHandler : IInboxHandler 4 | { 5 | private readonly InboxVerifier _inboxVerifier; 6 | 7 | public SendableMessageInboxHandler(InboxVerifier inboxVerifier) 8 | { 9 | _inboxVerifier = inboxVerifier; 10 | } 11 | 12 | public Task Handle(SendableMessage message, IExecutionContext executionContext) 13 | { 14 | _inboxVerifier.IsExecuted = true; 15 | 16 | return Task.CompletedTask; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/SeedWork/Outbox/SendableMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.End2EndTests.SeedWork.Outbox 2 | { 3 | internal sealed class SendableMessage : OutboxMessage 4 | { 5 | public required string Message { get; init; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/SeedWork/Outbox/SendableMessageDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace TransactionalBox.End2EndTests.SeedWork.Outbox 2 | { 3 | internal sealed class SendableMessageDefinition : OutboxDefinition 4 | { 5 | public SendableMessageDefinition() 6 | { 7 | Receiver = "INBOX"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/TestCases/Dependencies.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TransactionalBox.End2EndTests.TestCases 8 | { 9 | public sealed record Dependencies(IServiceProvider OutboxDependecies, IServiceProvider InboxDependecies); 10 | } 11 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/TestCases/End2EndTestCase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit.Abstractions; 7 | 8 | namespace TransactionalBox.End2EndTests.TestCases 9 | { 10 | public class End2EndTestCase 11 | { 12 | private readonly Func> _init; 13 | 14 | private readonly Func _cleanUp; 15 | 16 | private readonly string _testName; 17 | 18 | public End2EndTestCase( 19 | Func> init, 20 | Func cleanUp, 21 | string testName) 22 | { 23 | _init = init; 24 | _cleanUp = cleanUp; 25 | _testName = testName; 26 | } 27 | 28 | public Task Init(ITestOutputHelper testOutputHelper) 29 | { 30 | return _init(testOutputHelper); 31 | } 32 | 33 | public Task CleanUp() 34 | { 35 | return _cleanUp(); 36 | } 37 | 38 | public override string ToString() => _testName; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/TestCases/Storage/EntityFrameworkCore/DbContexts/InboxDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace TransactionalBox.End2EndTests.TestCases.Storage.EntityFrameworkCore.DbContexts 4 | { 5 | internal class InboxDbContext : DbContext 6 | { 7 | public InboxDbContext() : base() { } 8 | 9 | public InboxDbContext(DbContextOptions options) 10 | : base(options) { } 11 | 12 | protected override void OnModelCreating(ModelBuilder modelBuilder) 13 | { 14 | modelBuilder.AddInbox(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/TestCases/Storage/EntityFrameworkCore/DbContexts/OutboxDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace TransactionalBox.End2EndTests.TestCases.Storage.EntityFrameworkCore.DbContexts 4 | { 5 | internal sealed class OutboxDbContext : DbContext 6 | { 7 | public OutboxDbContext() : base() { } 8 | 9 | public OutboxDbContext(DbContextOptions options) 10 | : base(options) { } 11 | 12 | protected override void OnModelCreating(ModelBuilder modelBuilder) 13 | { 14 | modelBuilder.AddOutbox(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/TransactionalBox.End2EndTests/Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using TransactionalBox.End2EndTests.TestCases.Storage.EntityFrameworkCore; 3 | 4 | namespace TransactionalBox.End2EndTests 5 | { 6 | public class Tests : IEnumerable 7 | { 8 | public IEnumerator GetEnumerator() 9 | { 10 | //yield return new object[] { new EntityFrameworkCoreSqlServer().GetEnd2EndTestCase() }; 11 | yield return new object[] { new EntityFrameworkCorePostgresSql().GetEnd2EndTestCase() }; 12 | //TODO problem with the enumeration of tests 13 | 14 | } 15 | 16 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/TransactionalBox.EntityFrameworkCore.Tests/Internals/InternalPackages/DistributedLock/SeedWork/DisabledKeyedInMemoryLock.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock; 2 | 3 | namespace TransactionalBox.EntityFrameworkCore.Tests.Internals.InternalPackages.DistributedLock.SeedWork 4 | { 5 | internal sealed class DisabledKeyedInMemoryLock : IKeyedInMemoryLock 6 | { 7 | public Task Acquire(string key, CancellationToken cancellationToken = default) 8 | { 9 | return Task.FromResult(new DisabledLockInstance()); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/TransactionalBox.EntityFrameworkCore.Tests/Internals/InternalPackages/DistributedLock/SeedWork/DisabledLockInstance.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.KeyedInMemoryLock; 2 | 3 | namespace TransactionalBox.EntityFrameworkCore.Tests.Internals.InternalPackages.DistributedLock.SeedWork 4 | { 5 | internal sealed class DisabledLockInstance : ILockInstance 6 | { 7 | public void Dispose() { return; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/TransactionalBox.EntityFrameworkCore.Tests/Internals/InternalPackages/DistributedLock/SeedWork/TestDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using TransactionalBox.EntityFrameworkCore.Internals.InternalPackages.DistributedLock; 3 | 4 | namespace TransactionalBox.EntityFrameworkCore.Tests.Internals.InternalPackages.DistributedLock.SeedWork 5 | { 6 | public sealed class TestDbContext : DbContext 7 | { 8 | public TestDbContext() : base() { } 9 | 10 | public TestDbContext(DbContextOptions options) 11 | : base(options) { } 12 | 13 | protected override void OnModelCreating(ModelBuilder modelBuilder) 14 | { 15 | modelBuilder.AddEntityFrameworkCoreDistributedLock(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/TransactionalBox.EntityFrameworkCore.Tests/Internals/InternalPackages/DistributedLock/SeedWork/TestLock.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals.InternalPackages.DistributedLock; 2 | 3 | namespace TransactionalBox.EntityFrameworkCore.Tests.Internals.InternalPackages.DistributedLock.SeedWork 4 | { 5 | internal sealed class TestLock : Lock; 6 | } 7 | -------------------------------------------------------------------------------- /tests/TransactionalBox.UnitTests/TopicFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using TransactionalBox.Internals; 2 | using Xunit; 3 | 4 | namespace TransactionalBox.UnitTests 5 | { 6 | public sealed class TopicFactoryTests 7 | { 8 | private readonly ITopicFactory _topicFactory = new TopicFactory(); 9 | 10 | [Fact] 11 | public void TopicFactoryTest() 12 | { 13 | // Arrange 14 | const string serviceName = "ModuleName"; 15 | const char separator = '.'; 16 | var messageName = "TestMessage"; 17 | 18 | // Act 19 | var actualTopic = _topicFactory.Create(serviceName, messageName); 20 | 21 | // Assert 22 | var expectedTopic = serviceName + separator + messageName; 23 | 24 | Assert.Equal(expectedTopic, actualTopic); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/TransactionalBox.UnitTests/TransactionalBox.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------