Author: Daniel Gerlag, Principal Software Engineer
Event-driven architectures tout scalability, loose coupling, and eventual consistency. The architectural patterns are sound, the theory is compelling, and the blog posts make it look straightforward.
Then you implement it.
Suddenly you're maintaining separate event stores, implementing transactional outboxes, debugging projection rebuilds, versioning events across a dozen micro-services, and writing mountains of boilerplate to handle what should be simple queries.
Your domain events that were supposed to capture rich business meaning have devolved into glorified database change notifications. Downstream services diff field values to extract intent from "OrderUpdated" events because developers just don't get what constitutes a proper domain event.
The complexity tax is real, don't get me wrong, it's very elegant but for many systems it's unjustified.
Drasi offers an alternative: change-driven architecture that delivers reactive, real-time capabilities across multiple data sources without requiring you to rewrite your application or over complicate your architecture.
What do we mean by “Event-driven” architecture
As Martin Fowler notes, event-driven architecture isn't a single pattern, it's at least four distinct patterns that are often confused, each with its own benefits and traps.
Event Notification is the simplest form. Here, events act as signals that something has happened, but carry minimal data, often just an identifier. The recipient must query the source system for more details if needed. For example, a service emits an OrderPlaced event with just the order ID. Downstream consumers must query the order service to retrieve full order details.
Event Carried State Transfer broadcasts full state changes through events. When an order ships, you publish an OrderShipped event containing all the order details. Downstream services maintain their own materialized views or projections by consuming these events.
Event Sourcing goes further, events become your source of truth. Instead of storing current state, you store the sequence of events that led to that state. Your order isn't a row in a database; it's the sum of OrderPlaced, ItemAdded, PaymentProcessed, and OrderShipped events.
CQRS (Command Query Responsibility Segregation) separates write operations (commands) from read operations (queries). While not inherently event-driven, CQRS is often paired with event sourcing or event-carried state transfer to optimize for scalability and maintainability. Originally derived from Bertrand Meyer's Command-Query Separation principle and popularized by Greg Young, CQRS addresses a specific architectural challenge: the tension between optimizing for writes versus optimizing for reads.
The pattern promises several benefits:
Optimized data models: Your write model can focus on transactional consistency while read models optimize for query performance
Scalability: Read and write sides can scale independently
Temporal queries: With event sourcing, you get time travel for free—reconstruct state at any point in history
Audit trail: Every change is captured as an immutable event
While CQRS isn't inherently tied to Domain-Driven Design (DDD), the pattern complements DDD well. In DDD contexts, CQRS enables different bounded contexts to maintain their own read models tailored to their specific ubiquitous language, while the write model protects domain invariants. This is why you'll often see them discussed together, though each can be applied independently.
The core motivation for these patterns is often to invert the dependency between systems, so that your downstream services do not need to know about your upstream services.
The Developer's Struggle: When Domain Events Become Database Events
Chris Kiehl puts it bluntly in his article "Don't Let the Internet Dupe You, Event Sourcing is Hard": "The sheer volume of plumbing code involved is staggering—instead of a friendly N-tier setup, you now have classes for commands, command handlers, command validators, events, aggregates, and then projections, model classes, access classes, custom materialization code, and so on."
But the real tragedy isn't the boilerplate, it's what happens to those carefully crafted domain events. As developers are disconnected from the real-world business, they struggle to understand the nuances of domain events, a dangerous pattern emerges. Instead of modeling meaningful business processes, teams default to what they know: CRUD.
Your event stream starts looking like this:
OrderCreated
OrderUpdated
OrderUpdated (again)
OrderUpdated (wait, what changed?)
OrderDeleted
As one developer noted on LinkedIn, these "CRUD events" are really just "leaky events that lack clarity and should not be used to replicate databases as this leaks implementation details and couples services to a shared data model."
Dennis Doomen, reflecting on real-world production issues, observes: "It's only once you have a living, breathing machine, users which depend on you, consumers which you can't break, and all the other real-world complexities that plague software projects that the hard problems in event sourcing will rear their heads."
The result? Your elegant event-driven architecture devolves into an expensive, brittle form of self-maintained Change Data Capture (CDC). You're not modeling business processes; you're just broadcasting database mutations with extra steps.
The Anti-Corruption Layer: Your Defense Against the Outside World
In DDD, an Anti-Corruption Layer (ACL) protects your bounded context from external models that would corrupt your domain. Think of it as a translator that speaks both languages, the messy external model and your clean internal model.
The ACL ensures that changes to the external system don't ripple through your domain. If the legacy system changes its schema, you update the translator, not your entire domain model.
When Event Taxonomies Become Your ACL (And Why They Fail)
In most event-driven architectures, your event taxonomy is supposed to serve as the shared contract between services. Each service publishes events using its own ubiquitous language, and consumers translate these into their own models, this translation is the ACL.
The theory looks beautiful:
But reality? Most teams end up with this:
Instead of OrderPaid events that carry business meaning, we get OrderUpdated events that force every consumer to reconstruct intent by diffing fields. When you change your database schema, say splitting the orders table or switching from SQL to NoSQL, every downstream service breaks because they're all coupled to your internal data model.
You haven't built an anti-corruption layer. You've built a corruption pipeline that efficiently distributes your internal implementation details across the entire system, forcing you to deploy all services in lock step and eroding the decoupling benefits you were supposed to get.
Enter Drasi: Continuous Queries
This is where Drasi changes the game. Instead of publishing events and hoping downstream services can make sense of them, Drasi tails the changelog of the data source itself and derives meaning through continuous queries.
A continuous query in Drasi isn't just a query that runs repeatedly, it's a living, breathing projection that reacts to changes in real-time. Here's the key insight: instead of imperative code that processes events ("when this happens, do that"), you write declarative queries that describe the state you care about ("I want to know about orders that are ready and have drivers waiting").
Let's break down what makes this powerful:
Declarative vs. Imperative
Traditional event processing:
Drasi continuous query:
Semantic Mapping from Low-Level Changes
Drasi excels at transforming database-level changes into business-meaningful events. You're not reacting to "row updated in orders table", you're reacting to "order ready for curbside pickup."
This enables the same core benefits of dependency inversion we get from event-driven architectures but at a fraction of the effort.
Advanced Temporal Features
Remember those developers struggling with "OrderUpdated" events, trying to figure out if something just happened or has been true for a while? Drasi handles this elegantly:
This query only fires when a driver has been waiting for more than 10 minutes, no timestamp tracking, no state machines, no complex event correlation logic, imagine trying to manually implement this in a downstream event consumer. 😱
Cross-Source Aggregation Without Code
With Drasi, you can have live projections across PostgreSQL, MySQL, SQL Server, and Cosmos DB as if they were a single graph:
No custom aggregation service. No event stitching logic. No custom downstream datastore to track the sum or keep a materialized projection. Just a query.
Continuous Queries as Your Shared Contract
Drasi's continuous queries, combined with pre-processing middleware, can form the shared contract that your anti-corruption layer can depend on.
The continuous query becomes your contract. Downstream systems don't know or care whether orders come from PostgreSQL, MongoDB, or a CSV file. They don't know if you normalized your database, denormalized it, or moved to event sourcing. They just consume the query results. Clean, semantic, and stable.
Reactions as your Declarative Consumers
Drasi does not simply output a stream of raw change diffs, instead it has a library of interchangeable Reactions, that can act on the output of continuous queries. These are declared using YAML and can do anything from host a web-socket endpoint that provides a live projection to your UI, to calling an Http endpoint or publishing a message on a queue.
Example: The Curbside Pickup System
Let's see how this works in Drasi's curbside pickup tutorial. This example has two independent databases and serves as a great illustration of a real-time projection built from multiple upstream services.
The Business Problem
A retail system needs to:
- Match ready orders with drivers who've arrived at pickup zones
- Alert staff when drivers wait more than 10 minutes without their order being ready
- Coordinate data from two different systems (retail ops in PostgreSQL, physical ops in MySQL)
Traditional Event-Driven Approach
In this architecture, you'd need something like:
That's just the happy path. We haven't handled:
- Event ordering issues
- Partial failures
- Cache invalidation
- Service restarts and replay
- Duplicate events
- Transactional outboxing
The Drasi Approach
With Drasi, the entire aggregation service above becomes two queries:
Delivery Dashboard Query:
Wait Detection Query:
That's it. No event handlers. No caching. No timers. No state management. Drasi handles:
- Change detection across both databases
- Correlation between orders and vehicles
- Temporal logic for wait detection
- Pushing updates to dashboards via SignalR
The queries define your business logic declaratively. When data changes in either database, Drasi automatically re-evaluates the queries and triggers reactions for any changes in the result set.
Drasi: The Non-Invasive Alternative to Legacy System Rewrites
Here's perhaps the most compelling argument for Drasi: it doesn't require you to rewrite anything.
Traditional event sourcing means:
- Redesigning your application around events
- Rewriting your persistence layer
- Implementing transactional outboxes
- Managing snapshots and replays
- Training your team on new patterns, steep learning curve
- Migrating existing data to event streams
- Building projection infrastructure
- Updating all consumers to handle events
As one developer noted about their event sourcing journey: "Event Sourcing is a beautiful solution for high-performance or complex business systems, but you need to be aware that this also introduces challenges most people don't tell you about."
Drasi's approach:
- Keep your existing databases
- Keep your existing services
- Keep your existing deployment model
- Add continuous queries where you need reactive behavior
- Get the benefits of dependency inversion
- Gradually migrate complexity from code to queries
You can start with a single query on a single table and expand from there. No big bang. No feature freeze. No three-month architecture sprint or large multi-year investments, full of risk.
Migration Example: From Polling to Reactive
Let's say you have a legacy order system where a scheduled job polls for ready orders every 30 seconds:
With Drasi, you:
- Point Drasi at your existing database
- Write the continuous query
- Update your dashboard to receive pushes instead of polls
- Turn off the polling job
Your database hasn't changed. Your order service hasn't changed. You've just added a reactive layer on top that eliminates polling overhead and reduces notification latency from 30 seconds to milliseconds.
The intellectually satisfying complexity of event sourcing often obscures a simple truth: most systems don't need it. They need to know when interesting things change in their data and react accordingly. They need to combine data from multiple sources without writing bespoke aggregation services. They need to transform low-level changes into business-meaningful events.
Drasi delivers these capabilities without the ceremony.
Where Do We Go from Here?
If you're building a new system and your team has deep event sourcing experience embrace the pattern. Event sourcing shines for certain domains.
But if you're like many teams, trying to add reactive capabilities to existing systems, struggling with data synchronization across services, or finding that your "events" are just CRUD operations in disguise, consider the change-driven approach.
Start small:
- Identify one painful polling loop or batch job
- Set up Drasi to monitor those same data sources
- Write a continuous query that captures the business condition
- Replace the polling with push-based reactions
- Measure the reduction in latency, overhead, and code complexity
The best architecture isn't the most sophisticated one, it's the one your team can understand, maintain, and evolve. Sometimes that means acknowledging that we've been mid-curving it with overly complex event-driven architectures.
Drasi and change-driven architecture offer the power of reactive systems without the complexity tax. Your data changes. Your queries notice. Your systems react.
It makes it a non-event.
Want to explore Drasi further? Check out the official documentation and try the curbside pickup tutorial to see change-driven architecture in action.