├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── pr-checks.yml ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SUPPORT.md ├── Samples └── Metadata │ ├── MetadataSamples.sln │ ├── PersistedMetadata │ ├── MessageWindow.Designer.cs │ ├── MessageWindow.cs │ ├── Messages.cs │ ├── PersistedMetadata.csproj │ └── Program.cs │ └── SimpleMetadata │ ├── MessageWindow.Designer.cs │ ├── MessageWindow.cs │ ├── Messages.cs │ ├── Program.cs │ └── SimpleMetadata.csproj ├── build ├── ReactiveDomain.Policy.props ├── ReactiveDomain.Testing.props ├── ReactiveDomain.UI.Testing.props ├── ReactiveDomain.UI.props └── ReactiveDomain.props ├── package.bat ├── publish.bat ├── src ├── .editorconfig ├── .nuget │ └── NuGet.exe ├── Dependency.Versions.props ├── PolicyTool.sln ├── ReactiveDomain.Core │ ├── Audit │ │ └── AuditRecord.cs │ ├── IEventSource.cs │ ├── IMetadata.cs │ ├── IMetadataSource.cs │ ├── Logging │ │ ├── ConsoleLogger.cs │ │ ├── ILogger.cs │ │ ├── LazyLogger.cs │ │ ├── LogManager.cs │ │ └── NullLogger.cs │ ├── Messaging │ │ └── Json.cs │ ├── Metadata.cs │ ├── MetadatumNotFoundException.cs │ ├── ReactiveDomain.Core.csproj │ └── Util │ │ ├── Application.cs │ │ ├── BytesFormatter.cs │ │ ├── CustomConfigLoader.cs │ │ ├── Disposer.cs │ │ ├── Empty.cs │ │ ├── Ensure.cs │ │ ├── EnumerableExtensions.cs │ │ ├── FileStreamExtensions.cs │ │ ├── FileUtils.cs │ │ ├── FluentExtensions.cs │ │ ├── Helper.cs │ │ ├── HostName.cs │ │ ├── IPEndPointComparer.cs │ │ ├── IpEndPointExtensions.cs │ │ ├── MD5Hash.cs │ │ ├── OS.cs │ │ ├── ShellExecutor.cs │ │ ├── StringExtensions.cs │ │ └── Unit.cs ├── ReactiveDomain.Debug.nuspec ├── ReactiveDomain.Foundation.Tests │ ├── Domain │ │ ├── AggregateRootEntityTests.cs │ │ ├── CapturingRoute.cs │ │ ├── CorrelatedAggregate.cs │ │ ├── MetadataTests.cs │ │ ├── MetadatumTests.cs │ │ ├── with_correlated_aggregate.cs │ │ ├── with_correlated_repository.cs │ │ └── with_repository.cs │ ├── EmbeddedStreamStoreConnectionCollection.cs │ ├── Logging │ │ ├── when_commands_are_fired.cs │ │ ├── when_events_are_published.cs │ │ ├── when_logging_disabled_and_commands_are_fired.cs │ │ ├── when_logging_disabled_and_events_are_published.cs │ │ ├── when_logging_disabled_and_mixed_messages_are_published.cs │ │ ├── when_logging_high_volume_message_traffic.cs │ │ ├── when_mixed_messages_are_published.cs │ │ ├── when_toggling_logging.cs │ │ ├── when_toggling_logging_from_disabled.cs │ │ ├── with_message_logging_disabled.cs │ │ └── with_message_logging_enabled.cs │ ├── PrefixedCamelCaseStreamNameBuilderTests.cs │ ├── ReactiveDomain.Foundation.Tests.csproj │ ├── StreamListenerTests │ │ ├── Common │ │ │ ├── CommonHelpers.cs │ │ │ └── TestStreamNameBuilder.cs │ │ ├── when_using_listener_start_with_aggregate_and_guid.cs │ │ ├── when_using_listener_start_with_category_aggregate.cs │ │ ├── when_using_listener_start_with_custom_stream_not_synched.cs │ │ ├── when_using_listener_start_with_custom_stream_synched.cs │ │ ├── when_using_listener_start_with_custom_stream_synched_bus.cs │ │ ├── when_using_listener_start_with_event_type.cs │ │ └── when_using_listener_start_with_future_stream.cs │ ├── can_serialize_correlated_messages.cs │ ├── when_serializing_with_json_message_serializer.cs │ ├── when_using_caching_repository.cs │ ├── when_using_correlated_repository.cs │ ├── when_using_read_model_base.cs │ ├── when_using_read_model_base_with_reader.cs │ └── when_using_snapshot_read_model.cs ├── ReactiveDomain.Foundation │ ├── BootStrap.cs │ ├── ConfiguredConnection.cs │ ├── Domain │ │ ├── AggregateRoot.cs │ │ ├── ChildEntity.cs │ │ ├── EventDrivenStateMachine.cs │ │ ├── EventRecorder.cs │ │ ├── EventRouter.cs │ │ ├── ICorrelatedEventSource.cs │ │ ├── ISnapshotSource.cs │ │ ├── IdempotentEventIdGenerator.cs │ │ ├── NameBasedGuidGenerator.cs │ │ └── ProcessManager.cs │ ├── ICatchupStreamSubscriber.cs │ ├── IConfiguredConnection.cs │ ├── ICorrelatedRepository.cs │ ├── IListener.cs │ ├── IRepository.cs │ ├── IStreamNameBuilder.cs │ ├── IStreamReader.cs │ ├── PrefixedCamelCaseStreamNameBuilder.cs │ ├── ReactiveDomain.Foundation.csproj │ ├── RepositoryExtensions.cs │ ├── StreamStore │ │ ├── AggregateDeletedException.cs │ │ ├── AggregateNotFoundException.cs │ │ ├── AggregateVersionException.cs │ │ ├── CachingRepository.cs │ │ ├── CommonMetadata.cs │ │ ├── ContractResolver.cs │ │ ├── CorrelatedStreamStoreRepository.cs │ │ ├── IAggregateCache.cs │ │ ├── IEventSerializer.cs │ │ ├── JsonMessageSerializer.cs │ │ ├── JsonMessageSerializerSettings.cs │ │ ├── OptimisticCacheRepository.cs │ │ ├── QueuedStreamListener.cs │ │ ├── ReadModelBase.cs │ │ ├── ReadModelProperty.cs │ │ ├── ReadModelState.cs │ │ ├── ReadThroughAggregateCache.cs │ │ ├── SnapshotReadModel.cs │ │ ├── StreamListener.cs │ │ ├── StreamReader.cs │ │ ├── StreamStoreMsgs.cs │ │ └── StreamStoreRepository.cs │ └── TransientSubscriber.cs ├── ReactiveDomain.IdentityStorage.Tests │ ├── ClientStoreTests.cs │ ├── MockPrincipal.cs │ ├── ReactiveDomain.IdentityStorage.Tests.csproj │ ├── ResourceStoreTests.cs │ ├── SubjectAggTests.cs │ ├── SubjectRmTests.cs │ ├── TestMessages.cs │ ├── UserAggregateTests.cs │ ├── UserServiceTests.cs │ ├── UserStoreTests.cs │ └── UsersRMTests.cs ├── ReactiveDomain.IdentityStorage │ ├── Domain │ │ ├── Client.cs │ │ ├── Subject.cs │ │ └── User.cs │ ├── Messages │ │ ├── ClientMsgs.cs │ │ ├── SubjectMsgs.cs │ │ └── UserMsgs.cs │ ├── ReactiveDomain.IdentityStorage.csproj │ ├── ReadModels │ │ ├── PrincipalWrapper.cs │ │ ├── SubjectsRm.cs │ │ ├── UserDTO.cs │ │ └── UsersRm.cs │ ├── Services │ │ ├── ActiveDirectoryUserSearch.cs │ │ ├── ClientStore.cs │ │ ├── ClientSvc.cs │ │ ├── ResourcesStore.cs │ │ ├── UserStore.cs │ │ ├── UserSvc.cs │ │ └── UserValidation.cs │ ├── SubjectExceptions.cs │ └── UserExceptions.cs ├── ReactiveDomain.Messaging.Tests │ ├── LaterServiceTests.cs │ ├── MessageExtensionsTests.cs │ ├── ReactiveDomain.Messaging.Tests.csproj │ ├── RemoteBusFixture.cs │ ├── Subscribers │ │ └── QueuedSubscriber │ │ │ ├── can_handle_multiple_publisher_threads_queued.cs │ │ │ ├── can_handle_ordered_queued_messages.cs │ │ │ └── can_unsubscribe_queued_messages.cs │ ├── TimePositionTests.cs │ ├── can_cancel_commands_via_cancellation_token.cs │ ├── when_connecting_buses.cs │ ├── when_creating_a_message.cs │ ├── when_ensuring_values.cs │ ├── when_publishing_a_message.cs │ ├── when_rebuilding_message_hierarchy.cs │ ├── when_sending_commands.cs │ ├── when_sending_commands_via_single_queued_dispatcher.cs │ ├── when_sending_concurrent_commands.cs │ ├── when_sending_remote_handled_commands.cs │ ├── when_serializing_commands.cs │ └── when_serializing_ids.cs ├── ReactiveDomain.Messaging │ ├── BootStrap.cs │ ├── Bus │ │ ├── AdHocCommandHandler.cs │ │ ├── AdHocHandler.cs │ │ ├── AdHocTypedCommandHandler.cs │ │ ├── BusConnector.cs │ │ ├── CommandException.cs │ │ ├── CommandHandler.cs │ │ ├── CommandManager.cs │ │ ├── CommandTracker.cs │ │ ├── DelaySendEnvelope.cs │ │ ├── Dispatcher.cs │ │ ├── DynamicHandler.cs │ │ ├── ExistingHandlerException.cs │ │ ├── HandleExtensions.cs │ │ ├── IApply.cs │ │ ├── IBus.cs │ │ ├── ICommandBus.cs │ │ ├── ICommandPublisher.cs │ │ ├── ICommandSubscriber.cs │ │ ├── IDispatcher.cs │ │ ├── IHandle.cs │ │ ├── IHandleCommand.cs │ │ ├── IMonitoredQueue.cs │ │ ├── IPublisher.cs │ │ ├── IQueuedHandler.cs │ │ ├── ISubscriber.cs │ │ ├── IdempotentHandler.cs │ │ ├── InMemoryBus.cs │ │ ├── LaterService.cs │ │ ├── MessageHandler.cs │ │ ├── MultiQueuedHandler.cs │ │ ├── MultiQueuedPublisher.cs │ │ ├── NarrowingHandler.cs │ │ ├── NullBus.cs │ │ ├── NullableBus.cs │ │ ├── QueueMonitor.cs │ │ ├── QueueStatsCollector.cs │ │ ├── QueuedHandler.cs │ │ ├── QueuedHandlerDiscarding.cs │ │ ├── QueuedSubscriber.cs │ │ ├── TimePosition.cs │ │ ├── TimeSource.cs │ │ └── WideningHandler.cs │ ├── CallbackEnvelope.cs │ ├── Forwarder.cs │ ├── IEnvelope.cs │ ├── MessageBuilder.cs │ ├── MessageHierarchy.cs │ ├── Messages │ │ ├── Command.cs │ │ ├── CommandResponse.cs │ │ ├── CorrelatedRoot.cs │ │ ├── CorrelationSource.cs │ │ ├── Event.cs │ │ ├── IChainedMessage.cs │ │ ├── ICommand.cs │ │ ├── ICommandResponse.cs │ │ ├── ICorrelatedMessage.cs │ │ ├── IEvent.cs │ │ ├── IMessage.cs │ │ ├── IQueueAffineMessage.cs │ │ ├── Message.cs │ │ └── MessageExtensions.cs │ ├── Monitoring │ │ ├── Stats │ │ │ ├── DiskIo.cs │ │ │ ├── GcStats.cs │ │ │ ├── QueueStats.cs │ │ │ ├── StatMetadata.cs │ │ │ └── StatsContainer.cs │ │ └── Utils │ │ │ ├── PerfCounterHelper.cs │ │ │ └── StatsCsvEncoder.cs │ ├── NLog.xsd │ ├── NoopEnvelope.cs │ ├── PriorityQueue.cs │ ├── PublishEnvelope.cs │ ├── ReactiveDomain.Messaging.csproj │ ├── RequestResponseDispatcher.cs │ ├── SendToThisEnvelope.cs │ └── TimeoutService.cs ├── ReactiveDomain.Persistence │ ├── CatchupSubscriptionSettings.cs │ ├── ClientConnectionEventArgs.cs │ ├── Consts.cs │ ├── EventData.cs │ ├── EventReadResult.cs │ ├── EventStore │ │ ├── EsdbConfig.cs │ │ ├── EventStoreConnectionManager.cs │ │ ├── EventStoreConnectionWrapper.cs │ │ └── EventStoreLauncher.cs │ ├── EventStoreCatchUpSubscription.cs │ ├── EventStoreStreamCatchUpSubscription.cs │ ├── ExpectedVersion.cs │ ├── IStreamStoreConnection.cs │ ├── Position.cs │ ├── ProjectedEvent.cs │ ├── ReactiveDomain.Persistence.csproj │ ├── ReadDirection.cs │ ├── RecordedEvent.cs │ ├── ScopedSerialization │ │ ├── IMessageDeserializer.cs │ │ ├── IMessageSerializer.cs │ │ ├── ISnapshotDeserializer.cs │ │ ├── ISnapshotSerializer.cs │ │ ├── Serialization.cs │ │ ├── SerializationPair.cs │ │ ├── SerializationRegistry.cs │ │ ├── SerializationRegistryMessageExtensions.cs │ │ ├── SerializationRegistrySnapshotExtensions.cs │ │ ├── SerializedMessage.cs │ │ ├── Snapshot.cs │ │ ├── StorableMessage.cs │ │ └── StreamStoreReadResult.cs │ ├── StreamEventsSlice.cs │ ├── StreamName.cs │ ├── StreamNameConversions.cs │ ├── StreamNameConverter.cs │ ├── StreamPosition.cs │ ├── StreamStoreConnectionException.cs │ ├── StreamSubscription.cs │ ├── SubscriptionDropReason.cs │ ├── UnknownTypeException.cs │ ├── UserCredentials.cs │ └── WriteResult.cs ├── ReactiveDomain.Policy.Debug.nuspec ├── ReactiveDomain.Policy.Tests │ ├── GlobalSuppressions.cs │ ├── PolicyMapTest.cs │ ├── PolicyTests.cs │ └── ReactiveDomain.Policy.Tests.csproj ├── ReactiveDomain.Policy.nuspec ├── ReactiveDomain.Policy.targets ├── ReactiveDomain.Policy │ ├── AuthorizationException.cs │ ├── Permission.cs │ ├── Permissions.cs │ ├── Policy.cs │ ├── PolicyDTO.cs │ ├── PolicyDispatcher.cs │ ├── ReactiveDomain.Policy.csproj │ ├── Role.cs │ ├── UserDetails.cs │ └── UserPolicy.cs ├── ReactiveDomain.PolicyStorage.Tests │ ├── ApplicationAggregateTests.cs │ ├── ApplicationServiceTests.cs │ ├── ExternalProviderAggregateTests.cs │ ├── Helpers │ │ └── TestMessages.cs │ ├── ReactiveDomain.PolicyStorage.Tests.csproj │ ├── RoleAggregateTests.cs │ ├── RoleServiceTests.cs │ ├── UserEntitlementRMTests.cs │ └── with_policy_user.cs ├── ReactiveDomain.PolicyStorage │ ├── Domain │ │ ├── PolicyUser.cs │ │ ├── SecuredApplication.cs │ │ └── SecurityPolicy.cs │ ├── Messages │ │ ├── ApplicationMsgs.cs │ │ └── PolicyUserMsgs.cs │ ├── ReactiveDomain.PolicyStorage.csproj │ ├── ReadModels │ │ ├── ApplicationDTO.cs │ │ ├── ApplicationRm.cs │ │ ├── FilteredPoliciesRM.cs │ │ ├── PolicyDTO.cs │ │ ├── PolicyUserDTO.cs │ │ ├── PolicyUserRm.cs │ │ ├── ReadModelHelper.cs │ │ └── RoleDTO.cs │ └── Services │ │ ├── ApplicationSvc.cs │ │ ├── PolicyExceptions.cs │ │ └── PolicySvc.cs ├── ReactiveDomain.PolicyTool │ ├── Program.cs │ ├── Properties │ │ ├── PublishProfiles │ │ │ └── FolderProfile.pubxml │ │ └── launchSettings.json │ ├── ReactiveDomain.PolicyTool.csproj │ └── es_settings.json ├── ReactiveDomain.Testing.Debug.nuspec ├── ReactiveDomain.Testing.nuspec ├── ReactiveDomain.Testing │ ├── AssertEx.cs │ ├── ConnectionUtil.cs │ ├── DispatcherUtil.cs │ ├── Domain │ │ ├── Catch.cs │ │ ├── ExpectEventsScenario.cs │ │ ├── ExpectEventsScenarioPassed.cs │ │ ├── ExpectExceptionScenario.cs │ │ ├── ExpectExceptionScenarioPassed.cs │ │ ├── IExpectEventsScenarioBuilder.cs │ │ ├── IExpectExceptionScenarioBuilder.cs │ │ ├── IScenarioGivenNoneStateBuilder.cs │ │ ├── IScenarioGivenStateBuilder.cs │ │ ├── IScenarioInitialStateBuilder.cs │ │ ├── IScenarioThenNoneStateBuilder.cs │ │ ├── IScenarioThenStateBuilder.cs │ │ ├── IScenarioThrowsStateBuilder.cs │ │ ├── IScenarioWhenStateBuilder.cs │ │ ├── Scenario.cs │ │ ├── ScenarioExpectedEventsButRecordedOtherEvents.cs │ │ ├── ScenarioExpectedEventsButThrewException.cs │ │ ├── ScenarioExpectedExceptionButRecordedEvents.cs │ │ ├── ScenarioExpectedExceptionButThrewNoException.cs │ │ ├── ScenarioExpectedExceptionButThrewOtherException.cs │ │ └── ScenarioExtensions.cs │ ├── EventStore │ │ ├── EmbeddedStreamStoreConnectionCollection.cs │ │ ├── EventStoreRepositoryIntegrationTests.cs │ │ ├── MockStreamStoreConnection.cs │ │ ├── MockStreamStoreConnectionTests.cs │ │ ├── StreamReaderTests.cs │ │ ├── StreamStoreConnectionFixture.cs │ │ ├── StreamStoreReadTests.cs │ │ └── StreamStoreSubscriptionTests.cs │ ├── Foundation │ │ ├── TestAggregate.cs │ │ ├── TestAggregateCreated.cs │ │ ├── TestAggregateMessages.cs │ │ ├── TestWoftamAggregate.cs │ │ └── WoftamEvent.cs │ ├── Messaging │ │ ├── ConcurrentMessageQueue.cs │ │ ├── MessageComparer.cs │ │ ├── TestCommandHandler.cs │ │ ├── TestCommandSubscriber.cs │ │ ├── TestCommands.cs │ │ ├── TestEvents.cs │ │ ├── TestInheritedMessageSubscriber.cs │ │ ├── TestMessageHierarchy.cs │ │ ├── TestMessageSubscriber.cs │ │ ├── TestMessages.cs │ │ └── TestTimeSource.cs │ ├── ReactiveDomain.Testing.csproj │ ├── ReactiveDomain.Testing.csproj.DotSettings │ ├── Specifications │ │ ├── DispatcherSpecification.cs │ │ ├── MockRepositorySpecification.cs │ │ ├── MockRepositoryTests.cs │ │ ├── NullComfiguredConnection.cs │ │ ├── NullConnection.cs │ │ ├── NullListener.cs │ │ ├── NullReader.cs │ │ ├── NullRepository.cs │ │ ├── ReadModelTestSpecification.cs │ │ ├── TestQueue.cs │ │ └── TestQueueTests.cs │ ├── TestPublisher.cs │ └── TestVisibilityHelpers.cs ├── ReactiveDomain.Tools │ ├── EventTransformer │ │ ├── DummyEventTransformer.cs │ │ ├── EventTransformer.csproj │ │ └── TransformerFactory.cs │ └── Shovel │ │ ├── App.config │ │ ├── Bootstrap.cs │ │ ├── EventShovel.cs │ │ ├── EventShovelConfig.cs │ │ ├── IEventTransformer.cs │ │ ├── Program.cs │ │ ├── Shovel.csproj │ │ └── Shovel.sln ├── ReactiveDomain.Transport.Tests │ ├── MockTcpConnection.cs │ ├── ReactiveDomain.Transport.Tests.csproj │ ├── TcpBusClientSideTests.cs │ ├── TcpBusServerSideTests.cs │ └── can_serialize_messages.cs ├── ReactiveDomain.Transport │ ├── BufferManagement │ │ ├── BufferManager.cs │ │ ├── BufferPool.cs │ │ ├── BufferPoolStream.cs │ │ ├── UnableToAllocateBufferException.cs │ │ └── UnableToCreateMemoryException.cs │ ├── Formatting │ │ ├── FormatterBase.cs │ │ ├── IMessageFormatter.cs │ │ └── RawMessageFormatter.cs │ ├── Framing │ │ ├── IMessageFramer.cs │ │ ├── LengthPrefixMessageFramer.cs │ │ ├── LengthPrefixMessageFramerWithBufferPool.cs │ │ ├── PackageFramingException.cs │ │ └── StxEtxMessageFramer.cs │ ├── IMonitoredTcpConnection.cs │ ├── ITcpConnection.cs │ ├── Locks │ │ └── SpinLock2.cs │ ├── ReactiveDomain.Transport.csproj │ ├── Serialization │ │ ├── IMessageSerializer.cs │ │ ├── SimpleJsonSerializer.cs │ │ └── TcpMessageEncoder.cs │ ├── SocketArgsPool.cs │ ├── SystemData │ │ ├── InspectionDecision.cs │ │ ├── InspectionResult.cs │ │ ├── TcpCommand.cs │ │ ├── TcpPackage.cs │ │ └── UserCredentials.cs │ ├── TcpBus.cs │ ├── TcpBusClientSide.cs │ ├── TcpBusServerSide.cs │ ├── TcpClientConnector.cs │ ├── TcpConfiguration.cs │ ├── TcpConnection.cs │ ├── TcpConnectionBase.cs │ ├── TcpConnectionLockless.cs │ ├── TcpConnectionMonitor.cs │ ├── TcpConnectionSsl.cs │ ├── TcpInboundMessageHandler.cs │ ├── TcpOutboundMessageHandler.cs │ ├── TcpServerListener.cs │ ├── TcpStats.cs │ ├── TcpTypedConnection.cs │ └── Util │ │ ├── IPEndPointComparer.cs │ │ └── IpEndPointExtensions.cs ├── ReactiveDomain.nuspec ├── ReactiveDomain.sln ├── ReactiveDomain.sln.DotSettings ├── Versions │ └── latest.txt ├── build.props ├── ci.build.imports └── nuget.config └── tools ├── CheckAssemblyVersion.ps1 ├── CreateDebugNuget.ps1 ├── CreateNuget.ps1 └── wget.exe /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. Unless a later match takes precedence, 3 | # @ReactiveDomain/ReactiveDomain-codeowners will be requested for 4 | # review when someone opens a pull request. 5 | * @ReactiveDomain/ReactiveDomain-codeowners -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 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 | **Environment (please complete the following information):** 27 | - .NET version: [e.g. .NET 6] 28 | - ReactiveDomain version: [e.g. 0.8.22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions 4 | url: https://github.com/ReactiveDomain/reactive-domain/discussions 5 | about: 'For general questions about ReactiveDomain, ask in the GitHub discussions' 6 | - name: Chat 7 | url: https://reactivedomain.slack.com 8 | about: 'Join the discussion on Slack' 9 | -------------------------------------------------------------------------------- /.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/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **What does this pull request do?** 2 | 3 | 4 | 5 | **How does this pull request accomplish that goal?** 6 | 7 | 8 | 9 | **Why is this pull request important?** 10 | 11 | 12 | 13 | **Link to any issues that this resolves** 14 | 21 | 22 | 23 | **Checklist** 24 | - [ ] All unit tests in the solution must pass on all versions of .NET that the solution supports 25 | - [ ] Any new code must be covered by at least one unit test 26 | - [ ] All public methods must be documented with XML comments -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: csharp 3 | os: windows 4 | mono: none 5 | dist: trusty 6 | 7 | branches: 8 | only: 9 | - master 10 | - /.*/ 11 | 12 | addons: 13 | apt: 14 | packages: 15 | - powershell 16 | 17 | before_script: 18 | - powershell -executionpolicy unrestricted -File ./tools/CheckAssemblyVersion.ps1 19 | 20 | script: 21 | - choco install dotnet-5.0-sdk 22 | - echo $TRAVIS_BRANCH 23 | - echo $TRAVIS_BUILD_DIR 24 | - echo $TRAVIS_PULL_REQUEST 25 | - echo $TRAVIS_PULL_REQUEST_BRANCH 26 | - echo $TRAVIS_EVENT_TYPE 27 | - echo $STABLE 28 | - dotnet restore ./src/ReactiveDomain.sln -s https://api.nuget.org/v3/index.json 29 | - dotnet msbuild ./src/ReactiveDomain.sln -p:Configuration=Debug 30 | - dotnet msbuild ./src/ReactiveDomain.sln -p:Configuration=Release 31 | # - dotnet test ./src/ReactiveDomain.Messaging.Tests/ReactiveDomain.Messaging.Tests.csproj #TODO: Fix unit test 32 | - dotnet test ./src/ReactiveDomain.Foundation.Tests/ReactiveDomain.Foundation.Tests.csproj 33 | - dotnet test ./src/ReactiveDomain.Transport.Tests/ReactiveDomain.Transport.Tests.csproj 34 | 35 | after_success: 36 | - powershell -executionpolicy unrestricted -File ./tools/CreateNuget.ps1 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ReactiveDomain 2 | 3 | Contributions can take many forms: open an issue, contribute code, enhance the documentation. We welcome contribtutions of any sort to ReactiveDomain. Here are some things to keep in mind when contributing: 4 | 5 | - [Code of Conduct](https://github.com/ReactiveDomain/reactive-domain/CODE_OF_CONDUCT.md) 6 | - [Issues](#issues) 7 | - [Feature Requests](#requests) 8 | - [Coding](#coding) 9 | 10 | ## Issues and Bugs 11 | Found a bug in the code or documentation? Submit an issue. Even better, submit a Pull Request to fix it! 12 | 13 | ## Feature Requests 14 | We welcome well-considered feature requests. For **major features**, please engage with us in [Slack](https://reactivedomain.slack.com) first to discuss it and work out the details. For **minor features**, feel free to submit a Pull Request right away. 15 | 16 | ## Coding 17 | ### Development Environment 18 | - Visual Studio 2022 (with latest patches/updates) 19 | - All versions of .NET currently supported in the solution: .NET Framework 4.8 & .NET 5.0. 20 | 21 | ### Coding Guidelines 22 | When submitting a Pull Request, keep these rules in mind: 23 | - All unit tests in the solution must pass on all versions of .NET that the solution supports 24 | - Any new code must be covered by at least one unit test 25 | - All public methods must be documented with XML comments -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Cloud-Connect 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/oir89k5nyyouqtsm?svg=true)](https://ci.appveyor.com/project/jageall/reactive-domain) 2 | [![Build Status](https://travis-ci.org/linedata/reactive-domain.svg?branch=master)](https://travis-ci.org/linedata/reactive-domain) 3 | [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](code_of_conduct.md) 4 | 5 | # Reactive Domain 6 | 7 | ## Overview 8 | 9 | Reactive Domain is an open source framework for implementing event sourcing in .NET projects using reactive programming principles. This includes interfaces for using [EventStoreDB](https://eventstore.com). It also provides a messaging framework and other tools for using CQRS. 10 | 11 | The framework is highly opinionated. It focuses on using a small number of consistent patterns and design principles in its public interfaces to enable developers to get up to speed quickly. Ease of use and "design for code review" have been the driving forces behind the framework's evolution. Where trade-offs have been necessary, these principles have been emphasized over performance. 12 | 13 | ## Contributing 14 | 15 | Pull requests are welcome! Take a look at the open issues, join our [discussion on Slack](https://reactivedomain.slack.com), or contribute in an area where you see a need. Contributors and participants on our Slack channels are expected to abide by the project's [code of conduct](CODE_OF_CONDUCT.md). Read the full guidelines on [contributing](CONTRIBUTING.md). -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | For help getting started with ReactiveDomain or to discuss issues you encounter, join the [discussion on Slack](https://reactivedomain.slack.com). -------------------------------------------------------------------------------- /Samples/Metadata/PersistedMetadata/Messages.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | 3 | namespace Metadata_Sample_App 4 | { 5 | public class Messages 6 | { 7 | public class Greeting : Message 8 | { 9 | public readonly string Text; 10 | public Greeting(string text) 11 | { 12 | Text = text; 13 | } 14 | } 15 | public class Farewell : Message 16 | { 17 | public readonly string Text; 18 | public Farewell(string text) 19 | { 20 | Text = text; 21 | } 22 | } 23 | public class Sender 24 | { 25 | public string Name; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Samples/Metadata/PersistedMetadata/PersistedMetadata.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | Metadata_Sample_App 7 | enable 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Samples/Metadata/PersistedMetadata/Program.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging.Bus; 2 | using Terminal.Gui; 3 | 4 | namespace Sample1 5 | { 6 | internal class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | Application.Init(); 11 | Application.Top.Add(new Sample1.MessageWindow(new InMemoryBus("message bus"))); 12 | Application.Run(); 13 | 14 | Application.Shutdown(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Samples/Metadata/SimpleMetadata/Messages.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | 3 | namespace Metadata_Sample_App 4 | { 5 | public class Messages 6 | { 7 | public class Greeting : Message 8 | { 9 | public readonly string Text; 10 | public Greeting(string text) 11 | { 12 | Text = text; 13 | } 14 | } 15 | public class Farewell : Message 16 | { 17 | public readonly string Text; 18 | public Farewell(string text) 19 | { 20 | Text = text; 21 | } 22 | } 23 | public class Sender 24 | { 25 | public string Name; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Samples/Metadata/SimpleMetadata/Program.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging.Bus; 2 | using Terminal.Gui; 3 | 4 | namespace Metadata_Sample_App 5 | { 6 | internal class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | Application.Init(); 11 | Application.Top.Add(new Metadata_Sample_App.MessageWindow(new InMemoryBus("message bus"))); 12 | Application.Run(); 13 | 14 | Application.Shutdown(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Samples/Metadata/SimpleMetadata/SimpleMetadata.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | Metadata_Sample_App 7 | enable 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /build/ReactiveDomain.Policy.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | false 8 | 9 | 10 | -------------------------------------------------------------------------------- /build/ReactiveDomain.Testing.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | false 8 | 9 | 10 | -------------------------------------------------------------------------------- /build/ReactiveDomain.UI.Testing.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | false 8 | 9 | 10 | -------------------------------------------------------------------------------- /build/ReactiveDomain.UI.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | false 8 | 9 | 10 | -------------------------------------------------------------------------------- /build/ReactiveDomain.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | false 8 | 9 | 10 | -------------------------------------------------------------------------------- /package.bat: -------------------------------------------------------------------------------- 1 | del .\nupkgs\*.* /q 2 | del .\bld\Debug\*.* /q 3 | del .\bld\pub\*.* /q 4 | dotnet restore .\src\ReactiveDomain.sln 5 | dotnet build .\src\ReactiveDomain.sln -c Debug 6 | dotnet publish .\src\ReactiveDomain.PolicyTool\ReactiveDomain.PolicyTool.csproj -p:PublishProfile=FolderProfile 7 | pwsh.exe -Command "& {.\tools\CreateDebugNuget.ps1 -local002}" 8 | 9 | -------------------------------------------------------------------------------- /publish.bat: -------------------------------------------------------------------------------- 1 | del .\*.nukpg /q 2 | del .\bld\Release\*.* /q 3 | del .\bld\pub\*.* /q 4 | dotnet restore .\src\ReactiveDomain.sln 5 | dotnet build .\src\ReactiveDomain.sln -c Release 6 | dotnet publish .\src\ReactiveDomain.PolicyTool\ReactiveDomain.PolicyTool.csproj -c Release -p:PublishProfile=FolderProfile 7 | pwsh.exe -Command "& {.\tools\CreateNuget.ps1}" 8 | 9 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # xUnit1031: Do not use blocking task operations in test method 4 | dotnet_diagnostic.xUnit1031.severity = suggestion 5 | 6 | # VSSpell001: Spell Check 7 | dotnet_diagnostic.VSSpell001.severity = suggestion 8 | -------------------------------------------------------------------------------- /src/.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveDomain/reactive-domain/05e5268f0ceef1034885905402590486fcb6fcad/src/.nuget/NuGet.exe -------------------------------------------------------------------------------- /src/Dependency.Versions.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.4.1 4 | 1.0.1 5 | 12.0.2 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/PolicyTool.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32210.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveDomain.PolicyTool", "ReactiveDomain.PolicyTool\ReactiveDomain.PolicyTool.csproj", "{EE09B354-435A-4ECA-88E4-21718C3795EE}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {EE09B354-435A-4ECA-88E4-21718C3795EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {EE09B354-435A-4ECA-88E4-21718C3795EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {EE09B354-435A-4ECA-88E4-21718C3795EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {EE09B354-435A-4ECA-88E4-21718C3795EE}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {7D908F06-2C09-4FB2-A4A5-438AFDF4B433} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Audit/AuditRecord.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace ReactiveDomain.Audit 3 | { 4 | public class AuditRecord 5 | { 6 | public int Version = 0; 7 | public Guid PolicyUserId { get; set; } 8 | public Guid CommitId { get; set; } 9 | public string AggregateName { get; set; } 10 | public string EventName { get; set; } 11 | public DateTime EventDateUTC { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/IMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | /// 4 | /// Exposes methods for reading and writing message metadata. 5 | /// 6 | public interface IMetadata 7 | { 8 | /// 9 | /// Gets the metadata object of a type. 10 | /// 11 | /// The type of metadata to retrieve. 12 | /// An object of that type. 13 | T Read(); 14 | /// 15 | /// Tries to get a metadata object of a type. 16 | /// 17 | /// The type of metadata to retrieve. 18 | /// The metadata object that was read. 19 | /// True if a metadata object of the type was found, otherwise false. 20 | bool TryRead(out T value); 21 | /// 22 | /// Adds or replaces a metadata object of a type. 23 | /// 24 | /// The type of metadata to write. 25 | /// The metadata object to write. 26 | void Write(T metadatum); 27 | } 28 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/IMetadataSource.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | /// 4 | /// An interface for types that have . 5 | /// 6 | public interface IMetadataSource 7 | { 8 | /// 9 | /// Gets the object's metadata. 10 | /// 11 | /// The object's . 12 | Metadata ReadMetadata(); 13 | /// 14 | /// Initializes an object's metadata using default values. 15 | /// 16 | /// The initialized . 17 | Metadata Initialize(); 18 | /// 19 | /// Initializes an object using the provided . 20 | /// 21 | /// The to use for initialization. 22 | void Initialize(Metadata md); 23 | } 24 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Logging 4 | { 5 | public enum LogLevel 6 | { 7 | Fatal = 0, 8 | Error = 1, 9 | Info = 2, 10 | Debug = 3, 11 | Trace = 4 12 | } 13 | 14 | public interface ILogger 15 | { 16 | void Flush(TimeSpan? maxTimeToWait = null); 17 | 18 | LogLevel LogLevel { get; } 19 | 20 | void Fatal(string text); 21 | void Error(string text); 22 | void Info(string text); 23 | void Debug(string text); 24 | void Trace(string text); 25 | 26 | void Fatal(string format, params object[] args); 27 | void Error(string format, params object[] args); 28 | void Info(string format, params object[] args); 29 | void Debug(string format, params object[] args); 30 | void Trace(string format, params object[] args); 31 | 32 | void FatalException(Exception exc, string text); 33 | void ErrorException(Exception exc, string text); 34 | void InfoException(Exception exc, string text); 35 | void DebugException(Exception exc, string text); 36 | void TraceException(Exception exc, string text); 37 | 38 | void FatalException(Exception exc, string format, params object[] args); 39 | void ErrorException(Exception exc, string format, params object[] args); 40 | void InfoException(Exception exc, string format, params object[] args); 41 | void DebugException(Exception exc, string format, params object[] args); 42 | void TraceException(Exception exc, string format, params object[] args); 43 | } 44 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Logging/NullLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Logging 4 | { 5 | public class NullLogger : ILogger 6 | { 7 | public void Flush(TimeSpan? maxTimeToWait = null) { } 8 | public LogLevel LogLevel => LogLevel.Fatal; 9 | public void Fatal(string text) { } 10 | public void Error(string text) { } 11 | public void Info(string text) { } 12 | public void Debug(string text) { } 13 | public void Trace(string text) { } 14 | public void Fatal(string format, params object[] args) { } 15 | public void Error(string format, params object[] args) { } 16 | public void Info(string format, params object[] args) { } 17 | public void Debug(string format, params object[] args) { } 18 | public void Trace(string format, params object[] args) { } 19 | public void FatalException(Exception exc, string text) { } 20 | public void ErrorException(Exception exc, string text) { } 21 | public void InfoException(Exception exc, string text) { } 22 | public void DebugException(Exception exc, string text) { } 23 | public void TraceException(Exception exc, string text) { } 24 | public void FatalException(Exception exc, string format, params object[] args) { } 25 | public void ErrorException(Exception exc, string format, params object[] args) { } 26 | public void InfoException(Exception exc, string format, params object[] args) { } 27 | public void DebugException(Exception exc, string format, params object[] args) { } 28 | public void TraceException(Exception exc, string format, params object[] args) { } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/MetadatumNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain 4 | { 5 | /// 6 | /// Indicates that a requested metadata type was not found. 7 | /// 8 | public class MetadatumNotFoundException : Exception { } 9 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/ReactiveDomain.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | $(LibTargetFrameworks) 6 | ReactiveDomain 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/CustomConfigLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Linq; 4 | using System.Xml; 5 | using System.Xml.Serialization; 6 | 7 | namespace ReactiveDomain.Util 8 | { 9 | public class CustomConfigLoader : IConfigurationSectionHandler 10 | { 11 | public object Create(object parent, object configContext, XmlNode section) 12 | { 13 | if (section == null) 14 | { 15 | throw new ArgumentNullException($"XMLNode passed in is null."); 16 | } 17 | 18 | var type = AppDomain.CurrentDomain.GetAssemblies() 19 | .SelectMany(a => a.GetTypes()) 20 | .FirstOrDefault(t => t.Name == section.Name); 21 | 22 | if (type == null) 23 | { 24 | throw new ArgumentException($"Type with name {section.Name} couldn't be found."); 25 | } 26 | 27 | XmlSerializer ser = new XmlSerializer(type, new XmlRootAttribute(section.Name)); 28 | 29 | using (XmlReader reader = new XmlNodeReader(section)) 30 | { 31 | return ser.Deserialize(reader); 32 | } 33 | } 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/Disposer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace ReactiveDomain.Util 3 | { 4 | public class Disposer:IDisposable 5 | { 6 | private Func _disposeFunc; 7 | 8 | public Disposer(Func disposeFunc) 9 | { 10 | _disposeFunc = disposeFunc ?? throw new ArgumentNullException(nameof(disposeFunc)); 11 | } 12 | 13 | private bool _disposed; 14 | public void Dispose() 15 | { 16 | if (_disposed) return; 17 | try{ 18 | _disposeFunc(); 19 | _disposeFunc = null; 20 | } 21 | catch { 22 | //ignore 23 | } 24 | _disposed = true; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/Empty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Util 4 | { 5 | public static class Empty 6 | { 7 | public static readonly byte[] ByteArray = new byte[0]; 8 | public static readonly string[] StringArray = new string[0]; 9 | public static readonly object[] ObjectArray = new object[0]; 10 | 11 | public static readonly Action Action = () => { }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace ReactiveDomain.Util 7 | { 8 | public static class EnumerableExtensions 9 | { 10 | public static IEnumerable Safe(this IEnumerable collection) 11 | { 12 | return collection ?? Enumerable.Empty(); 13 | } 14 | 15 | public static bool Contains(this IEnumerable collection, Predicate condition) 16 | { 17 | return collection.Any(x => condition(x)); 18 | } 19 | 20 | public static bool IsEmpty(this IEnumerable collection) 21 | { 22 | if (collection == null) 23 | return true; 24 | var coll = collection as ICollection; 25 | if (coll != null) 26 | return coll.Count == 0; 27 | return !collection.Any(); 28 | } 29 | 30 | public static bool IsNotEmpty(this IEnumerable collection) 31 | { 32 | return !IsEmpty(collection); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/FileUtils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace ReactiveDomain.Util 4 | { 5 | public static class FileUtils 6 | { 7 | public static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs) 8 | { 9 | // Get the subdirectories for the specified directory. 10 | var dir = new DirectoryInfo(sourceDirName); 11 | 12 | if (!dir.Exists) 13 | throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName); 14 | 15 | var subdirs = copySubDirs ? dir.GetDirectories() : null; 16 | 17 | // If the destination directory doesn't exist, create it. 18 | if (!Directory.Exists(destDirName)) 19 | Directory.CreateDirectory(destDirName); 20 | 21 | // Get the files in the directory and copy them to the new location. 22 | foreach (FileInfo file in dir.GetFiles()) 23 | { 24 | file.CopyTo(Path.Combine(destDirName, file.Name), false); 25 | } 26 | 27 | if (copySubDirs) 28 | { 29 | foreach (DirectoryInfo subdir in subdirs) 30 | { 31 | DirectoryCopy(subdir.FullName, Path.Combine(destDirName, subdir.Name), true); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/FluentExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Util 4 | { 5 | public static class FluentExtensions { 6 | 7 | public static T If(this T t, Func cond, Func builder) where T : class { 8 | return cond() ? builder(t) : t; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/HostName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace ReactiveDomain.Util 5 | { 6 | public class HostName 7 | { 8 | public static string Combine(Uri requestedUrl, string relativeUri, params object[] arg) 9 | { 10 | try 11 | { 12 | return CombineHostNameAndPath(requestedUrl, relativeUri, arg); 13 | } 14 | catch (Exception e) 15 | { 16 | Debug.WriteLine("Failed to combine hostname with relative path: {0}", e.Message); 17 | return relativeUri; 18 | } 19 | } 20 | 21 | private static string CombineHostNameAndPath(Uri requestedUrl, 22 | string relativeUri, 23 | object[] arg) 24 | { 25 | //TODO: encode??? 26 | var path = string.Format(relativeUri, arg); 27 | return new UriBuilder(requestedUrl.Scheme, requestedUrl.Host, requestedUrl.Port, path).Uri.AbsoluteUri; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/IPEndPointComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | 4 | namespace ReactiveDomain.Util 5 | { 6 | public class IPEndPointComparer : IComparer 7 | { 8 | public int Compare(IPEndPoint x, IPEndPoint y) 9 | { 10 | var xx = x.Address.ToString(); 11 | var yy = y.Address.ToString(); 12 | var result = string.CompareOrdinal(xx, yy); 13 | return result == 0 ? x.Port.CompareTo(y.Port) : result; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/IpEndPointExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace ReactiveDomain.Util 4 | { 5 | public static class IpEndPointExtensions 6 | { 7 | public static string ToHttpUrl(this IPEndPoint endPoint, string rawUrl = null) 8 | { 9 | return string.Format("http://{0}:{1}/{2}", 10 | endPoint.Address, 11 | endPoint.Port, 12 | rawUrl != null ? rawUrl.TrimStart('/') : string.Empty); 13 | } 14 | 15 | public static string ToHttpUrl(this IPEndPoint endPoint, string formatString, params object[] args) 16 | { 17 | return string.Format("http://{0}:{1}/{2}", 18 | endPoint.Address, 19 | endPoint.Port, 20 | string.Format(formatString.TrimStart('/'), args)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/MD5Hash.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable MemberCanBePrivate.Global 2 | // ReSharper disable UnusedMember.Global 3 | 4 | using System; 5 | using System.IO; 6 | using System.Security.Cryptography; 7 | 8 | namespace ReactiveDomain.Util 9 | { 10 | public class MD5Hash 11 | { 12 | public static byte[] GetHashFor(Stream s) 13 | { 14 | //when using this, it will calculate from this point to the END of the stream! 15 | using (MD5 md5 = MD5.Create()) 16 | return md5.ComputeHash(s); 17 | } 18 | 19 | public static byte[] GetHashFor(Stream s, int startPosition, long count) 20 | { 21 | Ensure.Nonnegative(count, "count"); 22 | 23 | using (MD5 md5 = MD5.Create()) 24 | { 25 | ContinuousHashFor(md5, s, startPosition, count); 26 | md5.TransformFinalBlock(Empty.ByteArray, 0, 0); 27 | return md5.Hash; 28 | } 29 | } 30 | 31 | public static void ContinuousHashFor(MD5 md5, Stream s, int startPosition, long count) 32 | { 33 | Ensure.NotNull(md5, "md5"); 34 | Ensure.Nonnegative(count, "count"); 35 | 36 | // ReSharper disable once RedundantCheckBeforeAssignment 37 | if (s.Position != startPosition) 38 | s.Position = startPosition; 39 | 40 | var buffer = new byte[4096]; 41 | long toRead = count; 42 | while (toRead > 0) 43 | { 44 | int read = s.Read(buffer, 0, (int)Math.Min(toRead, buffer.Length)); 45 | if (read == 0) 46 | break; 47 | 48 | md5.TransformBlock(buffer, 0, read, null, 0); 49 | toRead -= read; 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/ShellExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace ReactiveDomain.Util 4 | { 5 | public static class ShellExecutor 6 | { 7 | public static string GetOutput(string command, string args = null) 8 | { 9 | var info = new ProcessStartInfo 10 | { 11 | RedirectStandardOutput = true, 12 | UseShellExecute = false, 13 | CreateNoWindow = true, 14 | FileName = command, 15 | Arguments = args ?? string.Empty 16 | }; 17 | 18 | using (var process = Process.Start(info)) 19 | { 20 | // ReSharper disable once PossibleNullReferenceException 21 | var res = process.StandardOutput.ReadToEnd(); 22 | return res; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Core/Util/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Util 2 | { 3 | public static class StringExtensions 4 | { 5 | public static bool IsEmptyString(this string s) 6 | { 7 | return string.IsNullOrEmpty(s); 8 | } 9 | 10 | public static bool IsNotEmptyString(this string s) 11 | { 12 | return !string.IsNullOrEmpty(s); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation.Tests/Domain/CapturingRoute.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Foundation.Tests.Domain 2 | { 3 | public class CapturingRoute 4 | { 5 | public CapturingRoute() 6 | { 7 | Captured = null; 8 | } 9 | 10 | public void Capture(TEvent result) 11 | { 12 | Captured = result; 13 | } 14 | 15 | public object Captured { get; private set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation.Tests/Domain/MetadatumTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Albedo; 3 | using AutoFixture; 4 | using AutoFixture.Idioms; 5 | using Xunit; 6 | 7 | namespace ReactiveDomain.Domain.Tests 8 | { 9 | public class MetadatumTests 10 | { 11 | private readonly Fixture _fixture; 12 | 13 | public MetadatumTests() 14 | { 15 | _fixture = new Fixture(); 16 | } 17 | 18 | [Fact] 19 | public void BothNameAndValueCanNotBeNull() 20 | { 21 | new GuardClauseAssertion(_fixture). 22 | Verify(Constructors.Select(() => new Metadatum(null, null))); 23 | } 24 | 25 | [Fact] 26 | public void BothNameAndValueReturnExpectedResult() 27 | { 28 | new ConstructorInitializedMemberAssertion(_fixture). 29 | Verify(Constructors.Select(() => new Metadatum("name", "value"))); 30 | } 31 | 32 | [Fact] 33 | public void ToKeyValuePairReturnsExpectedResult() 34 | { 35 | var sut = new Metadatum("name", "value"); 36 | var result = sut.ToKeyValuePair(); 37 | Assert.Equal(new KeyValuePair("name", "value"), result); 38 | } 39 | 40 | [Fact] 41 | public void VerifyEquality() 42 | { 43 | new CompositeIdiomaticAssertion( 44 | new EqualsNewObjectAssertion(_fixture), 45 | new EqualsNullAssertion(_fixture), 46 | new EqualsSelfAssertion(_fixture), 47 | new GetHashCodeSuccessiveAssertion(_fixture), 48 | new EqualsSuccessiveAssertion(_fixture)) 49 | .Verify(typeof(Metadatum)); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation.Tests/EmbeddedStreamStoreConnectionCollection.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Testing; 2 | using Xunit; 3 | 4 | namespace ReactiveDomain.Foundation.Tests 5 | { 6 | //n.b a copy of this marker class must be in the assembly with the test classes 7 | //copy and place in the local namespace to avoid collisions 8 | [CollectionDefinition(nameof(EmbeddedStreamStoreConnectionCollection))] 9 | public class EmbeddedStreamStoreConnectionCollection : ICollectionFixture 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation.Tests/Logging/with_message_logging_enabled.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using ReactiveDomain.Foundation.EventStore; 4 | using ReactiveDomain.Messaging.Bus; 5 | 6 | namespace ReactiveDomain.Foundation.Tests.Logging 7 | { 8 | // ReSharper disable once InconsistentNaming 9 | public abstract class with_message_logging_enabled :IDisposable 10 | { 11 | protected readonly IStreamStoreConnection Connection; 12 | protected IDispatcher Bus; 13 | protected EventStoreMessageLogger Logging; 14 | protected string StreamName = $"LogTest-{Guid.NewGuid():N}"; 15 | protected StreamStoreRepository Repo; 16 | protected PrefixedCamelCaseStreamNameBuilder StreamNameBuilder; 17 | protected IEventSerializer EventSerializer; 18 | 19 | protected with_message_logging_enabled(IStreamStoreConnection connection) 20 | { 21 | Connection = connection; 22 | Bus = new Dispatcher(nameof(with_message_logging_enabled)); 23 | StreamNameBuilder = new PrefixedCamelCaseStreamNameBuilder("UnitTest"); 24 | EventSerializer = new JsonMessageSerializer(); 25 | Repo = new StreamStoreRepository(StreamNameBuilder, Connection, new JsonMessageSerializer()); 26 | // instantiate Logger class that inherits from QueuedSubscriber 27 | Logging = new EventStoreMessageLogger(Bus, 28 | Connection, 29 | StreamName, 30 | true); 31 | } 32 | 33 | 34 | public void Dispose() { 35 | Dispose(true); 36 | GC.SuppressFinalize(this); 37 | } 38 | 39 | protected virtual void Dispose(bool disposing) { 40 | if (!disposing) return; 41 | 42 | Bus?.Dispose(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation.Tests/StreamListenerTests/Common/CommonHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ReactiveDomain.Messaging; 3 | using ReactiveDomain.Testing; 4 | 5 | namespace ReactiveDomain.Foundation.Tests.StreamListenerTests.Common 6 | { 7 | internal static class CommonHelpers 8 | { 9 | internal static void WaitForStream(IStreamStoreConnection conn, string streamName) 10 | { 11 | //wait for the category projection to be written 12 | AssertEx.IsOrBecomesTrue( 13 | () => 14 | { 15 | try 16 | { 17 | return conn.ReadStreamForward(streamName, 0, 1) != null; 18 | } 19 | catch 20 | { 21 | return false; 22 | } 23 | }, 24 | 2000, 25 | $"Stream '{streamName}' not created"); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation.Tests/StreamListenerTests/Common/TestStreamNameBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Foundation.Tests.StreamListenerTests.Common 4 | { 5 | /// 6 | /// Generate stream names for testing. 7 | /// 8 | /// 9 | /// todo: 10 | /// The use of the extra Guid doesn't match the generation by the 11 | /// and the checks for existing category and event streams fail. 12 | /// tests the stream name generation. So switching to the PrefixedCamelCaseStreamNameBuilder. Leaving these here so Chris can 13 | /// agree and remove or correct me. 14 | /// end todo 15 | /// 16 | internal class TestStreamNameBuilder : IStreamNameBuilder 17 | { 18 | private readonly Guid _testRunGuid; 19 | 20 | public TestStreamNameBuilder(Guid testRunGuid) 21 | { 22 | _testRunGuid = testRunGuid; 23 | } 24 | public string GenerateForAggregate(Type type, Guid id) 25 | { 26 | return $"{type.Name}-{id:N}{_testRunGuid:N}"; 27 | } 28 | 29 | public string GenerateForCategory(Type type) 30 | { 31 | //mock category stream, can't use $ here 32 | return $"ce-{type.Name}{_testRunGuid:N}"; 33 | } 34 | 35 | public string GenerateForEventType(string type) 36 | { 37 | //mock event type stream, can't use $ here 38 | return $"et-{type}{_testRunGuid:N}"; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation.Tests/can_serialize_correlated_messages.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | using ReactiveDomain.Testing; 3 | using System; 4 | using Xunit; 5 | 6 | namespace ReactiveDomain.Foundation.Tests { 7 | 8 | // ReSharper disable once InconsistentNaming 9 | public class when_serializing_correlated_messages { 10 | [Fact] 11 | public void can_use_json_message_serializer() { 12 | 13 | var evt = new TestEvent(); 14 | var evt2 = MessageBuilder 15 | .From(evt) 16 | .Build(()=> new TestEvent()); 17 | 18 | var serializer = new JsonMessageSerializer(); 19 | 20 | var data = serializer.Serialize(evt); 21 | var data2 = serializer.Serialize(evt2); 22 | 23 | var dEvent = (TestEvent)serializer.Deserialize(data); 24 | var dEvent2 = (TestEvent)serializer.Deserialize(data2); 25 | 26 | Assert.Equal(evt.MsgId,dEvent.MsgId); 27 | Assert.Equal(evt.CausationId,dEvent.CausationId); 28 | Assert.Equal(evt.CorrelationId,dEvent.CorrelationId); 29 | 30 | Assert.Equal(evt2.MsgId,dEvent2.MsgId); 31 | Assert.Equal(evt2.CausationId,dEvent2.CausationId); 32 | Assert.Equal(evt2.CorrelationId, dEvent2.CorrelationId); 33 | } 34 | public class TestEvent : ICorrelatedMessage { 35 | public Guid MsgId { get; private set; } 36 | public TestEvent() 37 | { 38 | MsgId = Guid.NewGuid(); 39 | } 40 | public Guid CorrelationId { get; set; } 41 | public Guid CausationId { get; set; } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/BootStrap.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using ReactiveDomain.Logging; 3 | 4 | namespace ReactiveDomain.Foundation 5 | { 6 | public static class BootStrap 7 | { 8 | private static readonly ILogger Log = LogManager.GetLogger("ReactiveDomain"); 9 | private static readonly string AssemblyName; 10 | static BootStrap() 11 | { 12 | var fullName = Assembly.GetExecutingAssembly().FullName; 13 | Log.Info(fullName + " Loaded."); 14 | AssemblyName = fullName.Split(new[] { ',' })[0]; 15 | 16 | } 17 | public static void Load() 18 | { 19 | Log.Info(AssemblyName + " Loaded."); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/Domain/ChildEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Foundation.Domain 4 | { 5 | public abstract class ChildEntity 6 | { 7 | public readonly Guid Id; 8 | 9 | private readonly Action _raise; 10 | private readonly EventRouter _router; 11 | 12 | protected ChildEntity( 13 | Guid id, 14 | AggregateRoot root) { 15 | Id = id; 16 | root.RegisterChild(this, out _raise, out _router ); 17 | } 18 | /// 19 | /// Registers a route for the specified type of event to the logic that needs to be applied to this instance to support future behaviors. 20 | /// n.b. A ChildAggregate will need to filter events by Id before applying 21 | /// 22 | /// The type of event. 23 | /// The logic to route the event to. 24 | protected void Register(Action route) { 25 | _router.RegisterRoute(route); 26 | } 27 | 28 | /// 29 | /// Registers a route for the specified type of event to the logic that needs to be applied to this instance to support future behaviors. 30 | /// n.b. A ChildAggregate will need to filter events by Id before applying 31 | /// 32 | /// The type of event. 33 | /// The logic to route the event to. 34 | protected void Register(Type typeOfEvent, Action route) { 35 | _router.RegisterRoute(typeOfEvent, route); 36 | } 37 | protected void Raise(object @event) { 38 | _raise(@event); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/Domain/EventRecorder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | // ReSharper disable once CheckNamespace 5 | namespace ReactiveDomain 6 | { 7 | /// 8 | /// Records events on behalf of an event source. 9 | /// 10 | public class EventRecorder 11 | { 12 | private readonly List _recorded; 13 | 14 | /// 15 | /// Initializes a new event recorder. 16 | /// 17 | public EventRecorder() 18 | { 19 | _recorded = new List(); 20 | } 21 | 22 | /// 23 | /// Indicates whether this instance has recorded events. 24 | /// 25 | public bool HasRecordedEvents => _recorded.Count != 0; 26 | 27 | /// 28 | /// The events recorded by the event source that holds on to this instance. 29 | /// 30 | public object[] RecordedEvents => _recorded.ToArray(); 31 | 32 | /// 33 | /// Records an event on this instance. 34 | /// 35 | /// The event to record. 36 | /// Thrown when is null. 37 | public void Record(object @event) 38 | { 39 | if (@event == null) 40 | throw new ArgumentNullException(nameof(@event)); 41 | 42 | _recorded.Add(@event); 43 | } 44 | 45 | /// 46 | /// Resets this instance to its starting point or the point it was last reset on, effectively forgetting all events that have been recorded in the meantime. 47 | /// 48 | public void Reset() 49 | { 50 | _recorded.Clear(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/Domain/ICorrelatedEventSource.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | 3 | namespace ReactiveDomain 4 | { 5 | /// 6 | /// Represents a source of correlated events with the ablity to inject a correlation source. To be used by infrastructure code only. 7 | /// 8 | public interface ICorrelatedEventSource 9 | { 10 | /// 11 | /// Sets the source event to apply the corrolation and causation ids. 12 | /// 13 | ICorrelatedMessage Source { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/Domain/ISnapshotSource.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | /// 4 | /// Represents a source of snapshots from the perspective of restoring from or taking snapshots. To be used by infrastructure code only. 5 | /// 6 | public interface ISnapshotSource 7 | { 8 | /// 9 | /// Restores this instance from a snapshot using the specified object. 10 | /// 11 | /// The object to restore the snapshot from. 12 | void RestoreFromSnapshot(object snapshot); 13 | 14 | /// 15 | /// Takes a snapshot of this instance. 16 | /// 17 | /// The object that represents the snapshot. 18 | object TakeSnapshot(); 19 | } 20 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/Domain/ProcessManager.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | using ReactiveDomain.Messaging.Bus; 3 | 4 | 5 | namespace ReactiveDomain.Foundation.Domain 6 | { 7 | public abstract class ProcessManager: AggregateRoot, IHandle 8 | { 9 | public ProcessManager(ICorrelatedMessage source = null):base(source) { 10 | Register(msg => {/* input messages have no apply action, saved for audit only*/ }); 11 | } 12 | 13 | public abstract void Handle(IMessage message); 14 | 15 | protected void RecordInput(InputMsg recievedMsg) { 16 | Raise(new InputMsg(recievedMsg)); 17 | } 18 | public class InputMsg:Message { 19 | public IMessage Received { get; private set; } 20 | public InputMsg(IMessage recieved) 21 | { 22 | Received = recieved; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/IConfiguredConnection.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | using ReactiveDomain.Messaging.Bus; 3 | using System; 4 | 5 | namespace ReactiveDomain.Foundation 6 | { 7 | public interface IConfiguredConnection 8 | { 9 | IStreamStoreConnection Connection { get; } 10 | IStreamNameBuilder StreamNamer { get; } 11 | IEventSerializer Serializer { get; } 12 | IListener GetListener(string name); 13 | IListener GetQueuedListener(string name); 14 | IStreamReader GetReader(string name, Action handle); 15 | IRepository GetRepository(bool caching = false, Func currentPolicyUserId = null); 16 | ICorrelatedRepository GetCorrelatedRepository(IRepository baseRepository = null, bool caching = false, Func currentPolicyUserId = null); 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/ICorrelatedRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Messaging; 3 | 4 | namespace ReactiveDomain.Foundation { 5 | public interface ICorrelatedRepository 6 | { 7 | bool TryGetById(Guid id, out TAggregate aggregate, ICorrelatedMessage source) where TAggregate : AggregateRoot, IEventSource; 8 | bool TryGetById(Guid id, int version, out TAggregate aggregate, ICorrelatedMessage source) where TAggregate : AggregateRoot, IEventSource; 9 | TAggregate GetById(Guid id, ICorrelatedMessage source) where TAggregate : AggregateRoot, IEventSource; 10 | TAggregate GetById(Guid id, int version, ICorrelatedMessage source) where TAggregate : AggregateRoot, IEventSource; 11 | void Save(IEventSource aggregate); 12 | void Delete(IEventSource aggregate); 13 | void HardDelete(IEventSource aggregate); 14 | } 15 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace ReactiveDomain.Foundation 3 | { 4 | public interface IRepository 5 | { 6 | bool TryGetById(Guid id, out TAggregate aggregate, int version = int.MaxValue) where TAggregate : class, IEventSource; 7 | TAggregate GetById(Guid id, int version = int.MaxValue) where TAggregate : class, IEventSource; 8 | void Update(ref TAggregate aggregate, int version = int.MaxValue) where TAggregate : class, IEventSource; 9 | void Save(IEventSource aggregate); 10 | void Delete(IEventSource aggregate); 11 | void HardDelete(IEventSource aggregate); 12 | } 13 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/IStreamNameBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Foundation 4 | { 5 | public interface IStreamNameBuilder 6 | { 7 | /// 8 | /// Generate a standard stream name for a given aggregate id 9 | /// 10 | /// 11 | /// 12 | /// 13 | string GenerateForAggregate(Type type, Guid id); 14 | 15 | /// 16 | /// Generate a stream name for a category 17 | /// 18 | /// 19 | /// 20 | string GenerateForCategory(Type type); 21 | 22 | /// 23 | /// Generate a stream name for an event type 24 | /// 25 | /// 26 | /// 27 | string GenerateForEventType(string type); 28 | } 29 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/ReactiveDomain.Foundation.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(LibTargetFrameworks) 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/RepositoryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Foundation 2 | { 3 | public static class RepositoryExtensions 4 | { 5 | public static void Save(this IRepository repository, IEventSource aggregate) 6 | { 7 | repository.Save(aggregate); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/StreamStore/AggregateDeletedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // ReSharper disable MemberCanBePrivate.Global 4 | // ReSharper disable NotAccessedField.Global 5 | // ReSharper disable once CheckNamespace 6 | namespace ReactiveDomain.Foundation 7 | { 8 | public class AggregateDeletedException : Exception 9 | { 10 | 11 | public readonly Guid Id; 12 | public readonly Type Type; 13 | 14 | public AggregateDeletedException(Guid id, Type type) 15 | : base($"Aggregate '{id}' (type {type.Name}) was deleted.") 16 | { 17 | Id = id; 18 | Type = type; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/StreamStore/AggregateNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // ReSharper disable MemberCanBePrivate.Global 4 | // ReSharper disable NotAccessedField.Global 5 | // ReSharper disable once CheckNamespace 6 | namespace ReactiveDomain.Foundation 7 | { 8 | public class AggregateNotFoundException : Exception 9 | { 10 | public readonly Guid Id; 11 | public readonly Type Type; 12 | 13 | public AggregateNotFoundException(Guid id, Type type) 14 | : base($"Aggregate '{id}' (type {type.Name}) was not found.") 15 | { 16 | Id = id; 17 | Type = type; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/StreamStore/AggregateVersionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // ReSharper disable MemberCanBePrivate.Global 4 | // ReSharper disable NotAccessedField.Global 5 | // ReSharper disable once CheckNamespace 6 | namespace ReactiveDomain.Foundation 7 | { 8 | public class AggregateVersionException : Exception 9 | { 10 | public readonly Guid Id; 11 | public readonly Type Type; 12 | public readonly long AggregateVersion; 13 | public readonly long RequestedVersion; 14 | 15 | public AggregateVersionException(Guid id, Type type, long aggregateVersion, long requestedVersion) 16 | : base(string.Format("Requested version {2} of aggregate '{0}' (type {1}) - aggregate version is {3}", id, type.Name, requestedVersion, aggregateVersion)) 17 | { 18 | Id = id; 19 | Type = type; 20 | AggregateVersion = aggregateVersion; 21 | RequestedVersion = requestedVersion; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/StreamStore/CommonMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace ReactiveDomain.Foundation.StreamStore 6 | { 7 | public class CommonMetadata 8 | { 9 | public int Version = 1; //legacy string dictionary metadata is implicitly version 0 10 | public Guid CommitId { get; set; } 11 | public string AggregateName { get; set; } 12 | public string EventName { get; set; } 13 | public string EventAssembly { get; set; } 14 | public string EventFullyQualifiedName { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/StreamStore/ContractResolver.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | using System.Reflection; 4 | 5 | // ReSharper disable once CheckNamespace 6 | namespace ReactiveDomain.Foundation 7 | { 8 | public class ContractResolver : DefaultContractResolver { 9 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { 10 | var property = base.CreateProperty(member, memberSerialization); 11 | property.Writable = CanSetMemberValue(member, true); 12 | return property; 13 | } 14 | 15 | public static bool CanSetMemberValue(MemberInfo member, bool nonPublic) { 16 | switch (member.MemberType) { 17 | case MemberTypes.Field: 18 | var fieldInfo = (FieldInfo)member; 19 | 20 | return nonPublic || fieldInfo.IsPublic; 21 | case MemberTypes.Property: 22 | var propertyInfo = (PropertyInfo)member; 23 | 24 | if (!propertyInfo.CanWrite) 25 | return false; 26 | if (nonPublic) 27 | return true; 28 | // ReSharper disable once ConditionIsAlwaysTrueOrFalse 29 | return (propertyInfo.GetSetMethod(nonPublic) != null); 30 | default: 31 | return false; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/StreamStore/IAggregateCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Foundation.StreamStore 4 | { 5 | /// 6 | /// While it might seem more natural to save and restore the event set behind the aggregate, 7 | /// this cache stores only the collapsed state in the aggregate 8 | /// 9 | public interface IAggregateCache : IRepository, IDisposable 10 | { 11 | bool Remove(Guid id); 12 | void Clear(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/StreamStore/IEventSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | // ReSharper disable once CheckNamespace 5 | namespace ReactiveDomain.Foundation { 6 | public interface IEventSerializer { 7 | EventData Serialize(object @event, IDictionary headers = null); 8 | object Deserialize(IEventData @event); 9 | Type FindType(string typeName); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/StreamStore/ReadModelState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ReactiveDomain.Util; 4 | 5 | // ReSharper disable once CheckNamespace 6 | namespace ReactiveDomain.Foundation 7 | { 8 | public class ReadModelState 9 | { 10 | public readonly string ModelName; 11 | public readonly List> Checkpoints; 12 | public readonly object State; 13 | 14 | public ReadModelState( 15 | string modelName, 16 | List> checkpoint, 17 | object state) { 18 | Ensure.NotNullOrEmpty(modelName,nameof(modelName)); 19 | ModelName = modelName; 20 | Checkpoints = checkpoint; 21 | State = state; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Foundation/StreamStore/StreamStoreMsgs.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | using System; 3 | 4 | // ReSharper disable once CheckNamespace 5 | namespace ReactiveDomain.Foundation 6 | { 7 | public class StreamStoreMsgs 8 | { 9 | public class CatchupSubscriptionBecameLive : IMessage 10 | { 11 | public Guid MsgId { get; private set; } 12 | public CatchupSubscriptionBecameLive() 13 | { 14 | MsgId = Guid.NewGuid(); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ReactiveDomain.IdentityStorage.Tests/ClientStoreTests.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 ReactiveDomain.IdentityStorage.Tests 8 | { 9 | internal class ClientStoreTests 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ReactiveDomain.IdentityStorage.Tests/MockPrincipal.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.IdentityStorage.ReadModels; 2 | 3 | namespace ReactiveDomain.IdentityStorage.Tests 4 | { 5 | internal class MockPrincipal : IPrincipal 6 | { 7 | public string Provider { get; set; } 8 | 9 | public string Domain { get; set; } 10 | 11 | public string SId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ReactiveDomain.IdentityStorage.Tests/TestMessages.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | 3 | namespace ReactiveDomain.IdentityStorage.Tests 4 | { 5 | class TestMessages 6 | { 7 | public class RootCommand : Command 8 | { } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ReactiveDomain.IdentityStorage/ReactiveDomain.IdentityStorage.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(LibTargetFrameworks) 5 | true 6 | 7 | 8 | 9 | <_Parameter1>$(AssemblyName).Tests 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/ReactiveDomain.IdentityStorage/ReadModels/PrincipalWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.DirectoryServices.AccountManagement; 2 | using ReactiveDomain.Util; 3 | using System.Runtime.Versioning; 4 | 5 | namespace ReactiveDomain.IdentityStorage.ReadModels 6 | { 7 | 8 | public interface IPrincipal 9 | { 10 | string Provider { get; } 11 | string Domain { get; } 12 | string SId { get; } 13 | } 14 | #if NETCOREAPP 15 | [SupportedOSPlatform("windows")] 16 | #endif 17 | public class PrincipalWrapper : IPrincipal 18 | { 19 | private readonly UserPrincipal _principal; 20 | 21 | public PrincipalWrapper(UserPrincipal principal) 22 | { 23 | Ensure.NotNull(principal, nameof(principal)); 24 | _principal = principal; 25 | } 26 | 27 | public string Provider => _principal.ContextType.ToString(); 28 | 29 | public string Domain => _principal.Context.Name; 30 | 31 | public string SId => _principal.Sid.ToString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ReactiveDomain.IdentityStorage/SubjectExceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.IdentityStorage 4 | { 5 | /// 6 | /// An attempt was made to add a duplicate subject to the system. 7 | /// 8 | public class DuplicateSubjectException : Exception 9 | { 10 | /// 11 | /// An attempt was made to add a duplicate subject to the system. 12 | /// 13 | public DuplicateSubjectException(string authProvider, string authDomain, string userName) 14 | : base($"User {authDomain}\\{userName} with provider {authProvider} already exists.") 15 | { } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ReactiveDomain.IdentityStorage/UserExceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.IdentityStorage 4 | { 5 | /// 6 | /// An attempt was made to add a duplicate user to the system. 7 | /// 8 | public class DuplicateUserException : Exception 9 | { 10 | /// 11 | /// An attempt was made to add a duplicate user to the system. 12 | /// 13 | public DuplicateUserException(Guid id, string fullName, string email) 14 | : base($"User {id}: {fullName}\\{email} already exists.") 15 | { } 16 | } 17 | 18 | /// 19 | /// Throw this exception when a user lookup returns no results. 20 | /// 21 | public class UserNotFoundException : Exception 22 | { 23 | /// 24 | /// Throw this exception when a user lookup returns no results. 25 | /// 26 | public UserNotFoundException(string message) 27 | : base(message) 28 | { 29 | } 30 | } 31 | 32 | /// 33 | /// Throw this exception when a user lookup returns a user but that user is deactivated. 34 | /// 35 | public class UserDeactivatedException : Exception 36 | { 37 | /// 38 | /// Throw this exception when a user lookup returns a user but that user is deactivated. 39 | /// 40 | public UserDeactivatedException(string message) 41 | : base(message) 42 | { 43 | } 44 | } 45 | 46 | public class DuplicateClientException : Exception 47 | { 48 | public DuplicateClientException(Guid id, string name) 49 | : base($"Client {name} with ID {id} already exists.") 50 | { } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging.Tests/MessageExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace ReactiveDomain.Messaging.Tests 4 | { 5 | public class MessageExtensionsTests 6 | { 7 | [Fact] 8 | public void can_write_and_read_metadatum() 9 | { 10 | var metadatum = new CustomMetadata { Data = "Test" }; 11 | var message = new TestEvent(); 12 | message.WriteMetadatum(metadatum); 13 | 14 | var md = message.ReadMetadatum(); 15 | Assert.Equal(metadatum.Data, md.Data); 16 | } 17 | 18 | [Fact] 19 | public void read_metadatum_throws_if_not_found() 20 | { 21 | var message = new TestEvent(); 22 | Assert.Throws(() => message.ReadMetadatum()); 23 | } 24 | 25 | [Fact] 26 | public void can_try_read_metadatum() 27 | { 28 | var metadatum = new CustomMetadata { Data = "Test" }; 29 | var message = new TestEvent(); 30 | message.WriteMetadatum(metadatum); 31 | 32 | Assert.True(message.TryReadMetadatum(out var md)); 33 | Assert.Equal(metadatum.Data, md.Data); 34 | } 35 | 36 | [Fact] 37 | public void try_read_metadatum_reports_if_not_found() 38 | { 39 | var message = new TestEvent(); 40 | Assert.False(message.TryReadMetadatum(out var md)); 41 | Assert.Equal(default, md); 42 | } 43 | 44 | public class TestEvent : Event { } 45 | 46 | public class CustomMetadata 47 | { 48 | public string Data; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging.Tests/ReactiveDomain.Messaging.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(TestTargetFrameworks) 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging.Tests/RemoteBusFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using ReactiveDomain.Messaging.Bus; 4 | 5 | namespace ReactiveDomain.Messaging.Tests { 6 | public class RemoteBusFixture : IDisposable { 7 | public Dispatcher LocalBus { get; } 8 | public Dispatcher RemoteBus { get; } 9 | public long LocalMsgCount; 10 | public long RemoteMsgCount; 11 | public readonly TimeSpan StandardTimeout; 12 | private readonly BusConnector _connector; 13 | 14 | public RemoteBusFixture() { 15 | StandardTimeout = TimeSpan.FromMilliseconds(200); 16 | LocalBus = new Dispatcher(nameof(TestCommandBusFixture), 1, false, StandardTimeout, StandardTimeout); 17 | RemoteBus = new Dispatcher(nameof(TestCommandBusFixture), 1, false, StandardTimeout, StandardTimeout); 18 | 19 | LocalBus.SubscribeToAll(new AdHocHandler(_ => Interlocked.Increment(ref LocalMsgCount))); 20 | RemoteBus.SubscribeToAll(new AdHocHandler(_ => Interlocked.Increment(ref RemoteMsgCount))); 21 | 22 | _connector = new BusConnector(LocalBus, RemoteBus); 23 | 24 | Reset(); 25 | } 26 | public void Reset() { 27 | Interlocked.Exchange(ref LocalMsgCount, 0); 28 | Interlocked.Exchange(ref RemoteMsgCount, 0); 29 | } 30 | protected virtual void Dispose(bool disposing) { 31 | if (disposing) { 32 | LocalBus?.Dispose(); 33 | RemoteBus?.Dispose(); 34 | _connector?.Dispose(); 35 | } 36 | } 37 | public void Dispose() { 38 | Dispose(true); 39 | GC.SuppressFinalize(this); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging.Tests/when_creating_a_message.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Testing; 3 | using Xunit; 4 | 5 | namespace ReactiveDomain.Messaging.Tests 6 | { 7 | // ReSharper disable InconsistentNaming 8 | public class when_creating_a_message 9 | { 10 | [Fact] 11 | public void new_message_with_default_constructor_should_have_an_id() 12 | { 13 | var msg1 = new TestMessage(); 14 | Assert.NotEqual(Guid.Empty, msg1.MsgId); 15 | } 16 | [Fact] 17 | public void new_message_with_default_constructor_should_have_empty_metadata() 18 | { 19 | var msg1 = new TestMessage(); 20 | var md = msg1.ReadMetadata(); 21 | Assert.Null(md); 22 | Assert.NotEqual(Guid.Empty, msg1.MsgId); 23 | } 24 | 25 | [Fact] 26 | public void new_message_with_default_constructor_should_have_new_id() 27 | { 28 | var msg1 = new TestMessage(); 29 | Assert.NotEqual(Guid.Empty, msg1.MsgId); 30 | var msg2 = new TestMessage(); 31 | Assert.NotEqual(Guid.Empty, msg2.MsgId); 32 | Assert.NotEqual(msg1.MsgId, msg2.MsgId); 33 | } 34 | } 35 | // ReSharper restore InconsistentNaming 36 | } 37 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging.Tests/when_ensuring_values.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Util; 3 | using Xunit; 4 | 5 | // ReSharper disable InconsistentNaming 6 | 7 | namespace ReactiveDomain.Messaging.Tests 8 | { 9 | public class when_ensuring_values 10 | { 11 | [Fact] 12 | public void can_ensure_not_default_datetime() 13 | { 14 | Ensure.NotDefault(DateTime.Now, "DateTime"); 15 | Assert.Throws(() => Ensure.NotDefault(new DateTime(), "DateTime")); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/BootStrap.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using ReactiveDomain.Logging; 3 | 4 | namespace ReactiveDomain.Messaging 5 | { 6 | //this class is to help force assembly loading when building the message hierarchy 7 | public static class BootStrap 8 | { 9 | private static readonly ILogger Log = LogManager.GetLogger("ReactiveDomain.Messaging"); 10 | private static readonly string AssemblyName; 11 | static BootStrap() 12 | { 13 | var fullName = Assembly.GetExecutingAssembly().FullName; 14 | Log.Info(fullName + " Loaded."); 15 | AssemblyName = fullName.Split(new[] { ',' })[0]; 16 | 17 | } 18 | public static void Load() 19 | { 20 | Log.Info(AssemblyName + " Configured."); 21 | } 22 | public static void Configure() 23 | { 24 | Log.Info(AssemblyName + " Configured."); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/AdHocCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Util; 3 | 4 | namespace ReactiveDomain.Messaging.Bus 5 | { 6 | public class AdHocCommandHandler : IHandleCommand where T : Command 7 | { 8 | private readonly Func _handleCommand; 9 | private readonly bool _wrapExceptions; 10 | private Guid _currentCommand = Guid.Empty; 11 | 12 | 13 | public AdHocCommandHandler( 14 | Func handleCommandCommand, 15 | bool wrapExceptions = true) 16 | { 17 | Ensure.NotNull(handleCommandCommand, "handle"); 18 | _handleCommand = handleCommandCommand; 19 | _wrapExceptions = wrapExceptions; 20 | } 21 | 22 | public CommandResponse Handle(T command) 23 | { 24 | bool passed; 25 | _currentCommand = command.MsgId; 26 | try 27 | { 28 | if (command.IsCanceled) 29 | return command.Canceled(); 30 | 31 | passed = _handleCommand(command); 32 | } 33 | catch (Exception ex) 34 | { 35 | if (_wrapExceptions) 36 | return command.Fail(ex); 37 | throw; 38 | } 39 | if (command.IsCanceled) 40 | return command.Canceled(); 41 | 42 | return passed ? command.Succeed() : command.Fail(); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/AdHocHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Util; 3 | 4 | namespace ReactiveDomain.Messaging.Bus 5 | { 6 | public class AdHocHandler : IHandle where T : class, IMessage 7 | { 8 | private readonly Action _handle; 9 | 10 | public AdHocHandler(Action handle) 11 | { 12 | Ensure.NotNull(handle, "handle"); 13 | _handle = handle; 14 | } 15 | 16 | public void Handle(T message) 17 | { 18 | _handle(message); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/AdHocTypedCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Util; 3 | 4 | namespace ReactiveDomain.Messaging.Bus 5 | { 6 | public class AdHocTypedCommandHandler : IHandleCommand where T : Command 7 | where R : CommandResponse 8 | { 9 | private readonly Func _handleCommand; 10 | private readonly bool _wrapExceptions; 11 | 12 | 13 | public AdHocTypedCommandHandler( 14 | Func handleCommandCommand, 15 | bool wrapExceptions = true) 16 | { 17 | Ensure.NotNull(handleCommandCommand, "handle"); 18 | _handleCommand = handleCommandCommand; 19 | _wrapExceptions = wrapExceptions; 20 | } 21 | 22 | public CommandResponse Handle(T command) 23 | { 24 | try 25 | { 26 | return _handleCommand(command); 27 | } 28 | catch (Exception ex) 29 | { 30 | if (_wrapExceptions) 31 | return command.Fail(ex); 32 | throw; 33 | } 34 | 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/CommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Logging; 3 | 4 | namespace ReactiveDomain.Messaging.Bus 5 | { 6 | public class CommandHandler : IHandle where T : class, ICommand 7 | { 8 | // ReSharper disable once StaticMemberInGenericType 9 | private static readonly ILogger Log = LogManager.GetLogger("ReactiveDomain"); 10 | private readonly IPublisher _bus; 11 | private readonly IHandleCommand _handler; 12 | public CommandHandler(IPublisher bus, IHandleCommand handler) 13 | { 14 | _bus = bus; 15 | _handler = handler; 16 | } 17 | 18 | public void Handle(T message) 19 | { 20 | _bus.Publish(new AckCommand(message)); 21 | try 22 | { 23 | if (Log.LogLevel >= LogLevel.Debug) 24 | Log.Debug("{0} command handled by {1}", message.GetType().Name, _handler.GetType().Name); 25 | _bus.Publish(_handler.Handle(message)); 26 | } 27 | catch (Exception ex) 28 | { 29 | _bus.Publish(message.Fail(ex)); 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/DelaySendEnvelope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging.Bus { 4 | public class DelaySendEnvelope : IMessage { 5 | public Guid MsgId { get; private set; } 6 | public readonly TimePosition At; 7 | public readonly IMessage ToSend; 8 | 9 | public DelaySendEnvelope(TimePosition at, IMessage toSend) { 10 | MsgId = Guid.NewGuid(); 11 | At = at; 12 | ToSend = toSend; 13 | } 14 | public DelaySendEnvelope(ITimeSource timeSource, TimeSpan delay, IMessage toSend) : 15 | this(timeSource.Now() + delay, toSend) {} 16 | } 17 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/DynamicHandler.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Messaging.Bus 2 | { 3 | public class DynamicHandler:IHandle 4 | { 5 | private readonly dynamic _target; 6 | 7 | public DynamicHandler(dynamic target) 8 | { 9 | _target = target; 10 | } 11 | 12 | public void Handle(IMessage message) 13 | { 14 | dynamic msg = message; 15 | _target.Handle(msg); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/ExistingHandlerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace ReactiveDomain.Messaging.Bus { 5 | [Serializable] 6 | public class ExistingHandlerException : Exception 7 | { 8 | 9 | public ExistingHandlerException() 10 | { 11 | } 12 | 13 | public ExistingHandlerException(string message) : base(message) 14 | { 15 | } 16 | 17 | public ExistingHandlerException(string message, Exception inner) : base(message, inner) 18 | { 19 | } 20 | #if NET8_0_OR_GREATER 21 | [Obsolete(DiagnosticId = "SYSLIB0051")] 22 | #endif 23 | protected ExistingHandlerException( 24 | SerializationInfo info, 25 | StreamingContext context) : base(info, context) 26 | { 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/HandleExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace ReactiveDomain.Messaging.Bus 3 | { 4 | public static class HandleExtensions 5 | { 6 | public static IHandle WidenFrom(this IHandle handler) 7 | where TOutput : IMessage 8 | where TInput : TOutput 9 | { 10 | return new WideningHandler(handler); 11 | } 12 | 13 | public static IHandle NarrowTo(this IHandle handler) 14 | where TInput : IMessage 15 | where TOutput : TInput 16 | { 17 | return new NarrowingHandler(handler); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/IApply.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable TypeParameterCanBeVariant 2 | namespace ReactiveDomain.Messaging.Bus 3 | { 4 | /// 5 | /// Used to apply events of type T to state. 6 | /// 7 | public interface IApply where T : Event 8 | { 9 | void Apply(T @event); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/IBus.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Messaging.Bus 2 | { 3 | /// 4 | /// 5 | public interface IBus: IPublisher, ISubscriber 6 | { 7 | /// 8 | /// Name of the Bus, used in logging and stats reporting 9 | /// 10 | string Name { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/ICommandBus.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Messaging.Bus 2 | { 3 | /// 4 | /// 5 | public interface ICommandBus : ICommandPublisher, ICommandSubscriber 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/ICommandSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging.Bus 4 | { 5 | public interface ICommandSubscriber 6 | { 7 | /// 8 | /// Set up a command-handler for the specified (T) command. The command-handler must be 9 | /// declared with IHandleCommand for T. 10 | /// 11 | /// 12 | /// 13 | IDisposable Subscribe(IHandleCommand handler) where T : class, ICommand; 14 | /// 15 | /// Remove up a command-handler for the specified (T) command. The command-handler must be 16 | /// declared with IHandleCommand for T. 17 | /// 18 | /// 19 | /// 20 | void Unsubscribe(IHandleCommand handler) where T : class, ICommand; 21 | } 22 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/IDispatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging.Bus 4 | { 5 | /// 6 | /// 7 | public interface IDispatcher : ICommandBus, IBus, IDisposable 8 | { 9 | bool Idle {get;} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/IHandle.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable TypeParameterCanBeVariant 2 | namespace ReactiveDomain.Messaging.Bus 3 | { 4 | /// 5 | /// Used to handle messages of type T without changing state. 6 | /// 7 | public interface IHandle where T: IMessage 8 | { 9 | void Handle(T message); 10 | } 11 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/IHandleCommand.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable TypeParameterCanBeVariant 2 | namespace ReactiveDomain.Messaging.Bus 3 | { 4 | /// 5 | /// Used to handle commands of type T. A command is usually used to request a state change. 6 | /// 7 | public interface IHandleCommand where T : class, ICommand 8 | { 9 | CommandResponse Handle(T command); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/IMonitoredQueue.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ReactiveDomain.Messaging.Bus 4 | { 5 | public interface IMonitoredQueue 6 | { 7 | //NOTE: This interface provides direct access to a queue internals breaking encapsulation of these objects. 8 | // This is implemented this way to minimize impact on performance and to allow monitor detect problems 9 | 10 | // The monitored queue can be represented as IHandle unless this impl can interfere 11 | // with queue message handling itself 12 | string Name { get; } 13 | //QueueStats GetStatistics(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/IPublisher.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Messaging.Bus 2 | { 3 | public interface IPublisher 4 | { 5 | /// 6 | /// Publishes a Message 7 | /// Does not block 8 | /// 9 | /// the message to publish 10 | void Publish(IMessage message); 11 | } 12 | 13 | /// 14 | /// Marks as being OK for 15 | /// cross-thread publishing (e.g. in replying to envelopes). 16 | /// 17 | public interface IThreadSafePublisher 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/IQueuedHandler.cs: -------------------------------------------------------------------------------- 1 | //using EventStore.Core.Services.Monitoring.Stats; 2 | 3 | namespace ReactiveDomain.Messaging.Bus 4 | { 5 | public interface IQueuedHandler: IHandle, IPublisher 6 | { 7 | string Name { get; } 8 | void Start(); 9 | void Stop(); 10 | void RequestStop(); 11 | bool Idle { get; } 12 | //void Publish(Message message); 13 | //QueueStats GetStatistics(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/IdempotentHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ReactiveDomain.Util; 4 | 5 | namespace ReactiveDomain.Messaging.Bus 6 | { 7 | public class IdempotentHandler : IHandle where T : class, IMessage 8 | { 9 | private readonly IHandle _handler; 10 | private readonly int _bufferSize; 11 | private readonly Queue _guidQueue; 12 | private readonly HashSet _guids; 13 | private Guid _last; 14 | 15 | 16 | //n.b. keep the buffer size small as this is scanned on every execution 17 | public IdempotentHandler(IHandle handle, int bufferSize = 1) 18 | { 19 | Ensure.NotNull(handle, "handle"); 20 | Ensure.GreaterThan(0, bufferSize, nameof(bufferSize)); 21 | _handler = handle; 22 | _bufferSize = bufferSize; 23 | _guidQueue = new Queue(bufferSize); 24 | _guids = new HashSet(); 25 | _last = Guid.Empty; 26 | } 27 | 28 | public void Handle(T message) 29 | { 30 | var id = message.MsgId; 31 | 32 | if (_last == id || _guids.Contains(id)) return; 33 | _last = id; 34 | 35 | if (_bufferSize > 1) 36 | { 37 | if (_guidQueue.Count >= _bufferSize) 38 | _guids.Remove(_guidQueue.Dequeue()); 39 | _guids.Add(id); 40 | _guidQueue.Enqueue(id); 41 | } 42 | _handler.Handle(message); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/MessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging.Bus 4 | { 5 | public interface IMessageHandler 6 | { 7 | string HandlerName { get; } 8 | Type MessageType { get; } 9 | bool TryHandle(IMessage message); 10 | bool IsSame(Type messagesType, object handler); 11 | } 12 | 13 | internal class MessageHandler : IMessageHandler where T : class, IMessage 14 | { 15 | public string HandlerName { get; private set; } 16 | 17 | public Type MessageType { get; private set; } 18 | 19 | private readonly IHandle _handler; 20 | 21 | internal MessageHandler(IHandle handler, string handlerName) 22 | { 23 | if (handler == null) 24 | throw new ArgumentNullException(nameof(handler)); 25 | _handler = handler; 26 | HandlerName = handlerName ?? string.Empty; 27 | MessageType = typeof(T); 28 | } 29 | 30 | public bool TryHandle(IMessage message) 31 | { 32 | var msg = message as T; 33 | if (msg == null) return false; 34 | 35 | _handler.Handle(msg); //if this throws let it bubble up. 36 | return true; 37 | } 38 | 39 | public bool IsSame(Type messageType, object handler) 40 | { 41 | return ReferenceEquals(_handler, handler) && typeof(T) == messageType; 42 | } 43 | 44 | public override string ToString() 45 | { 46 | return string.IsNullOrEmpty(HandlerName) ? _handler.ToString() : HandlerName; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/NarrowingHandler.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Messaging.Bus 2 | { 3 | public class NarrowingHandler : IHandle 4 | where TInput : IMessage 5 | where TOutput : TInput 6 | { 7 | private readonly IHandle _handler; 8 | 9 | public NarrowingHandler(IHandle handler) 10 | { 11 | _handler = handler; 12 | } 13 | 14 | public void Handle(TInput message) 15 | { 16 | _handler.Handle((TOutput) message); // will throw if message type is wrong 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/QueueMonitor.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | using System.Collections.Concurrent; 4 | using ReactiveDomain.Logging; 5 | 6 | namespace ReactiveDomain.Messaging.Bus 7 | { 8 | public class QueueMonitor 9 | { 10 | private static readonly ILogger Log = LogManager.GetLogger("ReactiveDomain"); 11 | public static readonly QueueMonitor Default = new QueueMonitor(); 12 | 13 | private readonly ConcurrentDictionary _queues = new ConcurrentDictionary(); 14 | 15 | private QueueMonitor() 16 | { 17 | } 18 | 19 | public void Register(IMonitoredQueue monitoredQueue) 20 | { 21 | _queues[monitoredQueue] = monitoredQueue; 22 | } 23 | 24 | public void Unregister(IMonitoredQueue monitoredQueue) 25 | { 26 | IMonitoredQueue v; 27 | _queues.TryRemove(monitoredQueue, out v); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/TimeSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace ReactiveDomain.Messaging.Bus { 5 | public interface ITimeSource { 6 | TimePosition Now(); 7 | void WaitFor(TimePosition position, ManualResetEventSlim cancel); 8 | } 9 | public class TimeSource : ITimeSource { 10 | public static TimeSource System = new TimeSource(); 11 | public TimePosition Now() { 12 | return new TimePosition(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); 13 | } 14 | public void WaitFor(TimePosition position, ManualResetEventSlim cancel) { 15 | cancel.Wait(Now().DistanceUntil(position)); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Bus/WideningHandler.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace ReactiveDomain.Messaging.Bus 3 | { 4 | public class WideningHandler : IHandle 5 | where TInput : TOutput 6 | where TOutput : IMessage 7 | { 8 | private readonly IHandle _handler; 9 | 10 | public WideningHandler(IHandle handler) 11 | { 12 | _handler = handler; 13 | } 14 | 15 | public void Handle(TInput message) 16 | { 17 | _handler.Handle(message); 18 | } 19 | 20 | public override string ToString() 21 | { 22 | return string.Format("WideningHandler<{0}, {1}>({2})", typeof (TInput).Name, typeof (TOutput).Name, _handler); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/CallbackEnvelope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Util; 3 | 4 | namespace ReactiveDomain.Messaging 5 | { 6 | public class CallbackEnvelope : IEnvelope 7 | { 8 | private readonly Action _callback; 9 | 10 | public CallbackEnvelope(Action callback) 11 | { 12 | _callback = callback; 13 | Ensure.NotNull(callback, "callback"); 14 | } 15 | 16 | public void ReplyWith(T message) where T : IMessage 17 | { 18 | _callback(message); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Forwarder.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging.Bus; 2 | 3 | namespace ReactiveDomain.Messaging 4 | { 5 | public static class Forwarder 6 | { 7 | /// 8 | /// Creates a message handler publishing all incoming messages on destination. 9 | /// 10 | public static IHandle Create(IPublisher to) where T : IMessage 11 | { 12 | return new F(to); 13 | } 14 | 15 | /// 16 | /// Creates a message handler publishing all incoming messages onto one of the destinations. 17 | /// 18 | public static IHandle CreateBalancing(params IPublisher[] to) where T : IMessage 19 | { 20 | return new Balancing(to); 21 | } 22 | 23 | class F : IHandle where T : IMessage 24 | { 25 | private readonly IPublisher _to; 26 | 27 | public F(IPublisher to) 28 | { 29 | _to = to; 30 | } 31 | 32 | public void Handle(T message) 33 | { 34 | _to.Publish(message); 35 | } 36 | } 37 | 38 | class Balancing : IHandle where T : IMessage 39 | { 40 | private readonly IPublisher[] _to; 41 | private int _last; 42 | 43 | public Balancing(IPublisher[] to) 44 | { 45 | _to = to; 46 | } 47 | 48 | public void Handle(T message) 49 | { 50 | var last = _last; 51 | if (last == _to.Length - 1) 52 | _last = 0; 53 | else 54 | _last = last + 1; 55 | _to[_last].Publish(message); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/IEnvelope.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Messaging 2 | { 3 | public interface IEnvelope 4 | { 5 | void ReplyWith(T message) where T : IMessage; 6 | } 7 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/MessageBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging 4 | { 5 | public class MessageBuilder 6 | { 7 | private Guid? Correlation; 8 | private Guid? Causation; 9 | public static MessageBuilder From(ICorrelatedMessage message) 10 | { 11 | return new MessageBuilder 12 | { 13 | Correlation = message.CorrelationId, 14 | Causation = message.MsgId 15 | }; 16 | } 17 | public T Build(Func MessageConstructor) where T : ICorrelatedMessage 18 | { 19 | var msg = MessageConstructor(); 20 | msg.CorrelationId = Correlation ?? msg.MsgId; 21 | msg.CausationId = Causation ?? Guid.Empty; 22 | return msg; 23 | } 24 | public static T New(Func MessageConstructor) where T : ICorrelatedMessage 25 | { 26 | return new MessageBuilder().Build(MessageConstructor); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/CommandResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Messaging.Bus; 3 | 4 | namespace ReactiveDomain.Messaging 5 | { 6 | public abstract class CommandResponse : Message, ICorrelatedMessage, ICommandResponse 7 | { 8 | public Guid CorrelationId { get; set; } 9 | public Guid CausationId { get; set; } 10 | 11 | public ICommand SourceCommand { get; } 12 | public Type CommandType => SourceCommand.GetType(); 13 | public Guid CommandId => SourceCommand.MsgId; 14 | 15 | protected CommandResponse(ICommand sourceCommand) 16 | { 17 | CorrelationId = sourceCommand.CorrelationId; 18 | CausationId = sourceCommand.MsgId; 19 | SourceCommand = sourceCommand; 20 | } 21 | } 22 | 23 | public class Success : CommandResponse 24 | { 25 | public Success(ICommand sourceCommand) : base(sourceCommand) {} 26 | } 27 | 28 | public class Fail : CommandResponse 29 | { 30 | public Exception Exception { get; } 31 | public Fail(ICommand sourceCommand, Exception exception) : base(sourceCommand) 32 | { 33 | Exception = exception; 34 | } 35 | } 36 | 37 | public class Canceled : Fail 38 | { 39 | public Canceled(ICommand sourceCommand) : base(sourceCommand, new CommandCanceledException(sourceCommand)) { } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/CorrelatedRoot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging.Messages 4 | { 5 | public class CorrelatedRoot : Event 6 | { 7 | public CorrelatedRoot(Guid? correlationId = null) 8 | { 9 | CorrelationId = correlationId ?? Guid.NewGuid(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/CorrelationSource.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System; 4 | 5 | namespace ReactiveDomain.Messaging.Messages 6 | { 7 | public class CorrelationSource : ICorrelatedMessage 8 | { 9 | public Guid MsgId{ get;} 10 | public Guid CausationId { get; set; } 11 | public Guid CorrelationId { get; set; } 12 | public CorrelationSource(Guid id, Guid causationId,Guid correlationId) 13 | { 14 | MsgId = id; 15 | CausationId = causationId; 16 | CorrelationId = correlationId; 17 | } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/Event.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging 4 | { 5 | public abstract class Event : Message, ICorrelatedMessage, IEvent 6 | { 7 | protected ushort Version = 1; 8 | public Guid CorrelationId { get; set; } 9 | public Guid CausationId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/IChainedMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging 4 | { 5 | public interface IChainedMessage : ICorrelatedMessage 6 | { 7 | Guid PrincipalId { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/ICommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace ReactiveDomain.Messaging 5 | { 6 | public interface ICommand : ICorrelatedMessage 7 | { 8 | bool IsCancelable { get; } 9 | bool IsCanceled { get; } 10 | CancellationToken? CancellationToken { get; } 11 | 12 | void RegisterOnCancellation(Action action); 13 | CommandResponse Succeed(); 14 | CommandResponse Fail(Exception ex = null); 15 | CommandResponse Canceled(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/ICommandResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging 4 | { 5 | public interface ICommandResponse : IMessage 6 | { 7 | Guid CommandId { get; } 8 | Type CommandType { get; } 9 | ICommand SourceCommand { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/ICorrelatedMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging 4 | { 5 | public interface ICorrelatedMessage : IMessage 6 | { 7 | Guid CorrelationId { get; set; } 8 | Guid CausationId { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/IEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging 4 | { 5 | //marker interface for events 6 | public interface IEvent : IMessage 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/IMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Messaging 4 | { 5 | public interface IMessage 6 | { 7 | Guid MsgId { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Messages/IQueueAffineMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Messaging 2 | { 3 | public interface IQueueAffineMessage : IMessage 4 | { 5 | int QueueId { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Monitoring/Stats/GcStats.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable MemberCanBePrivate.Global 2 | // ReSharper disable NotAccessedField.Global 3 | namespace ReactiveDomain.Messaging.Monitoring.Stats 4 | { 5 | public class GcStats 6 | { 7 | public readonly long Gen0ItemsCount; 8 | public readonly long Gen1ItemsCount; 9 | public readonly long Gen2ItemsCount; 10 | public readonly long Gen0Size; 11 | public readonly long Gen1Size; 12 | public readonly long Gen2Size; 13 | public readonly long LargeHeapSize; 14 | public readonly float AllocationSpeed; 15 | public readonly float TimeInGc; 16 | public readonly long TotalBytesInHeaps; 17 | 18 | public GcStats(long gcGen0Items, 19 | long gcGen1Items, 20 | long gcGen2Items, 21 | long gcGen0Size, 22 | long gcGen1Size, 23 | long gcGen2Size, 24 | long gcLargeHeapSize, 25 | float gcAllocationSpeed, 26 | float gcTimeInGc, 27 | long gcTotalBytesInHeaps) 28 | { 29 | Gen0ItemsCount = gcGen0Items; 30 | Gen1ItemsCount = gcGen1Items; 31 | Gen2ItemsCount = gcGen2Items; 32 | Gen0Size = gcGen0Size; 33 | Gen1Size = gcGen1Size; 34 | Gen2Size = gcGen2Size; 35 | LargeHeapSize = gcLargeHeapSize; 36 | AllocationSpeed = gcAllocationSpeed; 37 | TimeInGc = gcTimeInGc; 38 | TotalBytesInHeaps = gcTotalBytesInHeaps; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Monitoring/Stats/StatMetadata.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable MemberCanBePrivate.Global 2 | // ReSharper disable NotAccessedField.Global 3 | // ReSharper disable UnusedAutoPropertyAccessor.Global 4 | namespace ReactiveDomain.Messaging.Monitoring.Stats 5 | { 6 | public class StatMetadata 7 | { 8 | public object Value { get; set; } 9 | public string Category { get; set; } 10 | public string Title { get; set; } 11 | public bool DrawChart { get; set; } 12 | 13 | public StatMetadata() {} 14 | 15 | private StatMetadata(object value, string category, string title, bool drawChart) 16 | { 17 | Value = value; 18 | Category = category; 19 | Title = title; 20 | DrawChart = drawChart; 21 | } 22 | 23 | public StatMetadata(object value, string category, string title) 24 | : this(value, category, title, true) 25 | { 26 | 27 | } 28 | 29 | public StatMetadata(object value, string title) 30 | : this (value, null, title, true) 31 | { 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/Monitoring/Utils/StatsCsvEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | 6 | namespace ReactiveDomain.Messaging.Monitoring.Utils 7 | { 8 | public static class StatsCsvEncoder 9 | { 10 | private const string Comma = ","; 11 | private const string CommaEscapeSymbol = ";"; 12 | 13 | public static string GetHeader(Dictionary stats) 14 | { 15 | return Join(stats.Keys).Prepend("Time"); 16 | } 17 | 18 | public static string GetLine(Dictionary stats) 19 | { 20 | return Join(stats.Values).PrependTime(); 21 | } 22 | 23 | private static string Prepend(this string csvLine, string column) 24 | { 25 | return string.Format("{0}{1}{2}", column, Comma, csvLine); 26 | } 27 | 28 | private static string PrependTime(this string csvLine ) 29 | { 30 | return csvLine.Prepend(DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)); 31 | } 32 | 33 | private static string Join(IEnumerable items ) 34 | { 35 | var strValues = items.Select(TryGetInvariantString); 36 | var escapedValues = strValues.Select(str => str.Replace(Comma, CommaEscapeSymbol)); //extra safety 37 | 38 | return string.Join(Comma, escapedValues); 39 | } 40 | 41 | private static string TryGetInvariantString(object obj) 42 | { 43 | if (obj == null) 44 | return string.Empty; 45 | 46 | var convertible = obj as IConvertible; 47 | if (convertible != null) 48 | return convertible.ToString(CultureInfo.InvariantCulture); 49 | 50 | return obj.ToString(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/NoopEnvelope.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Messaging 2 | { 3 | public class NoopEnvelope : IEnvelope 4 | { 5 | public void ReplyWith(T message) where T : IMessage 6 | { 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/PublishEnvelope.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Threading; 3 | using ReactiveDomain.Messaging.Bus; 4 | 5 | namespace ReactiveDomain.Messaging 6 | { 7 | public class PublishEnvelope : IEnvelope 8 | { 9 | private readonly IPublisher _publisher; 10 | private readonly int _createdOnThread; 11 | 12 | public PublishEnvelope(IPublisher publisher, bool crossThread = false) 13 | { 14 | _publisher = publisher; 15 | _createdOnThread = crossThread ? -1 : Thread.CurrentThread.ManagedThreadId; 16 | } 17 | 18 | public void ReplyWith(T message) where T : IMessage 19 | { 20 | Debug.Assert(_createdOnThread == -1 || 21 | Thread.CurrentThread.ManagedThreadId == _createdOnThread || _publisher is IThreadSafePublisher); 22 | _publisher.Publish(message); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/ReactiveDomain.Messaging.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(LibTargetFrameworks) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Messaging/SendToThisEnvelope.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging.Bus; 2 | 3 | namespace ReactiveDomain.Messaging 4 | { 5 | // USE ONLY WHEN YOU KNOW WHAT YOU ARE DOING 6 | public class SendToThisEnvelope : IEnvelope 7 | { 8 | private readonly object _receiver; 9 | 10 | public SendToThisEnvelope(object receiver) 11 | { 12 | _receiver = receiver; 13 | } 14 | 15 | public void ReplyWith(T message) where T : IMessage 16 | { 17 | var x = _receiver as IHandle; 18 | if (x != null) 19 | x.Handle(message); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ClientConnectionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace ReactiveDomain { 5 | /// 6 | /// Event Arguments for the event raised when an is 7 | /// connected to or disconnected from an Event Store server. 8 | /// 9 | public class ClientConnectionEventArgs : EventArgs 10 | { 11 | /// 12 | /// The endpoint of the Event Store server to or from which the connection was connected or disconnected. 13 | /// 14 | public EndPoint RemoteEndPoint { get; } 15 | 16 | /// 17 | /// The responsible for raising the event. 18 | /// 19 | public IStreamStoreConnection Connection { get; } 20 | 21 | /// 22 | /// Constructs a new instance of . 23 | /// 24 | /// The responsible for raising the event. 25 | /// The endpoint of the Event Store server to or from which the connection was connected or disconnected. 26 | public ClientConnectionEventArgs(IStreamStoreConnection connection, EndPoint remoteEndPoint) { 27 | Connection = connection; 28 | RemoteEndPoint = remoteEndPoint; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/Consts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace ReactiveDomain 3 | { 4 | internal static class Consts 5 | { 6 | public static readonly TimeSpan DefaultReconnectionDelay = TimeSpan.FromMilliseconds(100.0); 7 | public static readonly TimeSpan DefaultOperationTimeout = TimeSpan.FromSeconds(7.0); 8 | public static readonly TimeSpan DefaultOperationTimeoutCheckPeriod = TimeSpan.FromSeconds(1.0); 9 | public static readonly TimeSpan TimerPeriod = TimeSpan.FromMilliseconds(200.0); 10 | public static readonly int MaxReadSize = 4096; 11 | public const int DefaultMaxQueueSize = 5000; 12 | public const int DefaultMaxConcurrentItems = 5000; 13 | public const int DefaultMaxOperationRetries = 10; 14 | public const int DefaultMaxReconnections = 10; 15 | public const bool DefaultRequireMaster = true; 16 | public const int DefaultMaxClusterDiscoverAttempts = 10; 17 | public const int DefaultClusterManagerExternalHttpPort = 30778; 18 | public const int CatchUpDefaultReadBatchSize = 500; 19 | public const int CatchUpDefaultMaxPushQueueSize = 10000; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/EventStore/EsdbConfig.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.EventStore 2 | { 3 | public class EsdbConfig 4 | { 5 | public string Path { get; set; } 6 | public string WorkingDir { get; set; } 7 | public string Args { get; set; } 8 | public string ConnectionString { get; set; } 9 | public string Schema { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ProjectedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain { 4 | public class ProjectedEvent:RecordedEvent 5 | { 6 | public string ProjectedStream; 7 | public long OriginalEventNumber; 8 | 9 | public ProjectedEvent( 10 | string projectedStream, 11 | long originalEventNumber, 12 | string eventStreamId, 13 | Guid eventId, 14 | long eventNumber, 15 | string eventType, 16 | byte[] data, 17 | byte[] metadata, 18 | bool isJson, 19 | DateTime created, 20 | long createdEpoch) : base( 21 | eventStreamId, 22 | eventId, 23 | eventNumber, 24 | eventType, 25 | data, 26 | metadata, 27 | isJson, 28 | created, 29 | createdEpoch) { 30 | ProjectedStream = projectedStream; 31 | OriginalEventNumber = originalEventNumber; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ReactiveDomain.Persistence.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(LibTargetFrameworks) 5 | ReactiveDomain 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ReadDirection.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | /// 4 | /// Represents the direction of read operation (both from $all and usual streams) 5 | /// 6 | public enum ReadDirection 7 | { 8 | Forward = 1, 9 | Backward = -1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/IMessageDeserializer.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace ReactiveDomain 6 | { 7 | public interface IMessageDeserializer 8 | { 9 | Func> CreateDeserializer(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/IMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | 3 | namespace ReactiveDomain 4 | { 5 | public interface IMessageSerializer 6 | { 7 | StorableMessage Serialize(Message msg); 8 | } 9 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/ISnapshotDeserializer.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | public interface ISnapshotDeserializer 4 | { 5 | T Deserialize(SerializedMessage message) where T:Snapshot; 6 | } 7 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/ISnapshotSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | public interface ISnapshotSerializer 4 | { 5 | StorableMessage Serialize(Snapshot snapshot); 6 | } 7 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/Serialization.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain 4 | { 5 | public class Serialization 6 | { 7 | public Serialization(SerializationPair messages, SerializationPair snapshots, Func metadata) 8 | { 9 | Messages = messages; 10 | Snapshots = snapshots; 11 | Metadata = metadata; 12 | } 13 | 14 | public SerializationPair Messages { get; } 15 | public SerializationPair Snapshots { get; } 16 | 17 | public Func Metadata { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/SerializationPair.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | public class SerializationPair 4 | { 5 | public readonly TSerializer Serializer; 6 | public readonly TDeserializer Deserializer; 7 | 8 | public SerializationPair(TSerializer serializer, TDeserializer deserializer) 9 | { 10 | Serializer = serializer; 11 | Deserializer = deserializer; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/SerializationRegistryMessageExtensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using ReactiveDomain.Messaging; 3 | 4 | namespace ReactiveDomain 5 | { 6 | public static class SerializationRegistryMessageExtensions 7 | { 8 | public static SerializationRegistry Default(this SerializationRegistry registry) where T : Message 9 | { 10 | registry.RegisterMessageSerializer(typeof(T), typeof(T).Name, 1, JObject.FromObject); 11 | registry.RegisterMessageDeserializer(typeof(object), typeof(T).Name, 1, x => new Message[] { x.ToObject() }); 12 | return registry; 13 | } 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/SerializationRegistrySnapshotExtensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace ReactiveDomain 4 | { 5 | public static class SerializationRegistrySnapshotExtensions 6 | { 7 | public static SerializationRegistry Default(this SerializationRegistry registry) where T : Snapshot 8 | { 9 | registry.RegisterSnapshotSerializer(typeof(T), typeof(T).Name, 1, JObject.FromObject); 10 | registry.RegisterSnapshotDeserializer(typeof(T).Name, 1, x => x.ToObject() ); 11 | return registry; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/SerializedMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain 4 | { 5 | public readonly struct SerializedMessage : IEquatable 6 | { 7 | public readonly Guid Id; 8 | public readonly string Name; 9 | public readonly string StreamName; 10 | public readonly long StreamRevision; 11 | public readonly byte[] Data; 12 | public readonly byte[] Metadata; 13 | 14 | public static readonly SerializedMessage None = new SerializedMessage(); 15 | 16 | public SerializedMessage(Guid id, string name, string streamName, long streamRevision, byte[] data, byte[] metadata) 17 | { 18 | Id = id; 19 | Name = name; 20 | StreamName = streamName; 21 | StreamRevision = streamRevision; 22 | Data = data; 23 | Metadata = metadata; 24 | } 25 | 26 | public bool Equals(SerializedMessage other) 27 | { 28 | return string.Equals(Name, other.Name) && string.Equals(StreamName, other.StreamName) && StreamRevision == other.StreamRevision; 29 | } 30 | 31 | public override bool Equals(object obj) 32 | { 33 | if (ReferenceEquals(null, obj)) return false; 34 | return obj is SerializedMessage && Equals((SerializedMessage)obj); 35 | } 36 | 37 | public override int GetHashCode() 38 | { 39 | unchecked 40 | { 41 | var hashCode = Name.GetHashCode(); 42 | hashCode = (hashCode * 397) ^ StreamName.GetHashCode(); 43 | hashCode = (hashCode * 397) ^ StreamRevision.GetHashCode(); 44 | return hashCode; 45 | } 46 | } 47 | 48 | public static bool operator ==(SerializedMessage left, SerializedMessage right) 49 | { 50 | return left.Equals(right); 51 | } 52 | 53 | public static bool operator !=(SerializedMessage left, SerializedMessage right) 54 | { 55 | return !left.Equals(right); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/Snapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain 4 | { 5 | public abstract class Snapshot : IMetadataSource 6 | { 7 | [NonSerialized] 8 | private Metadata _metadata; 9 | 10 | Metadata IMetadataSource.ReadMetadata() 11 | { 12 | return _metadata; 13 | } 14 | 15 | Metadata IMetadataSource.Initialize() 16 | { 17 | if(_metadata != null) throw new InvalidOperationException(); 18 | return _metadata = new Metadata(); 19 | } 20 | 21 | void IMetadataSource.Initialize(Metadata md) 22 | { 23 | _metadata = md; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/StorableMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain 4 | { 5 | public readonly struct StorableMessage 6 | { 7 | public readonly Guid Id; 8 | public readonly string Name; 9 | public readonly byte[] Data; 10 | public readonly byte[] Metadata; 11 | 12 | public StorableMessage(Guid id, string name, byte[] data, byte[] metadata) 13 | { 14 | Id = id; 15 | Name = name; 16 | Data = data; 17 | Metadata = metadata; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/ScopedSerialization/StreamStoreReadResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ReactiveDomain 4 | { 5 | public class StreamStoreReadResult 6 | { 7 | public StreamStoreReadResult(IReadOnlyList messages, bool isEndOfStream) 8 | { 9 | Messages = messages; 10 | IsEndOfStream = isEndOfStream; 11 | } 12 | 13 | public IReadOnlyList Messages { get; } 14 | public bool IsEndOfStream { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/StreamNameConversions.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | public static class StreamNameConversions 4 | { 5 | public static StreamNameConverter WithSuffix(string suffix) => 6 | name => name.WithSuffix(suffix); 7 | 8 | public static StreamNameConverter WithPrefix(string prefix) => 9 | name => name.WithPrefix(prefix); 10 | 11 | public static StreamNameConverter WithoutSuffix(string suffix) => 12 | name => name.WithoutSuffix(suffix); 13 | 14 | public static StreamNameConverter WithoutPrefix(string prefix) => 15 | name => name.WithoutPrefix(prefix); 16 | 17 | public static StreamNameConverter PassThru => 18 | name => name; 19 | } 20 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/StreamNameConverter.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | /// 4 | /// Converts a stream name into a converted stream name. 5 | /// 6 | /// The stream name to convert. 7 | /// A converted stream name. 8 | /// The reason for this concept is so that 9 | /// - prefixing (component or context), 10 | /// - suffixing, 11 | /// - fixing the casing of, 12 | /// - etc... 13 | /// of a stream name become possible. 14 | public delegate StreamName StreamNameConverter(StreamName name); 15 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/StreamPosition.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | public static class StreamPosition 4 | { 5 | /// The first event in a stream 6 | public const int Start = 0; 7 | /// The last event in the stream. 8 | public const int End = -1; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/StreamSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain 4 | { 5 | public abstract class StreamSubscription : IDisposable 6 | { 7 | /// 8 | /// The last commit position seen on the subscription (if this is 9 | /// a subscription to all events). 10 | /// 11 | public readonly long LastCommitPosition; 12 | /// 13 | /// The last event number seen on the subscription (if this is a 14 | /// subscription to a single stream). 15 | /// 16 | public readonly long? LastEventNumber; 17 | 18 | /// True if this subscription is to all streams. 19 | public bool IsSubscribedToAll => StreamId == string.Empty; 20 | 21 | /// 22 | /// The name of the stream to which the subscription is subscribed. 23 | /// 24 | public string StreamId { get; } 25 | 26 | internal StreamSubscription(string streamId, long lastCommitPosition, long? lastEventNumber) 27 | { 28 | StreamId = streamId; 29 | LastCommitPosition = lastCommitPosition; 30 | LastEventNumber = lastEventNumber; 31 | } 32 | 33 | /// Unsubscribes from the stream. 34 | public void Dispose() 35 | { 36 | Unsubscribe(); 37 | } 38 | 39 | /// Unsubscribes from the stream. 40 | public void Close() 41 | { 42 | Unsubscribe(); 43 | } 44 | 45 | /// Unsubscribes from the stream 46 | public abstract void Unsubscribe(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/SubscriptionDropReason.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain 2 | { 3 | public enum SubscriptionDropReason 4 | { 5 | UserInitiated = 0, 6 | NotAuthenticated = 1, 7 | AccessDenied = 2, 8 | SubscribingError = 3, 9 | ServerError = 4, 10 | ConnectionClosed = 5, 11 | CatchUpError = 6, 12 | ProcessingQueueOverflow = 7, 13 | EventHandlerException = 8, 14 | MaxSubscribersReached = 9, 15 | PersistentSubscriptionDeleted = 10, // 0x0000000A 16 | Unknown = 100, // 0x00000064 17 | NotFound = 101, // 0x00000065 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/UnknownTypeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain { 4 | public class UnknownTypeException : Exception 5 | { 6 | public UnknownTypeException(string typeName):base($"TypeName'{typeName}' was not found in the currently loaded appdomains.") 7 | {} 8 | } 9 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/UserCredentials.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | 4 | namespace ReactiveDomain 5 | { 6 | /// 7 | /// A username/password pair used for authentication and 8 | /// authorization to perform operations over an . 9 | /// 10 | public class UserCredentials 11 | { 12 | /// The username 13 | public readonly string Username; 14 | /// The password 15 | public readonly string Password; 16 | 17 | /// 18 | /// Constructs a new . 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | public UserCredentials(string username, string password) { 25 | if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullException(nameof(username)); 26 | if (string.IsNullOrWhiteSpace(password)) throw new ArgumentNullException(nameof(password)); 27 | Username = username; 28 | Password = password; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Persistence/WriteResult.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain { 2 | public struct WriteResult 3 | { 4 | /// The next expected version for the stream. 5 | public readonly long NextExpectedVersion; 6 | /// 7 | /// Constructs a new . 8 | /// 9 | /// The next expected version for the stream. 10 | 11 | public WriteResult(long nextExpectedVersion) 12 | { 13 | NextExpectedVersion = nextExpectedVersion; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Policy.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "test methods", Scope = "member", Target = "~M:ReactiveDomain.Policy.Tests.PolicyMapTest.policy_map_has_correct_roles_and_permisssions")] 9 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "test methods", Scope = "member", Target = "~M:ReactiveDomain.Policy.Tests.PolicyMapTest.policy_can_be_enforced")] 10 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "test methods", Scope = "member", Target = "~M:ReactiveDomain.Policy.Tests.PolicyMapTest.can_create_and_resolve_permissions_type_names")] 11 | [assembly: SuppressMessage("Usage", "xUnit1013:Public method should be marked as test", Justification = "handle interface", Scope = "member", Target = "~M:ReactiveDomain.Policy.Tests.PolicyMapTest.Handle(ReactiveDomain.Policy.Tests.OtherMsg)")] 12 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "", Scope = "member", Target = "~M:ReactiveDomain.Policy.Tests.PolicyMapTest.can_export_policy")] 13 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Policy.Tests/PolicyTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace ReactiveDomain.Policy.Tests 4 | { 5 | public class PolicyTests 6 | { 7 | /// 8 | /// this stop a warning from the test runner. 9 | /// once the actual tests get built out this can be removed 10 | /// 11 | [Fact] 12 | public void can_make_the_test_runner_happy() { 13 | var isHappy = true; 14 | Assert.True(isHappy); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Policy.Tests/ReactiveDomain.Policy.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(TestTargetFrameworks) 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Policy.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PolicyTool.exe 5 | Always 6 | false 7 | 8 | 9 | es_settings.json 10 | Always 11 | false 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Policy/AuthorizationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Policy 4 | { 5 | public class AuthorizationException:Exception 6 | { 7 | public Type Command { get; } 8 | public AuthorizationException(Type command, string message):base($"{command.Name} not authorized {message}") 9 | { 10 | Command = command; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Policy/Permissions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using ReactiveDomain.Messaging; 5 | 6 | namespace ReactiveDomain.Policy 7 | { 8 | public static class Permissions 9 | { 10 | private static readonly Type CommandType = typeof(Command); 11 | 12 | public static Permission[] GetCommandPermissions(Type type) 13 | { 14 | return GetCommands(type).Select(t => new Permission(t)).ToArray(); 15 | } 16 | 17 | public static IEnumerable GetCommands(Type type) 18 | { 19 | return type.GetNestedTypes().Where(t => CommandType.IsAssignableFrom(t)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Policy/PolicyDTO.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ReactiveDomain.Policy 4 | { 5 | public class PolicyDTO { 6 | public string ApplicationName { get; set; } 7 | public string ClientName { get; set; } 8 | public string PolicyName { get; set; } 9 | public bool SingleRole { get; set; } 10 | public List Roles { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Policy/ReactiveDomain.Policy.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(LibTargetFrameworks) 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Policy/UserDetails.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | 4 | namespace ReactiveDomain.Policy 5 | { 6 | public class UserDetails 7 | { 8 | public Guid UserId { get; set;} 9 | public string FullName { get; set; } 10 | public string GivenName { get; set; } 11 | public string Surname { get; set; } 12 | public string Email { get;set; } 13 | public string SubjectId { get; set; } 14 | public string AuthProvider { get;set; } 15 | public string AuthDomain { get; set; } 16 | public string UserName { get; set; } 17 | public string PolicyName { set;get; } 18 | public string[] RoleNames { set; get; } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyStorage.Tests/Helpers/TestMessages.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | 3 | namespace ReactiveDomain.Policy.Tests.Helpers 4 | { 5 | class TestMessages 6 | { 7 | public class RootCommand : Command 8 | { } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyStorage/ReactiveDomain.PolicyStorage.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | $(LibTargetFrameworks) 6 | true 7 | ReactiveDomain.Policy 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyStorage/ReadModels/ApplicationDTO.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Policy.Messages; 2 | using System; 3 | using System.Text; 4 | 5 | namespace ReactiveDomain.Policy.ReadModels 6 | { 7 | public class ApplicationDTO 8 | { 9 | public Guid ApplicationId { get; } 10 | public string Name { get; } 11 | public Version SecurityModelVersion { get; } 12 | public bool OneRolePerUser { get; } 13 | public ApplicationDTO(Guid applicationId, string name, Version securityModelVersion, bool oneRolePerUser) 14 | { 15 | ApplicationId = applicationId; 16 | Name = name; 17 | SecurityModelVersion = securityModelVersion; 18 | OneRolePerUser = oneRolePerUser; 19 | } 20 | public ApplicationDTO(ApplicationMsgs.ApplicationCreated @event) 21 | { 22 | ApplicationId = @event.ApplicationId; 23 | Name = @event.Name; 24 | SecurityModelVersion = Version.Parse(@event.SecurityModelVersion); 25 | OneRolePerUser = @event.OneRolePerUser; 26 | } 27 | public override string ToString() 28 | { 29 | var sb = new StringBuilder(); 30 | sb.AppendLine($"App Name:{Name}"); 31 | sb.AppendLine($"App Id:{ApplicationId}"); 32 | sb.AppendLine($"Ver:{SecurityModelVersion}"); 33 | sb.AppendLine($"IsSingleRole:{OneRolePerUser}"); 34 | return sb.ToString(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyStorage/ReadModels/ReadModelHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Foundation; 3 | using ReactiveDomain.Util; 4 | 5 | namespace ReactiveDomain.Policy.ReadModels 6 | { 7 | public static class ReadModelHelper 8 | { 9 | public static string GetSecuredApplicationStreamName(IConfiguredConnection conn, Guid id) 10 | { 11 | Ensure.NotEmptyGuid(id, nameof(id)); 12 | return conn.StreamNamer.GenerateForAggregate(typeof(Domain.SecuredApplication), id); 13 | } 14 | 15 | public static string GetSecuredApplicationCategoryStreamName(IConfiguredConnection conn) 16 | { 17 | return conn.StreamNamer.GenerateForCategory(typeof(Domain.SecuredApplication)); 18 | } 19 | 20 | public static string GetPolicyUserStreamName(IConfiguredConnection conn, Guid id) 21 | { 22 | Ensure.NotEmptyGuid(id, nameof(id)); 23 | return conn.StreamNamer.GenerateForAggregate(typeof(Domain.PolicyUser), id); 24 | } 25 | 26 | public static string GetPolicyUserCategoryStreamName(IConfiguredConnection conn) 27 | { 28 | return conn.StreamNamer.GenerateForCategory(typeof(Domain.PolicyUser)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyStorage/ReadModels/RoleDTO.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Policy.Messages; 2 | using System; 3 | 4 | namespace ReactiveDomain.Policy.ReadModels 5 | { 6 | public class RoleDTO 7 | { 8 | public Guid Id { get; } 9 | public Guid PolicyId { get; } 10 | public string Name { get; } 11 | public RoleDTO(Guid id, Guid policyId, string name) 12 | { 13 | Id = id; 14 | PolicyId = policyId; 15 | Name = name.Trim().ToLowerInvariant(); 16 | } 17 | 18 | public RoleDTO(ApplicationMsgs.RoleCreated @event) 19 | { 20 | Id = @event.RoleId; 21 | PolicyId = @event.PolicyId; 22 | Name = @event.Name.Trim().ToLowerInvariant(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyStorage/Services/ApplicationSvc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Foundation; 3 | using ReactiveDomain.Messaging; 4 | using ReactiveDomain.Messaging.Bus; 5 | using ReactiveDomain.Policy.Messages; 6 | using ReactiveDomain.Policy.ReadModels; 7 | 8 | namespace ReactiveDomain.Policy 9 | { 10 | public class ApplicationSvc: 11 | TransientSubscriber, 12 | IHandleCommand { 13 | private readonly IConfiguredConnection _conn; 14 | private readonly ICorrelatedRepository _repo; 15 | private readonly FilteredPoliciesRM _rm; 16 | 17 | public ApplicationSvc( 18 | IConfiguredConnection conn, 19 | ICommandSubscriber subscriber) 20 | : base(subscriber) { 21 | _conn = conn; 22 | _repo = conn.GetCorrelatedRepository(); 23 | _rm = new FilteredPoliciesRM(conn); 24 | Subscribe(this); 25 | } 26 | 27 | public CommandResponse Handle(ApplicationMsgs.CreateApplication cmd) 28 | { 29 | if (_rm.ApplicationExists(cmd.Name, new Version(cmd.SecurityModelVersion))) 30 | throw new DuplicateApplicationException(cmd.Name, cmd.SecurityModelVersion); 31 | var app = new Domain.SecuredApplication( 32 | cmd.ApplicationId, 33 | cmd.DefaultPolicyId, 34 | cmd.Name, 35 | cmd.SecurityModelVersion, 36 | cmd.OneRolePerUser, 37 | cmd); 38 | _repo.Save(app); 39 | return cmd.Succeed(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyStorage/Services/PolicyExceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Policy 4 | { 5 | /// 6 | /// An attempt was made to add a duplicate application to the system. 7 | /// 8 | public class DuplicateApplicationException : Exception 9 | { 10 | /// 11 | /// An attempt was made to add a duplicate application to the system. 12 | /// 13 | public DuplicateApplicationException(string appName, string securityModelVersion) 14 | : base($"Application {appName} with version {securityModelVersion} already exists.") 15 | { } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyTool/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | ..\..\bld\tools\ 10 | FileSystem 11 | net6.0 12 | true 13 | win-x64 14 | true 15 | true 16 | false 17 | 18 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyTool/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "PolicyTool": { 4 | "commandName": "Project", 5 | "commandLineArgs": "add-app --app-name PolicyManager --secret ChangeIt" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyTool/ReactiveDomain.PolicyTool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | true 7 | PolicyTool.Program 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/ReactiveDomain.PolicyTool/es_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "EventStoreUserName": "admin", 3 | "EventStorePassword": "changeit", 4 | "EventStoreIPAddress": "127.0.0.1", 5 | "EventStorePort": "1113", 6 | "PolicySchema": "rd_policy" 7 | } 8 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing.Debug.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ReactiveDomain.Testing 5 | 0.11.0.0 6 | Reactive Domain Group 7 | Reactive Domain Group 8 | false 9 | MIT 10 | Package includes ReactiveDomain assemblies needed for unit testing 11 | Copyright © 2024 Reactive Domain Group 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ReactiveDomain.Testing 5 | 0.11.0.0 6 | Reactive Domain Group 7 | Reactive Domain Group 8 | false 9 | MIT 10 | Package includes ReactiveDomain assemblies needed for unit testing 11 | Copyright © 2024 Reactive Domain Group 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/ConnectionUtil.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace ReactiveDomain.Testing { 4 | public static class ConnectionUtil { 5 | public static bool TryConfirmStream(this IStreamStoreConnection conn, string streamTypeName, int expectedEventCount) { 6 | int count = 0; 7 | while (true) { 8 | try { 9 | var slice = conn.ReadStreamForward(streamTypeName, StreamPosition.Start, 500); 10 | if (slice.IsEndOfStream && slice.Events.Length >= expectedEventCount) { 11 | return true; 12 | } 13 | Thread.Sleep(10); 14 | count++; 15 | if (count > 100) return false; 16 | } 17 | catch { 18 | //ignore 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/DispatcherUtil.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Permissions; 2 | using System.Windows.Threading; 3 | 4 | namespace ReactiveDomain.Testing 5 | { 6 | public static class DispatcherUtil 7 | { 8 | /// 9 | /// This is needed to get unit-tests for ViewModels to work. After your unit-test causes 10 | /// various ReadModel properties to update, call this. It will cause of the Subscribe() 11 | /// calls in all of the ViewModels to fire. Then you can check the ViewModel properties. 12 | /// If you have sequences to test (update some RM properties, check the VM's, update 13 | /// some more RM properties, check the VM's again) then you need to call DoEvents() after 14 | /// EACH SET of RM property updates. 15 | /// 16 | [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] 17 | public static void DoEvents() 18 | { 19 | var frame = new DispatcherFrame(); 20 | Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, 21 | new DispatcherOperationCallback(ExitFrame), frame); 22 | Dispatcher.PushFrame(frame); 23 | } 24 | 25 | private static object ExitFrame(object frame) 26 | { 27 | ((DispatcherFrame)frame).Continue = false; 28 | return null; 29 | } 30 | 31 | 32 | } 33 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/Catch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ReactiveDomain.Testing 5 | { 6 | public static class Catch 7 | { 8 | public static async Task Exception(Func action) 9 | { 10 | Exception caught = null; 11 | try 12 | { 13 | await action(); 14 | } 15 | catch (Exception exception) 16 | { 17 | caught = exception; 18 | } 19 | return caught; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ExpectEventsScenario.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class ExpectEventsScenario 6 | { 7 | public RecordedEvent[] Givens { get; } 8 | public object When { get; } 9 | public RecordedEvent[] Thens { get; } 10 | 11 | public ExpectEventsScenario( 12 | RecordedEvent[] givens, 13 | object when, 14 | RecordedEvent[] thens) 15 | { 16 | Givens = givens ?? throw new ArgumentNullException(nameof(givens)); 17 | When = when ?? throw new ArgumentNullException(nameof(when)); 18 | Thens = thens ?? throw new ArgumentNullException(nameof(thens)); 19 | } 20 | 21 | public ExpectEventsScenarioPassed Pass() 22 | { 23 | return new ExpectEventsScenarioPassed(this); 24 | } 25 | 26 | public ScenarioExpectedEventsButThrewException ButThrewException(Exception threw) 27 | { 28 | return new ScenarioExpectedEventsButThrewException(this, threw); 29 | } 30 | 31 | public ScenarioExpectedEventsButRecordedOtherEvents ButRecordedOtherEvents(RecordedEvent[] events) 32 | { 33 | return new ScenarioExpectedEventsButRecordedOtherEvents(this, events); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ExpectEventsScenarioPassed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class ExpectEventsScenarioPassed 6 | { 7 | public ExpectEventsScenarioPassed(ExpectEventsScenario scenario) 8 | { 9 | Scenario = scenario ?? throw new ArgumentNullException(nameof(scenario)); 10 | } 11 | 12 | public ExpectEventsScenario Scenario { get; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ExpectExceptionScenario.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class ExpectExceptionScenario 6 | { 7 | public RecordedEvent[] Givens { get; } 8 | public object When { get; } 9 | public Exception Throws { get; } 10 | 11 | public ExpectExceptionScenario( 12 | RecordedEvent[] givens, 13 | object when, 14 | Exception throws) 15 | { 16 | Givens = givens ?? throw new ArgumentNullException(nameof(givens)); 17 | When = when ?? throw new ArgumentNullException(nameof(when)); 18 | Throws = throws ?? throw new ArgumentNullException(nameof(throws)); 19 | } 20 | 21 | public ExpectExceptionScenarioPassed Pass() 22 | { 23 | return new ExpectExceptionScenarioPassed(this); 24 | } 25 | 26 | public ScenarioExpectedExceptionButThrewOtherException ButThrewException(Exception threw) 27 | { 28 | return new ScenarioExpectedExceptionButThrewOtherException(this, threw); 29 | } 30 | 31 | public ScenarioExpectedExceptionButThrewNoException ButThrewNoException() 32 | { 33 | return new ScenarioExpectedExceptionButThrewNoException(this); 34 | } 35 | 36 | public ScenarioExpectedExceptionButRecordedEvents ButRecordedEvents(RecordedEvent[] events) 37 | { 38 | return new ScenarioExpectedExceptionButRecordedEvents(this, events); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ExpectExceptionScenarioPassed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class ExpectExceptionScenarioPassed 6 | { 7 | public ExpectExceptionScenarioPassed(ExpectExceptionScenario scenario) 8 | { 9 | Scenario = scenario ?? throw new ArgumentNullException(nameof(scenario)); 10 | } 11 | 12 | public ExpectExceptionScenario Scenario { get; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/IExpectEventsScenarioBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Testing 2 | { 3 | public interface IExpectEventsScenarioBuilder 4 | { 5 | ExpectEventsScenario Build(); 6 | } 7 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/IExpectExceptionScenarioBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Testing 2 | { 3 | public interface IExpectExceptionScenarioBuilder 4 | { 5 | ExpectExceptionScenario Build(); 6 | } 7 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/IScenarioGivenNoneStateBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Testing 2 | { 3 | public interface IScenarioGivenNoneStateBuilder 4 | { 5 | IScenarioWhenStateBuilder When(object command); 6 | } 7 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/IScenarioGivenStateBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public interface IScenarioGivenStateBuilder 6 | { 7 | IScenarioGivenStateBuilder Given(IEnumerable events); 8 | IScenarioWhenStateBuilder When(object command); 9 | } 10 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/IScenarioInitialStateBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public interface IScenarioInitialStateBuilder 6 | { 7 | IScenarioGivenNoneStateBuilder GivenNone(); 8 | IScenarioGivenStateBuilder Given(IEnumerable events); 9 | IScenarioWhenStateBuilder When(object command); 10 | } 11 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/IScenarioThenNoneStateBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Testing 2 | { 3 | public interface IScenarioThenNoneStateBuilder : IExpectEventsScenarioBuilder 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/IScenarioThenStateBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public interface IScenarioThenStateBuilder : IExpectEventsScenarioBuilder 6 | { 7 | IScenarioThenStateBuilder Then(IEnumerable events); 8 | } 9 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/IScenarioThrowsStateBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace ReactiveDomain.Testing 2 | { 3 | public interface IScenarioThrowsStateBuilder : IExpectExceptionScenarioBuilder 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/IScenarioWhenStateBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ReactiveDomain.Testing 5 | { 6 | public interface IScenarioWhenStateBuilder 7 | { 8 | IScenarioThenNoneStateBuilder ThenNone(); 9 | IScenarioThenStateBuilder Then(IEnumerable events); 10 | IScenarioThrowsStateBuilder Throws(Exception exception); 11 | } 12 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ScenarioExpectedEventsButRecordedOtherEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class ScenarioExpectedEventsButRecordedOtherEvents 6 | { 7 | public ScenarioExpectedEventsButRecordedOtherEvents(ExpectEventsScenario scenario, RecordedEvent[] actual) 8 | { 9 | Scenario = scenario ?? throw new ArgumentNullException(nameof(scenario)); 10 | Actual = actual ?? throw new ArgumentNullException(nameof(actual)); 11 | } 12 | 13 | public ExpectEventsScenario Scenario { get; } 14 | public RecordedEvent[] Actual { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ScenarioExpectedEventsButThrewException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class ScenarioExpectedEventsButThrewException 6 | { 7 | public ScenarioExpectedEventsButThrewException(ExpectEventsScenario scenario, Exception actual) 8 | { 9 | Scenario = scenario ?? throw new ArgumentNullException(nameof(scenario)); 10 | Actual = actual ?? throw new ArgumentNullException(nameof(actual)); 11 | } 12 | 13 | public ExpectEventsScenario Scenario { get; } 14 | public Exception Actual { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ScenarioExpectedExceptionButRecordedEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class ScenarioExpectedExceptionButRecordedEvents 6 | { 7 | public ScenarioExpectedExceptionButRecordedEvents(ExpectExceptionScenario scenario, RecordedEvent[] actual) 8 | { 9 | Scenario = scenario ?? throw new ArgumentNullException(nameof(scenario)); 10 | Actual = actual ?? throw new ArgumentNullException(nameof(actual)); 11 | } 12 | 13 | public ExpectExceptionScenario Scenario { get; } 14 | public RecordedEvent[] Actual { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ScenarioExpectedExceptionButThrewNoException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class ScenarioExpectedExceptionButThrewNoException 6 | { 7 | public ScenarioExpectedExceptionButThrewNoException(ExpectExceptionScenario scenario) 8 | { 9 | Scenario = scenario ?? throw new ArgumentNullException(nameof(scenario)); 10 | } 11 | 12 | public ExpectExceptionScenario Scenario { get; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ScenarioExpectedExceptionButThrewOtherException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class ScenarioExpectedExceptionButThrewOtherException 6 | { 7 | public ScenarioExpectedExceptionButThrewOtherException(ExpectExceptionScenario scenario, Exception actual) 8 | { 9 | Scenario = scenario ?? throw new ArgumentNullException(nameof(scenario)); 10 | Actual = actual ?? throw new ArgumentNullException(nameof(actual)); 11 | } 12 | 13 | public ExpectExceptionScenario Scenario { get; } 14 | public Exception Actual { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Domain/ScenarioExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ReactiveDomain.Testing 5 | { 6 | public static class ScenarioExtensions 7 | { 8 | public static IScenarioGivenStateBuilder Given( 9 | this IScenarioInitialStateBuilder builder, 10 | StreamName stream, 11 | params object[] events) 12 | { 13 | if (events == null) throw new ArgumentNullException(nameof(events)); 14 | return builder.Given(events.Select(@event => new RecordedEvent(stream, @event))); 15 | } 16 | 17 | public static IScenarioGivenStateBuilder Given( 18 | this IScenarioGivenStateBuilder builder, 19 | StreamName stream, 20 | params object[] events) 21 | { 22 | if (events == null) throw new ArgumentNullException(nameof(events)); 23 | return builder.Given(events.Select(@event => new RecordedEvent(stream, @event))); 24 | } 25 | 26 | public static IScenarioThenStateBuilder Then( 27 | this IScenarioWhenStateBuilder builder, 28 | StreamName stream, 29 | params object[] events) 30 | { 31 | if (events == null) throw new ArgumentNullException(nameof(events)); 32 | return builder.Then(events.Select(@event => new RecordedEvent(stream, @event))); 33 | } 34 | 35 | public static IScenarioThenStateBuilder Then( 36 | this IScenarioThenStateBuilder builder, 37 | StreamName stream, 38 | params object[] events) 39 | { 40 | if (events == null) throw new ArgumentNullException(nameof(events)); 41 | return builder.Then(events.Select(@event => new RecordedEvent(stream, @event))); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/EventStore/EmbeddedStreamStoreConnectionCollection.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | // ReSharper disable once CheckNamespace 4 | namespace ReactiveDomain.Testing 5 | { 6 | //n.b a copy of this marker class must be in the assembly with the test classes 7 | //copy and place in the local namespace to avoid collisions 8 | [CollectionDefinition(nameof(EmbeddedStreamStoreConnectionCollection))] 9 | public class EmbeddedStreamStoreConnectionCollection : ICollectionFixture 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Foundation/TestAggregateCreated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Messaging; 3 | 4 | namespace ReactiveDomain.Testing 5 | { 6 | public class TestWoftamAggregateCreated : IEvent 7 | { 8 | public Guid MsgId { get; private set; } 9 | public Guid AggregateId { get; private set; } 10 | public TestWoftamAggregateCreated(Guid aggregateId) 11 | { 12 | MsgId = Guid.NewGuid(); 13 | AggregateId = aggregateId; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Foundation/TestAggregateMessages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Messaging; 3 | 4 | // ReSharper disable MemberCanBePrivate.Global 5 | // ReSharper disable ClassNeverInstantiated.Global 6 | // ReSharper disable NotAccessedField.Global 7 | namespace ReactiveDomain.Testing 8 | { 9 | public class TestAggregateMessages 10 | { 11 | public class NewAggregate : Event 12 | { 13 | public readonly Guid AggregateId; 14 | public NewAggregate(Guid aggregateId) 15 | { 16 | AggregateId = aggregateId; 17 | } 18 | } 19 | public class NewAggregate2 : Event 20 | { 21 | public readonly Guid AggregateId; 22 | public NewAggregate2(Guid aggregateId) 23 | { 24 | AggregateId = aggregateId; 25 | } 26 | } 27 | public class Increment : Event 28 | { 29 | public readonly Guid AggregateId; 30 | public readonly uint Amount; 31 | public Increment(Guid aggregateId, uint amount) 32 | { 33 | AggregateId = aggregateId; 34 | Amount = amount; 35 | } 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Foundation/TestWoftamAggregate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Testing 4 | { 5 | public class TestWoftamAggregate : EventDrivenStateMachine 6 | { 7 | public TestWoftamAggregate(Guid aggregateId) : this() 8 | { 9 | Raise(new TestWoftamAggregateCreated(aggregateId)); 10 | } 11 | 12 | private TestWoftamAggregate() 13 | { 14 | Register(e => Id = e.AggregateId); 15 | Register(e => AppliedEventCount++); 16 | } 17 | 18 | public int AppliedEventCount { get; private set; } 19 | 20 | public void ProduceEvents(int count) 21 | { 22 | for (int i = 0; i < count; i++) 23 | Raise(new WoftamEvent("Woftam1-" + i, "Woftam2-" + i)); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Foundation/WoftamEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Messaging; 3 | 4 | namespace ReactiveDomain.Testing 5 | { 6 | public class WoftamEvent : IEvent 7 | { 8 | public Guid MsgId { get; private set; } 9 | public string Property1 { get; private set; } 10 | public string Property2 { get; private set; } 11 | 12 | public WoftamEvent(string property1, string property2) 13 | { 14 | MsgId = Guid.NewGuid(); 15 | Property1 = property1; 16 | Property2 = property2; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Messaging/MessageComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ReactiveDomain.Messaging; 3 | 4 | // ReSharper disable once CheckNamespace 5 | namespace ReactiveDomain.Testing 6 | { 7 | public class MessageIdComparer: IEqualityComparer 8 | { 9 | public bool Equals(IMessage x, IMessage y) 10 | { 11 | return x.MsgId == y.MsgId; 12 | } 13 | 14 | public int GetHashCode(IMessage obj) 15 | { 16 | return obj.MsgId.GetHashCode(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Messaging/TestCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using ReactiveDomain.Messaging; 3 | using ReactiveDomain.Messaging.Bus; 4 | 5 | // ReSharper disable once CheckNamespace 6 | namespace ReactiveDomain.Testing 7 | { 8 | //No cancellation support 9 | public class TestCommandHandler : 10 | IHandleCommand 11 | { 12 | 13 | public CommandResponse Handle(TestCommands.Command3 command) 14 | { 15 | SpinWait.SpinUntil(()=>false, 500); 16 | return command.Succeed(); 17 | } 18 | 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Messaging/TestCommandSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using ReactiveDomain.Messaging; 3 | using ReactiveDomain.Messaging.Bus; 4 | 5 | // ReSharper disable once CheckNamespace 6 | namespace ReactiveDomain.Testing 7 | { 8 | public class TestCommandSubscriber : 9 | IHandleCommand, 10 | IHandleCommand 11 | { 12 | public long TestCommand2Handled; 13 | public long TestCommand3Handled; 14 | 15 | // ReSharper disable once FieldCanBeMadeReadOnly.Local 16 | private IDispatcher _bus; 17 | 18 | public TestCommandSubscriber(IDispatcher bus) 19 | { 20 | _bus = bus; 21 | TestCommand2Handled = 0; 22 | _bus.Subscribe (this); 23 | _bus.Subscribe(this); 24 | } 25 | 26 | 27 | public CommandResponse Handle(TestCommands.Command2 command) 28 | { 29 | Interlocked.Increment(ref TestCommand2Handled); 30 | return command.Succeed(); 31 | } 32 | 33 | public CommandResponse Handle(TestCommands.Command3 command) 34 | { 35 | Interlocked.Increment(ref TestCommand3Handled); 36 | return command.Succeed(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Messaging/TestEvents.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using ReactiveDomain.Messaging; 3 | using System; 4 | 5 | // ReSharper disable MemberCanBeProtected.Global 6 | // ReSharper disable once CheckNamespace 7 | namespace ReactiveDomain.Testing 8 | { 9 | public class TestEvent : Event { } 10 | public class ParentTestEvent : Message { } 11 | public class ChildTestEvent : ParentTestEvent { } 12 | public class GrandChildTestEvent : ChildTestEvent { } 13 | 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Messaging/TestMessageSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using ReactiveDomain.Messaging.Bus; 3 | 4 | // ReSharper disable once CheckNamespace 5 | namespace ReactiveDomain.Testing 6 | { 7 | public class TestQueuedSubscriber : 8 | QueuedSubscriber, 9 | IHandle, 10 | IHandle, 11 | IHandle 12 | { 13 | public long TimesChildTestMessageHandled; 14 | public long ParentTestMessage; 15 | public long TimesTestMessageHandled; 16 | public long TimesTestMessage2Handled; 17 | 18 | public TestQueuedSubscriber(IDispatcher bus) : base(bus) 19 | { 20 | TimesTestMessageHandled = 0; 21 | TimesTestMessage2Handled = 0; 22 | TimesChildTestMessageHandled = 0; 23 | ParentTestMessage = 0; 24 | 25 | Subscribe(this); 26 | Subscribe(this); 27 | Subscribe(this); 28 | } 29 | 30 | public void Handle(TestMessage message) 31 | { 32 | Interlocked.Increment(ref TimesTestMessageHandled); 33 | } 34 | 35 | public void Handle(TestMessage2 message) 36 | { 37 | Interlocked.Increment(ref TimesTestMessage2Handled); 38 | } 39 | 40 | public void Handle(ChildTestMessage message) 41 | { 42 | Interlocked.Increment(ref TimesChildTestMessageHandled); 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Messaging/TestMessages.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Messaging; 2 | using System; 3 | 4 | // ReSharper disable MemberCanBeProtected.Global 5 | // ReSharper disable once CheckNamespace 6 | namespace ReactiveDomain.Testing { 7 | 8 | public class TestMessage : Message {} 9 | public class TestMessage2 : Message {} 10 | public class TestMessage3 : Message {} 11 | public class ParentTestMessage : Message {} 12 | public class ChildTestMessage : ParentTestMessage {} 13 | public class GrandChildTestMessage : ChildTestMessage {} 14 | public class CountedTestMessage : Message 15 | { 16 | public int MessageNumber; 17 | public CountedTestMessage(int msgNumber) { 18 | MessageNumber = msgNumber; 19 | } 20 | } 21 | 22 | public class CountedEvent : Message, ICorrelatedMessage { 23 | public Guid CorrelationId { get; set; } 24 | public Guid CausationId { get; set; } 25 | public int MessageNumber; 26 | public CountedEvent(int msgNumber) { 27 | MessageNumber = msgNumber; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Messaging/TestTimeSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using ReactiveDomain.Messaging.Bus; 5 | 6 | namespace ReactiveDomain.Testing { 7 | public class TestTimeSource : ITimeSource { 8 | private long _virtualTime; 9 | public void AdvanceTime(long msDistance) { 10 | if (msDistance < 0) { throw new ArgumentOutOfRangeException(); } 11 | _virtualTime += msDistance; 12 | 13 | lock (_waiting) { 14 | foreach (var mres in _waiting) { 15 | mres.Set(); 16 | } 17 | } 18 | } 19 | private readonly List _waiting = new List(); 20 | 21 | public TimePosition Now() { 22 | return new TimePosition(_virtualTime); 23 | } 24 | public void WaitFor(TimePosition position, ManualResetEventSlim waitEvent) { 25 | var waitTime = Now().DistanceUntil(position); 26 | if (waitTime == TimeSpan.Zero) { return; } 27 | 28 | lock (_waiting) { _waiting.Add(waitEvent); } 29 | waitEvent.Wait(waitTime); 30 | lock (_waiting) { _waiting.Remove(waitEvent); } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/ReactiveDomain.Testing.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Specifications/DispatcherSpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Messaging; 3 | using ReactiveDomain.Messaging.Bus; 4 | 5 | namespace ReactiveDomain.Testing 6 | { 7 | public abstract class DispatcherSpecification : IDisposable 8 | { 9 | public readonly IDispatcher Dispatcher; 10 | public readonly IDispatcher LocalBus; 11 | public readonly TestQueue TestQueue; 12 | 13 | protected DispatcherSpecification() 14 | { 15 | Dispatcher = new Dispatcher("Fixture Bus", slowMsgThreshold: TimeSpan.FromMilliseconds(500)); 16 | LocalBus = new Dispatcher("Fixture LocalBus"); 17 | TestQueue = new TestQueue(Dispatcher, new[] { typeof(Event), typeof(Command) }); 18 | } 19 | 20 | private bool _disposed; 21 | protected virtual void Dispose(bool disposing) 22 | { 23 | if (_disposed) 24 | return; 25 | if (disposing) 26 | { 27 | Dispatcher?.Dispose(); 28 | LocalBus?.Dispose(); 29 | TestQueue?.Dispose(); 30 | } 31 | _disposed = true; 32 | } 33 | 34 | public void Dispose() 35 | { 36 | Dispose(true); 37 | GC.SuppressFinalize(this); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/Specifications/ReadModelTestSpecification.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Foundation; 2 | using ReactiveDomain.Messaging; 3 | using ReactiveDomain.Messaging.Bus; 4 | using System.Collections.Generic; 5 | 6 | namespace ReactiveDomain.Testing 7 | { 8 | /// 9 | /// A base test class for testing read models. Includes a 10 | /// that can be used creating read models under test without needing additional infrastructure, and can be used itself as an 11 | /// to collect published messages from read models. These messages are recorded in for use in tests. 12 | /// 13 | public abstract class ReadModelTestSpecification : IPublisher 14 | { 15 | /// 16 | /// Adds the message onto the test class's list of published messages. 17 | /// Required for implementation of . 18 | /// 19 | /// The message to add. 20 | void IPublisher.Publish(IMessage message) 21 | { 22 | PublishedMessages.Add(message); 23 | } 24 | /// 25 | /// The list of messages recorded from the test class's Publish method. 26 | /// 27 | protected List PublishedMessages = new List(); 28 | /// 29 | /// A for use in creating read models under test without needing additional infrastructure. 30 | /// 31 | protected IConfiguredConnection Connection = new NullConfiguredConnection(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Testing/TestPublisher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Messaging; 3 | using ReactiveDomain.Messaging.Bus; 4 | 5 | namespace ReactiveDomain.Testing 6 | { 7 | public class TestPublisher : IPublisher 8 | { 9 | private readonly Action _publish; 10 | 11 | public TestPublisher(Action publish) 12 | { 13 | _publish = publish; 14 | } 15 | 16 | public void Publish(IMessage msg) 17 | { 18 | _publish(msg); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Tools/EventTransformer/DummyEventTransformer.cs: -------------------------------------------------------------------------------- 1 | using EventStore.ClientAPI; 2 | using Shovel; 3 | using System; 4 | 5 | namespace EventTransformer 6 | { 7 | using System.Collections.Generic; 8 | 9 | public class DummyEventTransformer : IEventTransformer 10 | { 11 | public ICollection Transform(ResolvedEvent sourceEvent) 12 | { 13 | Console.WriteLine($"Doing dummy transformation for event {sourceEvent.Event.EventId}"); 14 | return new List() {sourceEvent}; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Tools/EventTransformer/EventTransformer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Tools/EventTransformer/TransformerFactory.cs: -------------------------------------------------------------------------------- 1 | using Shovel; 2 | 3 | namespace EventTransformer 4 | { 5 | public class TransformerFactory 6 | { 7 | public static IEventTransformer GetEventTransformer() 8 | { 9 | return new DummyEventTransformer(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Tools/Shovel/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Tools/Shovel/EventShovelConfig.cs: -------------------------------------------------------------------------------- 1 | using EventStore.ClientAPI; 2 | using EventStore.ClientAPI.SystemData; 3 | using System.Collections.Generic; 4 | 5 | namespace Shovel 6 | { 7 | public class EventShovelConfig 8 | { 9 | public IEventStoreConnection SourceConnection { get; set; } 10 | 11 | public IEventStoreConnection TargetConnection { get; set; } 12 | 13 | public UserCredentials SourceCredentials { get; set; } 14 | 15 | public UserCredentials TargetCredentials { get; set; } 16 | 17 | public IEventTransformer EventTransformer { get; set; } 18 | 19 | public IList StreamFilter { get; } 20 | 21 | public IList StreamWildcardFilter { get; } 22 | 23 | public IList EventTypeFilter { get; } 24 | 25 | public IList EventTypeWildcardFilter { get; } 26 | 27 | public EventShovelConfig() 28 | { 29 | StreamFilter = new List(); 30 | StreamWildcardFilter = new List(); 31 | EventTypeFilter = new List(); 32 | EventTypeWildcardFilter = new List(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Tools/Shovel/IEventTransformer.cs: -------------------------------------------------------------------------------- 1 | namespace Shovel 2 | { 3 | using System.Collections.Generic; 4 | using EventStore.ClientAPI; 5 | 6 | public interface IEventTransformer 7 | { 8 | ICollection Transform(ResolvedEvent sourceEvent); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Tools/Shovel/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Shovel 4 | { 5 | class Program 6 | { 7 | private static Bootstrap _bootstrap; 8 | 9 | static void Main(string[] args) 10 | { 11 | if (args.Length == 1 && args[0] == "help") 12 | { 13 | Console.WriteLine("Use: Shovel {stream=[stream name]} {eventtype=[event type]}"); 14 | Console.WriteLine("if stream name or event type ends with '*' symbol it will be used as wildcard"); 15 | return; 16 | } 17 | 18 | _bootstrap = new Bootstrap(); 19 | 20 | _bootstrap.Load(); 21 | 22 | if (_bootstrap.Loaded) 23 | { 24 | if (args.Length > 0) 25 | { 26 | _bootstrap.PrepareFilters(args); 27 | } 28 | 29 | _bootstrap.Run(); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Tools/Shovel/Shovel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Tools/Shovel/Shovel.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.452 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shovel", "Shovel.csproj", "{ECB6A5EA-D7DF-4F46-B4B1-FE9B0D6264AA}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventTransformer", "..\EventTransformer\EventTransformer.csproj", "{B8CCB769-5DCD-4F3F-907A-9C24B66034E2}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {ECB6A5EA-D7DF-4F46-B4B1-FE9B0D6264AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {ECB6A5EA-D7DF-4F46-B4B1-FE9B0D6264AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {ECB6A5EA-D7DF-4F46-B4B1-FE9B0D6264AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {ECB6A5EA-D7DF-4F46-B4B1-FE9B0D6264AA}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {B8CCB769-5DCD-4F3F-907A-9C24B66034E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {B8CCB769-5DCD-4F3F-907A-9C24B66034E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {B8CCB769-5DCD-4F3F-907A-9C24B66034E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {B8CCB769-5DCD-4F3F-907A-9C24B66034E2}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {4AF701DC-BF40-498F-99CA-EAAA2E0C5089} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport.Tests/ReactiveDomain.Transport.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(TestTargetFrameworks) 5 | true 6 | 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport.Tests/can_serialize_messages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Messaging; 3 | using ReactiveDomain.Transport.Serialization; 4 | using Xunit; 5 | 6 | namespace ReactiveDomain.Transport.Tests 7 | { 8 | // ReSharper disable InconsistentNaming 9 | public class when_creating_tcp_messages 10 | { 11 | private const string Prop1 = "prop1"; 12 | private const string Prop2 = "prop2"; 13 | private readonly WoftamEvent _testEvent = new WoftamEvent(Prop1, Prop2); 14 | 15 | [Fact] 16 | public void can_serialize_from_message() 17 | { 18 | var serializer = new SimpleJsonSerializer(); 19 | var json = serializer.SerializeMessage(_testEvent); 20 | var encoder = new TcpMessageEncoder(); 21 | var data = encoder.ToBytes(json, _testEvent.GetType()); 22 | var decoded = encoder.FromBytes(data); 23 | var msg = serializer.DeserializeMessage(decoded.Item1, decoded.Item2); 24 | Assert.IsType(msg); 25 | var msg2 = msg as WoftamEvent; 26 | Assert.NotNull(msg2); 27 | // ReSharper disable once PossibleNullReferenceException 28 | Assert.Equal(_testEvent.Property1, msg2.Property1); 29 | Assert.Equal(_testEvent.Property2, msg2.Property2); 30 | } 31 | } 32 | 33 | public class WoftamEvent : IMessage 34 | { 35 | public Guid MsgId { get; } 36 | public WoftamEvent(string property1, string property2) 37 | { 38 | MsgId = Guid.NewGuid(); 39 | Property1 = property1; 40 | Property2 = property2; 41 | } 42 | 43 | public string Property1 { get; } 44 | public string Property2 { get; } 45 | 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/Framing/IMessageFramer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ReactiveDomain.Transport.Framing 5 | { 6 | /// 7 | /// Encodes outgoing messages in frames and decodes incoming frames. 8 | /// For decoding it uses an internal state, raising a registered 9 | /// callback, once full message arrives 10 | /// 11 | public interface IMessageFramer 12 | { 13 | void UnFrameData(Guid routeId, IEnumerable> data); 14 | void UnFrameData(Guid routeId, ArraySegment data); 15 | IEnumerable> FrameData(ArraySegment data); 16 | 17 | void RegisterMessageArrivedCallback(Action> handler); 18 | } 19 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/Framing/PackageFramingException.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System; 4 | using System.Runtime.Serialization; 5 | 6 | namespace ReactiveDomain.Transport.Framing 7 | { 8 | public class PackageFramingException: Exception 9 | { 10 | public PackageFramingException() 11 | { 12 | } 13 | 14 | public PackageFramingException(string message) : base(message) 15 | { 16 | } 17 | 18 | public PackageFramingException(string message, Exception innerException) : base(message, innerException) 19 | { 20 | } 21 | 22 | #if NET8_0_OR_GREATER 23 | [Obsolete(DiagnosticId = "SYSLIB0051")] 24 | #endif 25 | protected PackageFramingException(SerializationInfo info, StreamingContext context) : base(info, context) 26 | { 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/IMonitoredTcpConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDomain.Transport 4 | { 5 | public interface IMonitoredTcpConnection 6 | { 7 | bool IsReadyForSend { get; } 8 | bool IsReadyForReceive { get; } 9 | bool IsInitialized { get; } 10 | bool IsFaulted { get; } 11 | bool IsClosed { get; } 12 | 13 | bool InSend { get; } 14 | bool InReceive { get; } 15 | 16 | DateTime? LastSendStarted { get; } 17 | DateTime? LastReceiveStarted { get; } 18 | 19 | int PendingSendBytes { get; } 20 | int InSendBytes { get; } 21 | int PendingReceivedBytes { get; } 22 | 23 | long TotalBytesSent { get; } 24 | long TotalBytesReceived { get; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/ReactiveDomain.Transport.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(LibTargetFrameworks) 5 | true 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/Serialization/IMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveDomain.Messaging; 3 | 4 | namespace ReactiveDomain.Transport.Serialization 5 | { 6 | public interface IMessageSerializer 7 | { 8 | IMessage DeserializeMessage(string json, Type messageType); 9 | string SerializeMessage(IMessage message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/Serialization/SimpleJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using ReactiveDomain.Messaging; 4 | 5 | namespace ReactiveDomain.Transport.Serialization 6 | { 7 | public class SimpleJsonSerializer : IMessageSerializer 8 | { 9 | public IMessage DeserializeMessage(string json, Type messageType) => (IMessage)JsonConvert.DeserializeObject(json, messageType, Json.JsonSettings); 10 | public string SerializeMessage(IMessage message) => JsonConvert.SerializeObject(message, Json.JsonSettings); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/SocketArgsPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Net.Sockets; 4 | 5 | namespace ReactiveDomain.Transport 6 | { 7 | internal class SocketArgsPool 8 | { 9 | public readonly string Name; 10 | 11 | private readonly Func _socketArgsCreator; 12 | private readonly ConcurrentStack _socketArgsPool = new ConcurrentStack(); 13 | 14 | public SocketArgsPool(string name, int initialCount, Func socketArgsCreator) 15 | { 16 | if (socketArgsCreator == null) 17 | throw new ArgumentNullException("socketArgsCreator"); 18 | if (initialCount < 0) 19 | throw new ArgumentOutOfRangeException("initialCount"); 20 | 21 | Name = name; 22 | _socketArgsCreator = socketArgsCreator; 23 | 24 | for (int i = 0; i < initialCount; ++i) 25 | { 26 | _socketArgsPool.Push(socketArgsCreator()); 27 | } 28 | } 29 | 30 | public SocketAsyncEventArgs Get() 31 | { 32 | SocketAsyncEventArgs result; 33 | if (_socketArgsPool.TryPop(out result)) 34 | return result; 35 | return _socketArgsCreator(); 36 | } 37 | 38 | public void Return(SocketAsyncEventArgs socketArgs) 39 | { 40 | _socketArgsPool.Push(socketArgs); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/SystemData/InspectionResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using ReactiveDomain.Util; 4 | 5 | namespace ReactiveDomain.Transport.SystemData 6 | { 7 | internal class InspectionResult 8 | { 9 | public readonly InspectionDecision Decision; 10 | public readonly string Description; 11 | public readonly IPEndPoint TcpEndPoint; 12 | public readonly IPEndPoint SecureTcpEndPoint; 13 | 14 | public InspectionResult(InspectionDecision decision, string description, IPEndPoint tcpEndPoint = null, IPEndPoint secureTcpEndPoint = null) 15 | { 16 | if (decision == InspectionDecision.Reconnect) 17 | Ensure.NotNull(tcpEndPoint, "tcpEndPoint"); 18 | else 19 | { 20 | if (tcpEndPoint != null) 21 | throw new ArgumentException(string.Format("tcpEndPoint is not null for decision {0}.", decision)); 22 | } 23 | 24 | Decision = decision; 25 | Description = description; 26 | TcpEndPoint = tcpEndPoint; 27 | SecureTcpEndPoint = secureTcpEndPoint; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/SystemData/UserCredentials.cs: -------------------------------------------------------------------------------- 1 | using ReactiveDomain.Util; 2 | 3 | namespace ReactiveDomain.Transport.SystemData 4 | { 5 | /// 6 | /// A username/password pair used for authentication and 7 | /// authorization to perform operations over an . 8 | /// 9 | public class UserCredentials 10 | { 11 | /// 12 | /// The username 13 | /// 14 | public readonly string Username; 15 | /// 16 | /// The password 17 | /// 18 | public readonly string Password; 19 | 20 | /// 21 | /// Constructs a new . 22 | /// 23 | /// 24 | /// 25 | public UserCredentials(string username, string password) 26 | { 27 | Ensure.NotNull(username, "username"); 28 | Ensure.NotNull(password, "password"); 29 | 30 | Username = username; 31 | Password = password; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ReactiveDomain.Transport/Util/IPEndPointComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | 4 | namespace ReactiveDomain.Transport.Util 5 | { 6 | public class IPEndPointComparer : IComparer 7 | { 8 | public int Compare(IPEndPoint x, IPEndPoint y) 9 | { 10 | var xx = x.Address.ToString(); 11 | var yy = y.Address.ToString(); 12 | var result = string.CompareOrdinal(xx, yy); 13 | return result == 0 ? x.Port.CompareTo(y.Port) : result; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/ReactiveDomain.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | DTO 3 | RM 4 | STS 5 | True 6 | True 7 | True 8 | True -------------------------------------------------------------------------------- /src/ci.build.imports: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tools/wget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveDomain/reactive-domain/05e5268f0ceef1034885905402590486fcb6fcad/tools/wget.exe --------------------------------------------------------------------------------