Skip to content

Conversation

leopoldodonnell
Copy link

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

  • Complete Documentation Structure: Created a comprehensive documentation structure with a clear table of contents and navigation
  • Core Documentation: Added detailed documentation for:
  • Core concepts of event sourcing and CQRS
  • Usage patterns and best practices
  • Component documentation with logical navigation
  • API reference for key interfaces and classes
  • Architecture guide with diagrams
  • Migration, deployment, and security guides
  • Enhanced Navigation: Implemented logical progression through documentation with next/previous links
  • Component Navigation: Added specialized navigation for components showing their relationships
  • README Updates: Enhanced the main README with documentation links and status badges
  • Learning Path: Added a recommended learning path for new users
  • Documentation Sections

The documentation now includes:

  • Core Concepts: Fundamental principles of event sourcing and CQRS
  • Usage Patterns: Common patterns and best practices
  • Component Documentation: Detailed documentation for each component
  • API Reference: Reference for key interfaces and classes
  • Architecture Guide: High-level architecture and design principles
  • Migration Guide: Guidance for version upgrades
  • Troubleshooting Guide: Solutions for common issues
  • Glossary: Definitions of key terms
  • FAQ: Answers to common questions
  • Deployment Guide: Best practices for deployment
  • Performance Guide: Optimization techniques
  • Security Guide: Security best practices
  • Integration Guide: Integration strategies
  • Video Tutorial Script: Outline for video tutorials
  • Workshop Materials: Materials for conducting workshops

Benefits

  • Improved Developer Experience: Makes it easier for developers to learn and use Reactive Domain
  • Reduced Learning Curve: Provides clear guidance for developers new to event sourcing
  • Better Maintenance: Makes the codebase more maintainable with clear documentation
  • Increased Adoption: Makes the library more accessible to new users
  • Consistent Understanding: Ensures consistent understanding of concepts and patterns

Future Improvements

  • Set up GitHub Pages for hosting the documentation as a website
  • Add a GitHub Action workflow to automatically publish documentation updates
  • Expand code examples with more real-world scenarios
  • Add more diagrams to illustrate complex concepts
  • Screenshots

Testing

Documentation changes only
Documentation has been reviewed for accuracy, completeness, and clarity.

@leopoldodonnell leopoldodonnell requested a review from a team as a code owner April 16, 2025 13:25
Copy link
Contributor

@joshkempner joshkempner left a 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
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong version of .NET

Leopold O'Donnell and others added 9 commits May 3, 2025 12:00
…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
```csharp
// Create a command with correlation information from an existing message
ICorrelatedMessage existingCommand = // ... existing command
ICorrelatedMessage newCommand = MessageBuilder.From(existingCommand, () => new DepositFunds(accountId, amount));
Copy link
Contributor

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.

Comment on lines 52 to 63
### 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)));
}
Copy link
Contributor

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.

Comment on lines 61 to 67
## 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>
{
Copy link
Contributor

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
Copy link
Contributor

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.

Comment on lines 65 to 68
public CreateAccount(Guid accountId, string accountNumber, string customerName,
Guid correlationId, Guid causationId)
: base(correlationId, causationId)
{
Copy link
Contributor

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.

Comment on lines 89 to 92
var depositCommand = MessageBuilder.From(createCommand, () => new DepositFunds(
((CreateAccount)createCommand).AccountId,
100.00m
));
Copy link
Contributor

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>
Copy link
Contributor

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.

{
public Account(Guid id, ICorrelatedMessage source) : base(id)
{
Apply(MessageBuilder.From(source, () => new AccountCreated(id, source.CorrelationId, source.MsgId)));
Copy link
Contributor

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.

Comment on lines +65 to +67
public AccountCreated(Guid accountId, string accountNumber, string customerName,
Guid correlationId, Guid causationId)
: base(correlationId, causationId)
Copy link
Contributor

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

Comment on lines 83 to 86
var createdEvent = MessageBuilder.From(command, () => new AccountCreated(
Guid.NewGuid(),
"ACC-123",
"John Doe"
Copy link
Contributor

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.

Events are typically handled by event handlers:

```csharp
public class AccountCreatedHandler : IEventHandler<AccountCreated>
Copy link
Contributor

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>.


public Account(Guid id, ICorrelatedMessage source) : base(id)
{
Apply(MessageBuilder.From(source, () => new AccountCreated(id, "ACC-" + id.ToString().Substring(0, 8), "New Customer")));
Copy link
Contributor

@joshkempner joshkempner May 5, 2025

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.

Leopold O'Donnell added 13 commits May 11, 2025 16:27
…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
@leopoldodonnell
Copy link
Author

@joshkempner - Can you review the examples again. I've made a lot of changes.

Leopold O'Donnell added 13 commits May 12, 2025 05:26
- 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
Copy link
Contributor

@joshkempner joshkempner left a 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.

[![Build Status](https://travis-ci.org/linedata/reactive-domain.svg?branch=master)](https://travis-ci.org/linedata/reactive-domain)
[![Build Status](https://travis-ci.org/ReactiveDomain/reactive-domain.svg?branch=master)](https://travis-ci.org/ReactiveDomain/reactive-domain)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](code_of_conduct.md)
[![Documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://reactivedomain.github.io/reactive-domain/)
Copy link
Contributor

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)
Copy link
Contributor

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 the Raise 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 private RegisterHandlers() method that is called from both ctors.

Most of the examples in this doc include calls to base ctors that don't exist.

Comment on lines +66 to +67
RaiseEvent(MessageBuilder.From(source, () => new AccountCreated(
Id, accountNumber, customerName, DateTime.UtcNow)));
Copy link
Contributor

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.

Comment on lines 121 to 123
### 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.
Copy link
Contributor

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
Copy link
Contributor

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.
Copy link
Contributor

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.
Copy link
Contributor

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
Copy link
Contributor

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.
Copy link
Contributor

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.
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants