Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
bfarmer67 committed Aug 6, 2024
2 parents 1639e64 + cd4f7c8 commit 29cf446
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 350 deletions.
32 changes: 32 additions & 0 deletions docs/child-pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
layout: default
title: Child Pipelines
nav_order: 5
---

# Child Piplelines

The `PipelineFactory` library allows you to use pipelines together. Since pipelines are just functions, they can be used
as input to other pipelines. This allows you to create complex data processing flows by reusing and chaining together
multiple pipelines.

Here's an example of how to use pipelines together:


```csharp
var pipeline2 = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => $"{arg} again!" )
.Build();

var pipeline1 = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => $"hello {arg}" )
.PipeAsync( pipeline2 )
.Build();

var result = await pipeline1( new PipelineContext(), "you" );

Assert.AreEqual( "hello you again!", result );
```

7 changes: 0 additions & 7 deletions docs/childPipeline.md

This file was deleted.

63 changes: 6 additions & 57 deletions docs/execution.md → docs/command-pattern.md
Original file line number Diff line number Diff line change
@@ -1,61 +1,10 @@
---
layout: default
title: Execution
nav_order: 2
title: Commands
nav_order: 6
---

# Execution
| Method | Description
| ---------- | -----------
| Call | Execute a `void` step that does not transform the pipeline output.
| CallAsync | Asynchronously execute a `void` step that does not transform the pipeline output.
| Pipe | Execute a step that transforms the pipeline output.
| PipeAsync | Asynchronously execute a step that transforms the pipeline output.

## Flow Control

| Method | Description
| ---------- | -----------
| Cancel | Cancels the pipeline after the current step.
| CancelWith | Cancels the pipeline with a value, after the current step.
| Pipe | Pipes a child pipeline with optional middlewares.
| PipeIf | Conditionally pipes a child pipeline with optional middlewares.
| Call | Calls a child pipeline with optional middlewares.
| CallIf | Conditionally calls a child pipeline with optional middlewares.
| ForEach | Enumerates a collection pipeline input.
| Reduce | Transforms an enumerable pipeline input.

## Parallel Flow

| Method | Description
| ---------- | -----------
| WaitAll | Waits for concurrent pipelines to complete.

## Middleware

| Method | Description
| ---------- | -----------
| Hook | Applies middleware to each step in the pipeline.
| Wrap | Wraps the middleware around the preceeding steps.

## Building and Executing Pipelines

Pipelines are built using `PipelineFactory`. Once built, a pipeline is just an async function that takes a `PipelineContext` and
an optional input value as parameters, and returns a result.

```csharp
var command = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => $"hello {arg}" )
.Build();

var result = await command( new PipelineContext(), "pipeline" );

Assert.AreEqual( "hello pipeline", result );
```

## Command Pattern
# Command Pattern

`ICommand*` interfaces and `Command*` base classes provide a lightweight pattern for constructing injectable commands built
around pipelines and middleware.
Expand All @@ -66,7 +15,7 @@ an optional input value as parameters, and returns a result.
| ICommandFunction&lt;TOutput&gt; | CommandFunction&lt;TOutput&gt; | A command that takes no input and returns an output
| ICommandProcedure&lt;TInput&gt; | CommandProcedure&lt;TInput&gt; | A command that takes an input and returns void

#### Example 1
## Example 1
Example of a command that takes an input and produces an output.

```csharp
Expand Down Expand Up @@ -103,7 +52,7 @@ Example of a command that takes an input and produces an output.
}
```

#### Example 2
## Example 2
Example of a command that takes no input and produces an output.

```csharp
Expand Down Expand Up @@ -140,7 +89,7 @@ Example of a command that takes no input and produces an output.
}
```

#### Example 3
## Example 3
Example of a command that takes an input and produces no output.

```csharp
Expand Down
File renamed without changes.
7 changes: 4 additions & 3 deletions docs/docs.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
<Import_RootNamespace>docs</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)childPipeline.md" />
<None Include="$(MSBuildThisFileDirectory)dependencyInjection.md" />
<None Include="$(MSBuildThisFileDirectory)execution.md" />
<None Include="$(MSBuildThisFileDirectory)child-pipeline.md" />
<None Include="$(MSBuildThisFileDirectory)dependency-injection.md" />
<None Include="$(MSBuildThisFileDirectory)command-pattern.md" />
<None Include="$(MSBuildThisFileDirectory)syntax.md" />
<None Include="$(MSBuildThisFileDirectory)index.md" />
<None Include="$(MSBuildThisFileDirectory)middleware.md" />
<None Include="$(MSBuildThisFileDirectory)_config.yml" />
Expand Down
179 changes: 45 additions & 134 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: Hyperbee Pipeline
nav_order: 1
---

# Hyperbee.Pipeline
# Hyperbee Pipeline

`Hyperbee.Pipeline` allows you to construct asynchronous fluent pipelines in .NET. A pipeline, in this context, refers to a
sequence of data processing elements arranged in series, where the output of one element serves as the input for the subsequent
Expand All @@ -25,63 +25,42 @@ Some key features are:
* Early returns and cancellation
* Child pipelines

## Why Use Pipelines

```csharp
// Takes a string and returns a number
var question = PipelineFactory
.Start<string>()
.PipeIf((ctx, arg) => arg == "Adams", builder => builder
.Pipe((ctx, arg) => 42)
.Cancel()
)
.Pipe((ctx, arg) => 0)
.Build();
Pipelines provide a structured approach to managing complex processes, promoting [SOLID](https://en.wikipedia.org/wiki/SOLID)
principles, including Inversion of Control (IoC) and Separation of Concerns (SoC). They enable composability, making it easier
to build, test, and maintain your code. By extending the benefits of middleware and request-response pipelines throughout your
application, you achieve greater modularity, scalability, and flexibility. This is especially critical in domains such as
healthcare, compliance auditing, identity and roles, and high-security environments where clear boundaries and responsibilities
are essential. Hyperbee.Pipeline ensures that the advantages of pipelines and middleware are not abandoned at the controller
implementation, addressing a common gap in many frameworks. By using a functional approach, Hyperbee.Pipeline ensures that your
pipelines are not only robust and maintainable but also highly adaptable to changing requirements.

var answer1 = await question(new PipelineContext(), "Adams");
Assert.AreEqual(42, answer1);

var answer2 = await question(new PipelineContext(), "Smith");
Assert.AreEqual(0, answer2);
```
## Getting Started

## Hook
To get started with Hyperbee.Pipeline, refer to the documentation for detailed instructions and examples.

The `Hook` and `HookAsync` methods allow you to add a hook that is called for every statement in the pipeline. This hook takes the current context, the current argument, and a delegate to the next part of the pipeline. It can manipulate the argument before and after calling the next part of the pipeline.
Install via NuGet:

Here's an example of how to use `HookAsync`:

```csharp
var command = PipelineFactory
.Start<string>()
.HookAsync( async ( ctx, arg, next ) => await next( ctx, arg + "{" ) + "}" )
.Pipe( ( ctx, arg ) => arg + "1" )
.Pipe( ( ctx, arg ) => arg + "2" )
.Build();

var result = await command( new PipelineContext() );

Assert.AreEqual( "{1}{2}", result );
```bash
dotnet add package Hyperbee.Pipeline
```

## Wrap
## Building and Executing Pipelines

The `Wrap` and `WrapAsync` method allows you to wrap a part of the pipeline. This is useful when you want to apply a transformation to only a part of the pipeline.

Here’s an example of how to use `WrapAsync`:
Pipelines are built using `PipelineFactory`. Once built, a pipeline is just an async function that takes a `PipelineContext` and
an optional input value as parameters, and returns a result.

```csharp
var command = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => arg + "1" )
.Pipe( ( ctx, arg ) => arg + "2" )
.WrapAsync( async ( ctx, arg, next ) => await next( ctx, arg + "{" ) + "}" )
.Pipe( ( ctx, arg ) => arg + "3" )
.Build();
var command = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => $"hello {arg}" )
.Build();

var result = await command( new PipelineContext() );

Assert.AreEqual( "{12}3", result );
var result = await command( new PipelineContext(), "pipeline" );

Assert.AreEqual( "hello pipeline", result );
```

## Dependency Injection
Expand Down Expand Up @@ -110,110 +89,42 @@ services.AddPipeline( (factoryServices, rootProvider) =>
} );
```

## Advanced Features

The `PipelineFactory` library provides a variety of helper methods that allow you to customize the behavior of your pipelines. These methods provide powerful functionality for manipulating data as it passes through the pipeline.

### Reduce

The `Reduce` and `ReduceAsync` methods allow you to reduce a sequence of elements to a single value. You can specify a reducer function that defines how the elements should be combined, and a builder function that creates the pipeline for processing the elements.

### WaitAll

The `WaitAll` method allows you to wait for all pipelines to complete before continuing. You can specify a set of builders that create the pipelines to wait for, a reducer function that combines the results of the pipelines.

```csharp
var count = 0;

var command = PipelineFactory
.Start<int>()
.WaitAll( builders => builders.Create(
builder => builder.Pipe( ( ctx, arg ) => Interlocked.Increment( ref count ) ),
builder => builder.Pipe( ( ctx, arg ) => Interlocked.Increment( ref count ) )
),
reducer: ( ctx, arg, results ) => { return arg + results.Sum( x => (int) x.Result ); }
)
.Build();

var result = await command( new PipelineContext() );

Assert.AreEqual( 2, count );
Assert.AreEqual( 3, result );
```

### PipeIf

The `PipeIf` method allows you to conditionally add a step to the pipeline. You can specify a condition function that determines whether the step should be added, a builder function that creates the step, and an optional flag indicating whether middleware should be inherited.
## Pipeline of Pipelines

### ForEach and ForEachAsync
The `PipelineFactory` library allows you to use pipelines together. Since pipelines are just functions, they can be used
as input to other pipelines. This allows you to create complex data processing flows by reusing and chaining together
multiple pipelines.

The `ForEach` and `ForEachAsync` methods allow you to apply a pipeline to each element in a sequence. You can specify a builder function that creates the pipeline for processing the elements.
Here's an example of how to use pipelines together:

```csharp
var count = 0;

var command = PipelineFactory
var pipeline2 = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => arg.Split( ' ' ) )
.ForEach<string>( builder => builder
.Pipe( ( ctx, arg ) => count += 10 )
)
.Pipe( ( ctx, arg ) => count += 5 )
.Pipe( ( ctx, arg ) => $"{arg} again!" )
.Build();

await command( new PipelineContext(), "e f" );

Assert.AreEqual( count, 25 );
```

### Call and CallAsync

The `Call` and `CallAsync` methods allow you to add a procedure to the pipeline. You can think of these as `Action<T>` and `Pipe` like `Func<T>`.

In this example notice that `arg + 9` is not returned from the use of `Call`

```csharp
var callResult = string.Empty;

var command = PipelineFactory
var pipeline1 = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => arg + "1" )
.Pipe( ( ctx, arg ) => arg + "2" )
.Call( builder => builder
.Call( ( ctx, arg ) => callResult = arg + "3" )
.Pipe( ( ctx, arg ) => arg + "9" )
)
.Pipe( ( ctx, arg ) => arg + "4" )
.Pipe( ( ctx, arg ) => $"hello {arg}" )
.PipeAsync( pipeline2 )
.Build();

var result = await command( new PipelineContext() );
var result = await pipeline1( new PipelineContext(), "you" );

Assert.AreEqual( "124", result );
Assert.AreEqual( "123", callResult );
Assert.AreEqual( "hello you again!", result );
```

### Chaining Child Pipelines
## Conditional Flow and Advanced Features

The `PipelineFactory` library allows you to chain pipelines together. Since pipelines are just functions, they can be used as input to other pipelines. This allows you to create complex data processing flows by reusing and chaining together multiple pipelines.
The `PipelineFactory` library provides a variety of builders that allow you to customize the behavior of your pipelines.
These methods provide powerful functionality for manipulating data as it passes through the pipeline.

Here's an example of how to chain pipelines together:

```csharp
var command2 = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => $"{arg} again!" )
.Build();

var command1 = PipelineFactory
.Start<string>()
.Pipe( ( ctx, arg ) => $"hello {arg}" )
.PipeAsync( command2 )
.Build();

var result = await command1( new PipelineContext(), "pipeline" );

Assert.AreEqual( "hello pipeline again!", result );
```
- Functions
- Procedures
- Conditional Flow
- Iterators
- Reduce
- Parallel execution

## Credits

Expand Down
Loading

0 comments on commit 29cf446

Please sign in to comment.