-
Notifications
You must be signed in to change notification settings - Fork 22
Reactive documentation #169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Reactive documentation #169
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReadModelBase
and the MessageBuilder
factory are key components, as are the Command
and Event
classes that implement ICorrelatedMessage
. It would be helpful to include them in the types that are covered by the docs. (Command
and Event
are mentioned briefly, but could be highlighted more.)
|
||
## Breaking Changes and Deprecations | ||
|
||
### Version 2.0.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of the version numbers in this document have no connection to the actual release version numbers.
|
||
To run these examples, you'll need: | ||
|
||
- .NET Core 3.1 or later |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong version of .NET
…rences and enhance documentation - Fix GitHub badges to point to ReactiveDomain organization - Add detailed documentation for ReadModelBase component - Create comprehensive documentation for MessageBuilder factory - Enhance documentation for Command and Event classes - Add documentation for ICorrelatedMessage interface - Update repository references in workshop materials - Improve documentation structure and navigation
this shouldn't be part of the documentation, it is an artifact of for creating the documentation
t fetch origin Merge branch 'reactive-documentation' of github.com:leopoldodonnell/reactive-domain into reactive-documentation Getting the branch up to date
… diagrams for key concepts
```csharp | ||
// Create a command with correlation information from an existing message | ||
ICorrelatedMessage existingCommand = // ... existing command | ||
ICorrelatedMessage newCommand = MessageBuilder.From(existingCommand, () => new DepositFunds(accountId, amount)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be
ICorrelatedMessage newCommand = MessageBuilder.From(existingCommand).Build(() => new DepositFunds(accountId, amount));
It seems the Build
method is missing from the class definition above, so not surprising that it's missing here.
### In an Aggregate | ||
|
||
Messages are often created within aggregates in response to commands: | ||
|
||
```csharp | ||
public class Account : AggregateRoot | ||
{ | ||
public Account(Guid id, ICorrelatedMessage source) : base(id) | ||
{ | ||
// Create a new event from the source command | ||
Apply(MessageBuilder.From(source, () => new AccountCreated(id))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should never be done this way. AggregateRoot
adds correlation information automatically to raised events, and you would never want to add new correlation info when rehydrating an aggregate.
## Integration with Event Handlers | ||
|
||
Read models are typically updated by event handlers that subscribe to domain events: | ||
|
||
```csharp | ||
public class AccountEventHandler : IEventHandler<AccountCreated>, IEventHandler<FundsDeposited> | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes no sense and is not how ReadModelBase
should be used.
The typical usage pattern is to implement event handlers in the read model class and have those handlers subscribed to EventStream
from the base class, then start playing back the event stream using Start<T>(id)
.
|
||
Read models in Reactive Domain represent the query side of the CQRS pattern. They are optimized for querying and provide a denormalized view of the domain data. The `ReadModelBase` class provides a common foundation for implementing read models with consistent behavior. | ||
|
||
## Class Definition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also include a mention of using the ReaderLock
object in the read model's public query methods. It gives a way to ensure cross-thread synchronization between event handlers and public query methods since the object is automatically locked in event handlers.
docs/api-reference/types/command.md
Outdated
public CreateAccount(Guid accountId, string accountNumber, string customerName, | ||
Guid correlationId, Guid causationId) | ||
: base(correlationId, causationId) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This overload of the base ctor should not be used explicitly. Use the MessageBuilder
factory to set correlation and causation IDs. Most classes that inherit from Command
will implicitly use the default ctor.
docs/api-reference/types/command.md
Outdated
var depositCommand = MessageBuilder.From(createCommand, () => new DepositFunds( | ||
((CreateAccount)createCommand).AccountId, | ||
100.00m | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use .From(createCommand).Build(...)
here.
Commands are typically handled by command handlers: | ||
|
||
```csharp | ||
public class CreateAccountHandler : ICommandHandler<CreateAccount> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is atypical. A class that handles commands will typically implement IHandleCommand<T>
for each type that it handles.
docs/api-reference/types/command.md
Outdated
{ | ||
public Account(Guid id, ICorrelatedMessage source) : base(id) | ||
{ | ||
Apply(MessageBuilder.From(source, () => new AccountCreated(id, source.CorrelationId, source.MsgId))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. See previous comment about this.
public AccountCreated(Guid accountId, string accountNumber, string customerName, | ||
Guid correlationId, Guid causationId) | ||
: base(correlationId, causationId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment here as for Command
docs/api-reference/types/event.md
Outdated
var createdEvent = MessageBuilder.From(command, () => new AccountCreated( | ||
Guid.NewGuid(), | ||
"ACC-123", | ||
"John Doe" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use From().Build()
.
Also, while technically correct, this would only be done when creating an event to be published on a message bus, and not when creating an event to be raised on an aggregate.
docs/api-reference/types/event.md
Outdated
Events are typically handled by event handlers: | ||
|
||
```csharp | ||
public class AccountCreatedHandler : IEventHandler<AccountCreated> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Events are typically handled in services and in read models by implementing IHandle<T>
.
docs/api-reference/types/event.md
Outdated
|
||
public Account(Guid id, ICorrelatedMessage source) : base(id) | ||
{ | ||
Apply(MessageBuilder.From(source, () => new AccountCreated(id, "ACC-" + id.ToString().Substring(0, 8), "New Customer"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be Raise(new AccountCreated(...))
.
Apply
methods are used for rehydration, not for creating new events.
Same thing a bit lower in this example in the public Deposit
method.
…reate todo list for PR 169
…ing explanations and diagrams
…tation files - Enhanced glossary with more precise definitions for key terms - Clarified the relationship between RaiseEvent() and Apply() methods - Standardized terminology across all documentation files - Improved component relationship descriptions - Added detailed best practices for each component - Ensured consistent event handling descriptions
…for key interfaces and classes in Reactive Domain
…ventory management domains
…nstead of From(source, () => ...)
…yntax instead of From(source, () => ...)
…tead of From(source, () => ...)
@joshkempner - Can you review the examples again. I've made a lot of changes. |
…rn CQRS best practices
…ent subscription patterns
- Added retry patterns with examples - Added circuit breaker pattern implementations - Added bulkhead pattern for isolation - Added timeout pattern strategies - Updated documentation checklist
- Added unit testing approaches for aggregates - Added event handler testing patterns - Added saga and process manager testing - Added integration testing approaches - Added event store testing patterns - Added snapshot and versioning testing - Updated documentation checklist - Updated API reference README.md
- Added advanced test fixtures with exception handling - Added event-specific test fixture with type-safe assertions - Added scenario-based test fixture for complex test cases - Added factory pattern for test fixtures - Added integration with Reactive Domain test framework - Updated documentation checklist
- Updated implementing-projections.md with modern async patterns and DI support - Updated saving-retrieving-aggregates.md with proper repository patterns - Updated handling-commands-events.md with improved command handling - Updated creating-aggregate-root.md with best practices - Added infrastructure-setup.md with modern configuration patterns - Added performance-considerations.md with optimization techniques - Updated documentation checklist to reflect completed sections - Updated API reference README with new links
- Replace inheritance notation '<|--' with standard arrow notation '-->' for better compatibility - Fix the relationship between ICorrelatedRepository and IRepository - Ensure diagram renders correctly in Markdown viewers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review, mostly focused on AggregateRoot
.
[](https://travis-ci.org/linedata/reactive-domain) | ||
[](https://travis-ci.org/ReactiveDomain/reactive-domain) | ||
[](code_of_conduct.md) | ||
[](https://reactivedomain.github.io/reactive-domain/) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This points to a URL that doesn't exist
private bool _isActive; | ||
private string _accountNumber; | ||
|
||
public Account(Guid id) : base(id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AggregateRoot
does not have a ctor that takes an ID and no correlation info. It has two ctors:
- A default one that's used for rehydration (and that should be redefined in any derived classes so that the
Apply
methods are registered) - A ctor that takes an
ICorrelatedMessage
. This is the standard ctor that used when making changes to the aggregate. Passing in the correlated message provides a source whose correlation and causation info are applied automatically to events that are raised using theRaise
method. Derived implementations that call this ctor should also register the same event handlers as the default ctor. A common pattern is to create a privateRegisterHandlers()
method that is called from both ctors.
Most of the examples in this doc include calls to base ctors that don't exist.
RaiseEvent(MessageBuilder.From(source, () => new AccountCreated( | ||
Id, accountNumber, customerName, DateTime.UtcNow))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RaiseEvent
does not exist in AggregateRoot
. The correct method to use for raising events is called Raise()
. If a correlated message has been passed into the ctor or the Source
property is otherwise set, Raise
will apply the correct correlation and causation IDs to any events that it's given.
This same error is repeated in the other examples below.
### AggregateRoot(Guid, IEnumerable\<object\>) | ||
|
||
Initializes a new instance of the `AggregateRoot` class with the specified ID and restores it from the provided events. | ||
Initializes a new instance of the `AggregateRoot` class with the specified ID and restores it from the provided events. This constructor is typically used by repositories when reconstituting an aggregate from its event history. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't exist. Rehydrating an aggregate from its event history is done using EventDrivenStateMachine.RestoreFromEvents(IEnumerable<object> events)
.
``` | ||
|
||
**Remarks**: This method records the event and applies it to the aggregate by calling the appropriate `Apply` method. | ||
### RaiseEvent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't exist. The method is Raise
, which is defined in EventDrivenStateMachine
.
### TakeEvents | ||
|
||
Takes the recorded history of events from this aggregate. | ||
Takes the recorded history of events from this aggregate. This method is typically called by a repository when saving the aggregate to extract the new events that need to be persisted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TakeEvents
is also commonly used in unit tests of aggregates to validate that the correct events are raised.
|
||
### Initialize | ||
|
||
A common pattern in Reactive Domain is to use an `Initialize` method instead of raising events directly in the constructor. This allows for more explicit validation and better control over the initialization process. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really true. Raising events directly from a ctor is not uncommon. It's also pretty common for the parameterized ctors to be private and to use public factory methods for creating aggregates with different initial sets of raised events.
2. Use the `RaiseEvent` method to record and apply events | ||
3. Define public methods that represent domain operations | ||
1. **Create a Class**: Create a new class that inherits from `AggregateRoot` | ||
2. **Define State**: Define private fields to hold the aggregate's state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An important point here is that the state of the aggregate should be as minimal as possible. The only state an aggregate should have is whatever is needed for it to make future business decisions.
|
||
3. **Validate in Command Methods**: Validate all business rules in command methods before raising events. Once an event is raised, it's considered a fact that has occurred. | ||
|
||
4. **Use MessageBuilder for Events**: Always use `MessageBuilder.From(source, () => new Event(...))` to create events with proper correlation tracking. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should say the exact opposite.
|
||
2. **Direct State Modification**: Modifying aggregate state directly instead of through events breaks the event sourcing pattern. | ||
|
||
3. **Losing Correlation**: Not using `MessageBuilder` when creating events results in lost correlation tracking. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. Use the ICorrelatedMessage
in the base ctor for this.
Comprehensive Documentation for Reactive Domain
Overview
This PR adds complete, well-structured documentation for the Reactive Domain library. The documentation covers all aspects of the library from core concepts to advanced usage patterns, making it easier for developers to understand and use Reactive Domain effectively.
Key Changes
The documentation now includes:
Benefits
Future Improvements
Testing
Documentation changes only
Documentation has been reviewed for accuracy, completeness, and clarity.