Skip to content

Commit e12e851

Browse files
authored
Clean Up Documentation (#20)
1 parent 7d1ff4f commit e12e851

9 files changed

Lines changed: 123 additions & 94 deletions

docs/docs/describe-pipelines.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ Example output:
4040
]
4141
```
4242

43+
:::warning Warning
44+
45+
The `Order` value in the JSON output represents the execution order of the steps. This value is assigned based on the order in which the steps will be executed, and it may differ from the `Order` specified in each step's `PipelineStep` attribute.
46+
47+
For example, if you have only two steps with `PipelineStep(Order = 3)` and `PipelineStep(Order = 4)`, the JSON output will show `Order` values of `0` and `1`, respectively - reflecting their relative execution sequence, not their original attribute values.
48+
49+
:::
50+
4351
## Instantiation Behavior
4452

4553
Calling `Describe()` **will instantiate all steps** in the pipeline by accessing their `Lazy<T>` wrappers. This may result in constructor injection or other side effects associated with instantiating the step class. Use this method only when you are prepared for that overhead.

docs/docs/diagnostics.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ listener.Subscribe(new Observer((name, payload) =>
4646

4747
## Conclusion
4848

49-
PipeForge diagnostics give you deep visibility into pipeline execution with minimal effort. Whether youre debugging a failing step or building runtime instrumentation, the diagnostics hooks are ready to help.
49+
PipeForge diagnostics give you deep visibility into pipeline execution with minimal effort. Whether you're debugging a failing step or building runtime instrumentation, the diagnostics hooks are ready to help.

docs/docs/index.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ PipeForge is a lightweight, composable, lazy-instantiation pipeline framework fo
99

1010
Pipelines operate on a specific class known as the **context**. Each pipeline step is a discrete unit of work, written in code and annotated with metadata indicating its order and (optional) filter. These steps are lazily instantiated and executed in sequence by the pipeline runner.
1111

12-
At any point, the pipeline can **short-circuit**, halting execution and preventing the instantiation of any remaining steps.
12+
At any point, the pipeline can **short-circuit**, halting execution - and preventing the instantiation of any remaining steps.
1313

1414
## Sample Context
1515

@@ -40,10 +40,10 @@ public class SampleContext
4040
```
4141

4242
This context allows us to:
43-
- Track pipeline progress via AddStep()
44-
- Print execution history using ToString()
45-
- Assert how many steps ran using StepCount
46-
- Simulate errors by passing invalid step names
43+
- Track pipeline progress via `AddStep()`
44+
- Print step execution history using `ToString()`
45+
- Assert how many steps ran using `StepCount`
46+
- Simulate errors by passing null or empty step names
4747

4848
## Installation
4949

docs/docs/manual-composition.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
sidebar_position: 7
3+
title: Manual Composition
4+
---
5+
6+
# Manual Composition
7+
8+
PipeForge is designed to integrate seamlessly with dependency injection, but it's flexible enough to support manual composition. This can be useful in testing scenarios, minimal environments, or when you need full control over pipeline configuration.
9+
10+
The main drawbacks of this approach are that you'll be responsible for manually instantiating all dependencies for each step, and the resulting pipeline may not accurately reflect the behavior of your production configuration.
11+
12+
## Creating a Pipeline
13+
14+
Manual pipeline composition is straightforward using the fluent API exposed by `PipelineBuilder<T>`, which is returned from the static method `Pipeline.CreateFor<T>()`. You can optionally pass an `ILoggerFactory` to enable logging during execution by the resulting `PipelineRunner<T>`.
15+
16+
Add steps to the pipeline in the desired execution order using the fluent, chainable methods:
17+
18+
- `Add<TStep>()` - for steps with a parameterless constructor
19+
- `AddStep<TStep>(Func<TStep> factory)` - for steps that require constructor parameters
20+
21+
```csharp title="Steps Used"
22+
private abstract class TestStep : PipelineStep<SampleContext>
23+
{
24+
public override async Task InvokeAsync(
25+
SampleContext context,
26+
PipelineDelegate<SampleContext> next,
27+
CancellationToken cancellationToken = default)
28+
{
29+
context.AddStep(Name);
30+
await next(context, cancellationToken);
31+
}
32+
}
33+
34+
private class StepA : TestStep
35+
{
36+
public StepA() => Name = "A";
37+
}
38+
39+
private class StepB : TestStep
40+
{
41+
public StepB() => Name = "B";
42+
}
43+
44+
private class StepC : TestStep
45+
{
46+
public StepC() => Name = "C";
47+
}
48+
49+
private class StepD : TestStep
50+
{
51+
public StepD(string name) => Name = name;
52+
}
53+
```
54+
55+
Note that `StepD` lacks a parameterless constructor, which means it can't be added using `Add<TStep>()`. Instead, you'll need to use `AddStep<TStep>(Func<TStep>)` to manually supply its dependencies - for example, if the step requires a configuration value, a logger, or a service instance.
56+
57+
```csharp title="Manual Pipeline Setup"
58+
var stepName = "Hello";
59+
var pipeline = Pipeline.CreateFor<SampleContext>()
60+
.WithStep<StepA>()
61+
.WithStep<StepB>()
62+
.WithStep<StepC>()
63+
.WithStep(() => new StepD(stepName))
64+
.Build();
65+
66+
var context = new SampleContext();
67+
await pipeline.ExecuteAsync(context);
68+
69+
Console.WriteLine(context);
70+
// Should be:
71+
// A,B,C,Hello
72+
```
73+
74+
## Advanced Scenarios
75+
76+
In more advanced scenarios, the factory delegate can create a scope and resolve dependencies from the scoped service provider before constructing the step. This is especially useful when the step relies on services with scoped lifetimes, such as per-request context or transient infrastructure components. For example:
77+
78+
```
79+
builder.WithStep(() =>
80+
{
81+
using var scope = serviceProvider.CreateScope();
82+
var scopedProvider = scope.ServiceProvider;
83+
var name = scopedProvider.GetRequiredService<IOptions<MySettings>>().Value.Name;
84+
return new StepD(name);
85+
});
86+
```
87+
88+
## Conclusion
89+
90+
Manual composition allows you to build and run a pipeline without relying on a DI container. While this approach is less common for production use, it provides maximum control for configuration, **testing** and minimal-host scenarios.

docs/docs/manual-wiring.md

Lines changed: 0 additions & 73 deletions
This file was deleted.

docs/docs/pipeline-steps.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The optional metadata properties do not impact step registration or execution, b
2121

2222
:::tip[Tip]
2323

24-
Because pipeline steps are plain classes, you can inject dependencies through their constructors - enabling full integration with your existing services, business logic, and data access layers.
24+
Because pipeline steps are plain classes, you can **inject dependencies** through their constructors - enabling full integration with your existing services, business logic, and data access layers.
2525

2626
:::
2727

@@ -73,7 +73,7 @@ public class ShortCircuitStep : PipelineStep<StepContext>
7373

7474
## Adding the `[PipelineStep]` Attribute
7575

76-
To be discoverable, each pipeline step must be decorated with the [PipelineStep] attribute. This attribute defines the order in which pipeline steps should execute.
76+
To be discoverable, each pipeline step must be decorated with the `[PipelineStep]` attribute. This attribute defines the order in which pipeline steps should execute.
7777

7878
```csharp title="Step1.cs"
7979
[PipelineStep(1)]
@@ -85,14 +85,14 @@ public class Step1 : PipelineStep<SampleContext>
8585

8686
:::tip[Tip]
8787

88-
- Multiple steps that are not using the same context will not be impacted by using the same order value.
89-
- Multiple steps that are using the same context and have the same order value will be ordered arbitrarily.
88+
- Multiple steps that are **not** using the same context will not be impacted by using the same order value.
89+
- Multiple steps that **are** using the same context and have the same order value will be ordered arbitrarily.
9090

9191
:::
9292

9393
### Adding a Step Filter
9494

95-
You can limit when a step will be registered in by adding the `Filter` parameter to the `PipelineStep` attribute. This is useful to add steps that will only be registered when the `Development` filter is applied.
95+
You can limit when a step will be registered by adding the `Filter` parameter to the `PipelineStep` attribute. This is useful to ensure that some steps will only be registered when the filter is applied during step discovery and registration.
9696

9797
```csharp title="Step2.cs"
9898
[PipelineStep(2, "Development")]
@@ -104,8 +104,8 @@ public class Step2 : PipelineStep<SampleContext>
104104

105105
:::danger[Caution]
106106

107-
- Steps that do NOT have an `Filter` parameter in the attribute will always be registered during the step discovery process.
108-
- Steps that DO have an `Filter` parameter in the attribute will only be registered if the same value is passed to the [registration extension method](./step-discovery.md).
107+
- Steps **without** a `Filter` parameter will always be registered during the step discovery and registration process.
108+
- Steps **with** `Filter` parameter will only be registered if the same value is passed to the [registration extension method](./step-discovery.md).
109109

110110
:::
111111

docs/docs/running-pipelines.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class StepB : PipelineStep<StepContext>
3333
}
3434

3535
[PipelineStep(3)]
36-
public class StepA : PipelineStep<StepContext>
36+
public class StepC : PipelineStep<StepContext>
3737
{
3838
public override async Task InvokeAsync(StepContext context, PipelineDelegate<StepContext> next, CancellationToken cancellationToken = default)
3939
{
@@ -46,12 +46,12 @@ var context = new SampleContext();
4646
var services = new ServiceCollection();
4747
services.AddPipelineFor<SampleContext>();
4848

49-
var provider = services.BuildServiceProvider()
49+
var provider = services.BuildServiceProvider();
5050
var runner = provider.GetRequiredService<IPipelineRunner<SampleContext>>();
5151

5252
await runner.ExecuteAsync(context);
5353

54-
Console.WriteLine(context.ToString()); // e.g. "A1,A2,B1"
54+
Console.WriteLine(context.ToString()); // e.g. "A1,B2,C3"
5555
```
5656

5757
Each registered pipeline step for `SampleContext` will be executed in order, unless short-circuited by a step that chooses not to call `next()`.

docs/docs/step-discovery.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ In order to ensure that pipeline steps are both registered correctly and registe
99

1010
## Using the Extension Method
1111

12-
When the extension method is called using only the type parameter `T`, it will scan all assemblies in the AppDomain for classes that implement `IPipelineStep<T>` and have the `PipelineStep` attribute that does not specify a filter. It will then register all matching classes in the dependency injection container - along with an instance of `IPipelineRunner<T>` - with a transient service lifetime.
12+
When the extension method is called using only the type parameter `T`, it will scan all assemblies in the `AppDomain` for classes that implement `IPipelineStep<T>` and have a `PipelineStep` attribute that does not specify a filter. It will then register all matching classes in the dependency injection container - along with an instance of `IPipelineRunner<T>` - with a transient service lifetime.
1313

1414
```csharp
1515
services.AddPipelineFor<SampleContext>();
@@ -21,10 +21,10 @@ The extension method takes optional parameters to change the service lifetime or
2121
// Add services using a scoped service lifetime
2222
services.AddPipelineFor<SampleContext>(ServiceLifetime.Scoped);
2323

24-
// Include steps explicitly marked with the "Development" filter
24+
// Include steps with the "Development" filter
2525
service.AddPipelineFor<SampleContext>("Development");
2626

27-
// Use a scoped lifetime and include marked with the "Development" filter
27+
// Use a scoped lifetime and include steps with the "Development" filter
2828
service.AddPipelineFor<SampleContext>(ServiceLifetime.Scoped, "Development");
2929
```
3030

@@ -43,7 +43,11 @@ public class ManuallyRegisterStep : PipelineStep<StepContext>
4343
services.AddPipelineStep<ManuallyRegisterStep>(ServiceLifetime.Scoped);
4444
```
4545

46-
Using this extension method will NOT register a corresponding instance of `IPipelineRunner<T>`.
46+
:::tip Note
47+
48+
Using this extension method will **not** register a corresponding instance of `IPipelineRunner<T>`.
49+
50+
:::
4751

4852
## Conclusion
4953

test-coverage.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ if ($IsWindows) {
3030
} elseif ($IsLinux) {
3131
xdg-open $indexPath
3232
} else {
33-
Write-Warning "Platform not detected open coverage-report/index.html manually"
33+
Write-Warning "Platform not detected - open coverage-report/index.html manually"
3434
}

0 commit comments

Comments
 (0)