Request Pipeline Implementation in Go
This challenge asks you to implement a request pipeline in Go, a common pattern for processing data sequentially through a series of functions. Request pipelines are useful for tasks like data validation, transformation, and enrichment, where each stage performs a specific operation on the incoming data. Successfully completing this challenge demonstrates your understanding of functional programming principles and Go's concurrency capabilities.
Problem Description
You are tasked with creating a generic request pipeline in Go. The pipeline should accept a request of any type and pass it through a series of processing functions (stages). Each stage should perform a specific operation on the request and return the modified request to the next stage. The final stage should return the processed request.
Key Requirements:
- Generic Request Type: The pipeline should be able to handle requests of any type. Use Go's
interface{}to achieve this. - Modular Stages: Stages should be defined as functions that accept and return the generic request type.
- Sequential Processing: Requests should be processed sequentially through the defined stages.
- Error Handling: Each stage should be able to return an error. If any stage returns an error, the pipeline should immediately stop processing and return the error.
- Pipeline Definition: The pipeline should be configurable, allowing users to define the order of stages.
Expected Behavior:
The Pipeline struct should have a Process method that takes a request (of type interface{}) and a slice of stage functions. The Process method should iterate through the stages, applying each stage to the request. If a stage returns an error, the Process method should return the error immediately. If all stages complete successfully, the Process method should return the final processed request and a nil error.
Edge Cases to Consider:
- Empty Pipeline: What should happen if the pipeline is empty (no stages defined)? Should it return the original request and a
nilerror, or an error? - Nil Request: What should happen if the input request is
nil? Should it return an error, or pass it through? - Error Handling in Stages: Ensure that errors returned by stages are properly propagated and handled.
Examples
Example 1:
Input: request := "Initial Request"
stages := []func(interface{}) (interface{}, error){
func(req interface{}) (interface{}, error) {
reqStr := req.(string)
return reqStr + " - Stage 1", nil
},
func(req interface{}) (interface{}, error) {
reqStr := req.(string)
return reqStr + " - Stage 2", nil
},
}
Output: "Initial Request - Stage 1 - Stage 2"
Explanation: The request is passed through two stages, each appending a string.
Example 2:
Input: request := 123
stages := []func(interface{}) (interface{}, error){
func(req interface{}) (interface{}, error) {
reqInt := req.(int)
return reqInt * 2, nil
},
func(req interface{}) (interface{}, error) {
reqInt := req.(int)
return reqInt + 10, nil
},
}
Output: 244
Explanation: The request (an integer) is passed through two stages, doubling it and then adding 10.
Example 3: (Error Handling)
Input: request := "Some Request"
stages := []func(interface{}) (interface{}, error){
func(req interface{}) (interface{}, error) {
reqStr := req.(string)
if reqStr == "Error Request" {
return nil, fmt.Errorf("Error occurred in Stage 1")
}
return reqStr + " - Stage 1", nil
},
func(req interface{}) (interface{}, error) {
reqStr := req.(string)
return reqStr + " - Stage 2", nil
},
}
Output: Error occurred in Stage 1
Explanation: Stage 1 detects the "Error Request" and returns an error, causing the pipeline to stop.
Constraints
- The pipeline should be able to handle requests of any type.
- Each stage function should accept an
interface{}and return aninterface{}, error. - The pipeline should be thread-safe (although concurrency is not explicitly required for this challenge, consider it for robustness).
- The solution should be well-documented and easy to understand.
- The solution should be efficient and avoid unnecessary allocations.
Notes
- Consider using Go's
interface{}to achieve generic request handling. - Think about how to define the stages in a flexible and reusable way.
- Pay close attention to error handling and ensure that errors are propagated correctly.
- While not required, consider how you might extend this pipeline to support concurrency (e.g., processing stages in parallel). This is a good exercise for thinking about future extensibility.
- The
fmtpackage is available for error formatting. - Focus on creating a clean and well-structured solution that adheres to Go's best practices.