Full Pipeline Integration
Overview
Section titled “Overview”The ultimate goal of this tutorial was for Pipelines to operate in a type-safe manner without reflection. This section connects all 7 built-in Pipelines and the Custom Pipeline slot (8 slots total) to simulate the complete request processing flow. While actual Mediator Pipelines are automatically registered via DI (dependency injection), here we manually invoke them for learning purposes to clearly understand each Pipeline’s role and execution order.
Pipeline execution order (Command success):
1. Metrics Pipeline ─ Increment request count2. Tracing Pipeline ─ Start Activity3. Logging Pipeline ─ Log request4. Validation Pipeline ─ Validate input5. Exception Pipeline ─ try { ... } catch6. Transaction Pipeline ─ BEGIN (Command only)7. Custom Pipeline ─ User-defined logic (optional)8. Handler ─ Execute business logic7. Custom Pipeline ─ Post-processing (optional)6. Transaction Pipeline ─ COMMIT/ROLLBACK5. Exception Pipeline ─ Catch on exception3. Logging Pipeline ─ Log result2. Tracing Pipeline ─ End Activity1. Metrics Pipeline ─ Increment response countLearning Objectives
Section titled “Learning Objectives”After completing this section, you will be able to:
- Explain the execution order and nested structure of the 8 Pipeline slots (7 built-in + Custom)
- Understand Pipeline branching based on Command vs Query
- Explain what capabilities each Pipeline’s constraint provides
- Explain how the Pipeline flow short-circuits on failure
Key Concepts
Section titled “Key Concepts”1. Pipeline Execution Order
Section titled “1. Pipeline Execution Order”Mediator Pipelines nest like Matryoshka (Russian nesting dolls). The outermost Pipeline executes first, processes inward, then returns outward for post-processing.
Metrics → Tracing → Logging → Validation → Exception → Transaction → Custom → Handler ↓Metrics ← Tracing ← Logging ← Validation ← Exception ← Transaction ← Custom ← Result2. Pipeline Constraint Summary
Section titled “2. Pipeline Constraint Summary”The following provides an at-a-glance summary of the constraints studied individually in Part 4.
| Pipeline | Constraint | Request Constraint | Required Capability |
|---|---|---|---|
| Metrics | IFinResponse, IFinResponseFactory<TResponse> | IMessage | Read + Create |
| Tracing | IFinResponse, IFinResponseFactory<TResponse> | IMessage | Read + Create |
| Logging | IFinResponse, IFinResponseFactory<TResponse> | IMessage | Read + Create |
| Validation | IFinResponseFactory<TResponse> | IMessage | CreateFail |
| Caching | IFinResponse, IFinResponseFactory<TResponse> | IQuery<TResponse> | Read + Create |
| Exception | IFinResponseFactory<TResponse> | IMessage | CreateFail |
| Transaction | IFinResponse, IFinResponseFactory<TResponse> | ICommand<TResponse> | Read + Create |
| Custom | (User-defined) | IMessage | Varies |
3. Command vs Query Branching
Section titled “3. Command vs Query Branching”In actual Functorium, where TRequest : ICommand<TResponse> / where TRequest : IQuery<TResponse> constraints ensure Transaction applies only to Commands and Caching only to Queries at compile time. In this educational code, branching is simulated with an isCommand boolean for learning purposes:
// Educational code: branching simulated with isCommand boolean// Actual Functorium: compile-time filtering via where TRequest : ICommand<TResponse> constraintif (isCommand) ExecutionLog.Add("Transaction: BEGIN");
// After Handler executionif (isCommand){ if (response.IsSucc) ExecutionLog.Add("Transaction: COMMIT"); else ExecutionLog.Add("Transaction: ROLLBACK");}4. Error Propagation Pattern
Section titled “4. Error Propagation Pattern”Each Pipeline returns immediately on failure. If Validation fails, the Handler is never reached.
On Validation failure: Metrics → Tracing → Logging → Validation → FAIL → immediate return ↓ Metrics ← Tracing ← Logging ← failure response5. Flow Tracking via ExecutionLog
Section titled “5. Flow Tracking via ExecutionLog”The PipelineOrchestrator records each step’s execution in the ExecutionLog. This enables tests to verify execution order and conditional branching.
sut.ExecutionLog.ShouldContain("Validation: PASS");sut.ExecutionLog.ShouldContain("Transaction: COMMIT");Project Structure
Section titled “Project Structure”03-Full-Pipeline-Integration/├── FullPipelineIntegration/│ ├── FullPipelineIntegration.csproj│ ├── PipelineOrchestrator.cs│ └── Program.cs├── FullPipelineIntegration.Tests.Unit/│ ├── FullPipelineIntegration.Tests.Unit.csproj│ ├── xunit.runner.json│ └── PipelineOrchestratorTests.cs└── README.mdHow to Run
Section titled “How to Run”# Run the programdotnet run --project FullPipelineIntegration
# Run testsdotnet test --project FullPipelineIntegration.Tests.UnitQ1: Does changing the Pipeline execution order affect behavior?
Section titled “Q1: Does changing the Pipeline execution order affect behavior?”A: Yes. Execution order is important. Metrics, Tracing, and Logging Pipelines must be outermost to ensure observability for all requests. Validation must be before Exception/Transaction so that invalid requests don’t start unnecessary transactions. The Exception Pipeline wraps Transaction and Handler to capture business logic exceptions, and Transaction is positioned closest to the Handler to minimize the commit/rollback scope.
Q2: All default Pipelines operate through FinResponse<T>, so why use different constraints per Pipeline?
Section titled “Q2: All default Pipelines operate through FinResponse<T>, so why use different constraints per Pipeline?”A: Following the Interface Segregation Principle (ISP), each Pipeline constrains only the minimum capabilities it needs. Validation constrains only IFinResponseFactory, while Logging constrains IFinResponse + IFinResponseFactory. Since FinResponse<T> implements all interfaces, it can pass through all Pipelines, but the code’s intent is clearly expressed through the constraints.
Q3: What happens to the Transaction Pipeline on Validation failure?
Section titled “Q3: What happens to the Transaction Pipeline on Validation failure?”A: When the Validation Pipeline fails, it returns a failure response immediately without calling next() (short-circuit). The subsequent Transaction Pipeline and Handler are not executed, preventing unnecessary transaction starts. The response passes through outer Pipelines (Logging, Tracing, etc.) where results are recorded.
Q4: In production, how are Pipelines registered instead of this manual orchestration?
Section titled “Q4: In production, how are Pipelines registered instead of this manual orchestration?”A: DI (dependency injection) registration from Mediator frameworks (Mediator, MediatR, etc.) is used. When services.AddMediator() is called, Pipelines are connected to the chain in registration order. The manual invocation in this section is for learning purposes to clearly understand each Pipeline’s role and execution order.
Q5: Why is the Custom Pipeline slot positioned right before the Handler?
Section titled “Q5: Why is the Custom Pipeline slot positioned right before the Handler?”A: Custom Pipelines handle domain-specific concerns. After observability (Metrics, Tracing, Logging) and infrastructure concerns (Validation, Exception, Transaction) are processed, performing domain-specific pre/post-processing at the position closest to the business logic is natural. Examples include custom metric collection or tracing attribute additions specific to certain Usecases.
From interface hierarchy design to Pipeline constraints, practical Usecase application, and full integration — the journey of how FinResponse<T> enables type-safe Pipelines without reflection is complete.
The appendices provide a complete reference of the IFinResponse interface hierarchy, Pipeline constraint matrix, and detailed implementations of each interface at a glance.
→ Appendix A: IFinResponse Interface Hierarchy Complete Reference