如何在采用DDD、六边形架构与端口适配器模式的后端系统中设计多阶段任务?
Great question—this is a common evolution as systems grow beyond simple CRUD and start handling complex, sequential business workflows. Since you’re already invested in DDD, Hexagonal, and CQRS, there are a few patterns that fit naturally with your existing architecture to model these multi-stage tasks effectively. Let’s break down the most practical approaches:
1. Sagas (Process Managers)
Sagas are perfect for coordinating cross-aggregate workflows where each step triggers the next, often across bounded contexts. Think of a Saga as an "orchestrator" that listens to domain events from your existing aggregates, then issues commands to kick off the next stage of the process.
Example for 2FA Registration:
- When a user submits their personal info, your
Useraggregate emits aPersonalInfoSubmitteddomain event. - A
UserRegistrationSagasubscribes to this event, then sends aSendVerificationSmsCommandto your SMS adapter (via a port in the domain layer). - Once the SMS is sent, the saga waits for a
VerificationCodeSubmittedevent (triggered when the user enters their code). - It validates the code (either via a domain service or directly in the saga), then sends a
GenerateAuthTokenCommandto complete the flow.
Key Benefits:
- Keeps your existing aggregates focused on their core responsibilities (the
Useraggregate doesn’t need to know about SMS or token generation). - Fits seamlessly with your CQRS setup—sagas consume events and emit commands, which your existing handlers can process.
- Supports compensating actions (e.g., if the SMS fails, the saga can trigger a retry or notify the user to resubmit their info).
2. Workflow as a Domain Aggregate
If the multi-stage process itself is a core domain concept (like a "DocumentVerificationWorkflow" or "OnboardingProcess"), model it as its own aggregate. This aggregate will track the current state of the workflow, enforce valid state transitions, and emit events as each step completes.
Example for Document Approval Flow:
- Create a
DocumentVerificationWorkflowaggregate with states likeDocumentsSubmitted,AwaitingAdminReview,ReviewApproved,PaymentPending,Completed. - When a user submits their documents, a
InitiateVerificationCommandcreates the aggregate and transitions it toDocumentsSubmitted, emitting aDocumentsReadyForReviewevent. - An admin’s
ApproveDocumentsCommandtransitions the state toReviewApprovedand emits aReviewCompletedevent, which triggers a notification to the user. - Finally, a
CompletePaymentCommandmoves the workflow toCompleted.
Integrating with Hexagonal Architecture:
- The workflow aggregate will define ports for external dependencies (e.g.,
NotificationPort,PaymentProcessingPort) that your infrastructure adapters implement. - This keeps the domain layer pure—no direct dependencies on external services, just abstractions.
3. State Machines for Transition Logic
To enforce valid state transitions (e.g., you can’t go from AwaitingReview to Completed without ReviewApproved), use a finite state machine (FSM) within your saga or workflow aggregate. This makes the allowed transitions explicit and prevents invalid state changes.
You can implement this with simple enum-based logic in your domain code:
// Example state transition logic in a workflow aggregate private readonly Dictionary<WorkflowState, List<WorkflowState>> _allowedTransitions = new() { { WorkflowState.DocumentsSubmitted, new List<WorkflowState> { WorkflowState.AwaitingAdminReview } }, { WorkflowState.AwaitingAdminReview, new List<WorkflowState> { WorkflowState.ReviewApproved, WorkflowState.ReviewRejected } }, // ... other transitions }; public void ApproveReview() { if (!_allowedTransitions[CurrentState].Contains(WorkflowState.ReviewApproved)) throw new InvalidOperationException("Cannot approve review from current state"); CurrentState = WorkflowState.ReviewApproved; AddDomainEvent(new ReviewApprovedEvent(Id)); }
Critical Considerations
- Idempotency: Ensure that events and commands can be processed multiple times without side effects (e.g., using unique IDs for each workflow instance to avoid duplicate SMS sends).
- Persistence: Saga or workflow state needs to be persisted between steps—store the current state and any necessary context (like verification code, user ID) in your database.
- Error Handling: Define retry policies for transient failures (e.g., SMS service downtime) and compensating actions for permanent failures (e.g., if admin rejects documents, notify the user to resubmit).
Recommendation
Start with Sagas for workflows that coordinate existing aggregates (like your 2FA flow), as they require the least change to your current setup. For workflows that are core to your domain (like document verification), model them as dedicated aggregates to keep the domain logic explicit and encapsulated. Both approaches fit perfectly with your Hexagonal and CQRS architecture, keeping your domain layer focused and your infrastructure decoupled.
内容的提问来源于stack exchange,提问作者Farzin Nasiri

