在.NET Core CQRS服务中,Saga/流程管理器是同步执行关联命令的最优方案吗?
Great question—let’s walk through this step by step, since your scenario is super common when dealing with legacy data constraints.
First, let’s cut to the chase: yes, a Saga/Process Manager is absolutely a strong (and often the best) solution here. But let’s break down why your current client-side Promise chain approach has limitations, and how a Saga fixes those gaps.
Problems with Your Current Client-Side Approach
Right now, your client is handling the sequencing of 3 commands via Promises, which works in a pinch, but comes with some big downsides:
- Coupled logic: The client now knows intimate details of your backend’s business process (like needing 3 specific commands to create an assessment). If you ever need to add/remove/reorder steps, you’ll have to update every client that uses this flow—scaling nightmare.
- Fragile failure handling: If the second command fails after the first succeeds, your client has no easy way to roll back the first command’s changes. This can leave your legacy database in an inconsistent state that’s hard to clean up.
- Network overhead: Each command is a separate round trip to the server, adding up latency for the end user.
- Hard to monitor: Tracking the progress of the full "create assessment" flow becomes tricky, since it’s spread across multiple client-initiated requests.
Why a Saga/Process Manager Fixes This
A Saga (or Process Manager) moves this sequencing logic to the server side, where it belongs. Here’s how it turns things around:
- Single entry point: Your client only sends one command (e.g.,
InitiateAssessmentCreationCommand) instead of three. The Saga handles all the internal steps—no client-side knowledge of the backend’s inner workings required. - Built-in consistency & compensation: If a step fails, the Saga can execute compensation commands to undo previous changes (e.g., if the third command fails, it sends a
RollbackAssessmentStep1Commandto revert the first step’s database changes). This keeps your legacy database consistent even when things go wrong. - Centralized logic: All the sequencing, error handling, and step dependencies live in one place in your backend. Updating the flow later is as simple as modifying the Saga, not every client.
- Reduced latency: Since all commands execute server-side, you eliminate multiple network hops between client and server, making the whole process faster for users.
Implementing This in .NET Core
You have a few solid options for building a Saga in your .NET Core CQRS service:
- Simple Pipeline Behavior (with MediatR): If your flow is straightforward (strictly synchronous, no long-running processes), you can create a MediatR pipeline behavior that handles the sequencing. For example, when
InitiateAssessmentCreationCommandis sent, the behavior runsCreateAssessmentStep1Command, waits for it to commit, then executesCreateAssessmentStep2Command, and so on. - Dedicated Saga Libraries: For more complex flows (or if you might need asynchronous steps later), libraries like MassTransit or NServiceBus have built-in Saga support that handles persistence of the Saga state, retries, and compensation out of the box.
- Custom Process Manager: If you want full control, you can build a simple process manager class that orchestrates the commands, tracks their status, and handles failures. This is perfect for small, well-defined flows where you don’t need all the bells and whistles of a full library.
When Might You Consider an Alternative?
The only time you might skip a Saga is if you can safely merge the three commands into a single command (e.g., CreateAssessmentCommand that handles all three database operations in one transaction). But you mentioned legacy database constraints make this impossible—so a Saga is clearly the way to go.
At the end of the day, moving this logic to a server-side Saga will make your system more maintainable, consistent, and easier to evolve as your requirements change.
内容的提问来源于stack exchange,提问作者Ben Thomson

