Event sourcing and CQRS are architectural patterns that solve specific problems but also introduce significant complexity. I think their use should be based on clear problem fit.

Event sourcing stores the history of state changes, or events, rather than the current state. This means instead of a database row that says 'order status: shipped', you have a sequence of events like OrderPlaced, PaymentConfirmed, OrderPacked, and OrderShipped. The current state is derived by replaying these events. This approach solves a few problems: it provides a complete audit trail without additional code, allows rebuilding projections from the event history, and enables temporal queries, such as what the state was at a specific time.

In one of my previous projects, we had an order management system that required a full audit trail for financial and compliance reasons. We used event sourcing to store over 100 million events per day. The event store was built using EventStoreDB, and we used Kafka as a message broker to handle event publishing. This setup allowed us to achieve high availability and scalability while maintaining a complete history of all state changes.

CQRS, on the other hand, separates the write model, which consists of commands that change state, from the read model, which handles queries that return data. The write model is optimized for consistency and business rule enforcement, while the read model is optimized for query performance and can be denormalized for specific queries the application performs. This solves the problem of conflicting requirements between the normalized write model and the denormalized read model in applications with complex reporting needs.

When implementing CQRS, a key trade-off is between consistency and performance. For example, in a system with high-volume reporting needs, you might choose to sacrifice some consistency in the read model to achieve better query performance. This could mean allowing for some delay between the write model and the read model being updated. In one system I worked on, we used a 5-minute delay to allow for batch processing of events, which significantly improved query performance without impacting business operations.

However, event sourcing and CQRS come with an operational complexity cost. They require an event store, projection engines to maintain read models from events, and handling eventual consistency between the write and read models. There's also the cognitive load of thinking in terms of events rather than current state. For applications with simple CRUD patterns and no requirements for audit trails or temporal queries, this complexity doesn't offer any benefits.

In my experience, these patterns are worth the complexity in systems where the benefits directly address specific requirements. They aren't a one-size-fits-all solution. When evaluating event sourcing and CQRS, I consider factors such as the need for audit trails, temporal queries, and complex reporting. If these features are essential, then the added complexity might be justified.

When implementing event sourcing in .NET, MassTransit and EventStoreDB are a practical foundation. MassTransit is a .NET message bus library that simplifies producer-consumer patterns and saga orchestration. EventStoreDB is a purpose-built event store with projection capabilities and a .NET client library. Together, they make event sourcing in .NET manageable.

For teams already using Dapr, Microsoft's Dapr pub/sub building block is an alternative to consider. The choice of tools often depends on the existing tech stack and the team's familiarity with the technologies.