Hone logo
Hone
Problems

Implementing a Generic Pipeline Pattern in Go

This challenge focuses on designing and implementing a flexible and reusable pipeline pattern in Go. The pipeline pattern is a powerful way to chain together a series of operations (stages) where the output of one stage becomes the input of the next. This is particularly useful for processing data sequentially, handling concurrent operations, and building complex data flows.

Problem Description

Your task is to create a generic pipeline system in Go that allows users to define a sequence of processing stages. Each stage will take an input and produce an output, and these stages will be connected to form a pipeline. The pipeline should be able to handle different data types as inputs and outputs, and ideally support concurrent execution of stages where appropriate.

Key Requirements:

  1. Generic Stages: Define a Stage interface that outlines the contract for any processing unit within the pipeline. This interface should define a method to execute the stage's logic.
  2. Pipeline Construction: Implement a Pipeline struct that can hold a collection of Stage instances in order.
  3. Execution: The Pipeline should have a method to execute the entire sequence of stages, passing data from one stage to the next.
  4. Error Handling: Each stage should be able to return an error. The pipeline execution should propagate and handle these errors gracefully.
  5. Genericity: The pipeline and its stages should be designed to work with arbitrary data types using Go's generics.

Expected Behavior:

When a pipeline is executed with an initial input, the input is passed to the first stage. The output of the first stage is then passed as input to the second stage, and so on. If any stage returns an error, the pipeline execution should stop, and the error should be returned. If all stages execute successfully, the final output of the last stage is returned.

Edge Cases to Consider:

  • An empty pipeline.
  • A pipeline with a single stage.
  • Stages that produce no output (e.g., they might just perform an action).
  • Stages that transform data of one type into another.

Examples

Example 1: Simple Integer Processing Pipeline

Let's imagine a pipeline that doubles an integer, then squares it, and finally adds 5.

Input: 5

Stages:

  1. DoubleStage: Takes an int, returns int * 2.
  2. SquareStage: Takes an int, returns int * int.
  3. AddFiveStage: Takes an int, returns int + 5.

Output: 55

Explanation:

  1. 5 is input to DoubleStage -> 10
  2. 10 is input to SquareStage -> 100
  3. 100 is input to AddFiveStage -> 105 (Correction: The initial example output was incorrect. The corrected output is 105)

Example 2: String Transformation Pipeline with Error

Consider a pipeline that converts a string to uppercase, then checks if it contains a specific substring, and finally trims whitespace.

Input: " HELLO WORLD "

Stages:

  1. ToUpperStage: Takes a string, returns strings.ToUpper().
  2. ContainsSubstringStage: Takes a string, checks if it contains "WORLD". If not, returns an error.
  3. TrimSpaceStage: Takes a string, returns strings.TrimSpace().

Output: "HELLO WORLD", nil (error)

Explanation:

  1. " HELLO WORLD " is input to ToUpperStage -> " HELLO WORLD "
  2. " HELLO WORLD " is input to ContainsSubstringStage. It contains "WORLD", so no error. Output is still " HELLO WORLD ".
  3. " HELLO WORLD " is input to TrimSpaceStage -> "HELLO WORLD". (Correction: The ContainsSubstringStage should return the processed string or an error. If it returns an error, the pipeline stops. If it doesn't return an error, it should pass the string along. Assuming the stage returns the processed string and an error, the above logic holds for the successful case. Let's refine the example to show an error.)

Example 2 (Revised): String Transformation Pipeline with Error

Input: " hello universe "

Stages:

  1. ToUpperStage: Takes a string, returns strings.ToUpper().
  2. ContainsSubstringStage: Takes a string, checks if it contains "WORLD". If not, returns an error.
  3. TrimSpaceStage: Takes a string, returns strings.TrimSpace().

Output: "", errors.New("substring 'WORLD' not found")

Explanation:

  1. " hello universe " is input to ToUpperStage -> " HELLO UNIVERSE "
  2. " HELLO UNIVERSE " is input to ContainsSubstringStage. It does not contain "WORLD". An error is returned, and the pipeline stops.
  3. TrimSpaceStage is not executed.

Example 3: Pipeline with Type Transformation

Input: 10 (an int)

Stages:

  1. IntToStringStage: Takes an int, returns strconv.Itoa().
  2. AddPrefixStage: Takes a string, returns "prefix-" + string.

Output: "prefix-10", nil

Explanation:

  1. 10 is input to IntToStringStage -> "10"
  2. "10" is input to AddPrefixStage -> "prefix-10"

Constraints

  • The pipeline should support at least 5 stages.
  • The implementation should use Go generics for type safety.
  • The Stage interface should be callable with a single input and return a single output and an error.
  • The pipeline execution should be sequential by default. You are not required to implement concurrent stage execution for this challenge, but you can consider how it might be extended.
  • Input and output types for stages can be different (e.g., int to string).

Notes

  • Consider how you will define the Stage interface to accommodate different input and output types. Generics will be key here.
  • Think about how to pass data between stages effectively, especially when types might change.
  • Error handling is crucial. Ensure errors from individual stages are propagated correctly.
  • You can define helper functions or structs to create common stages if needed, but the core focus is on the pipeline structure itself.
  • The problem is about designing the pipeline system, not necessarily about complex, specific processing logic within each stage. Focus on the generic plumbing.
Loading editor...
go