diff --git a/ETL-Abstractions.sln b/ETL-Abstractions.sln index ab191738..de0e11f5 100644 --- a/ETL-Abstractions.sln +++ b/ETL-Abstractions.sln @@ -83,6 +83,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wolfgang.Etl.Abstractions.Benchmarks", "benchmarks\Wolfgang.Etl.Abstractions.Benchmarks\Wolfgang.Etl.Abstractions.Benchmarks.csproj", "{7855D034-57F4-4CF0-8EB4-5677EF80EC57}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example7-FluentPipeline", "examples\Net8.0\Example7-FluentPipeline\Example7-FluentPipeline.csproj", "{A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example7-FluentPipeline", "examples\Net4.8\Example7-FluentPipeline\Example7-FluentPipeline.csproj", "{F26156B5-9FDC-46CA-9152-F888932238E1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -321,6 +325,30 @@ Global {7855D034-57F4-4CF0-8EB4-5677EF80EC57}.Release|x64.Build.0 = Release|Any CPU {7855D034-57F4-4CF0-8EB4-5677EF80EC57}.Release|x86.ActiveCfg = Release|Any CPU {7855D034-57F4-4CF0-8EB4-5677EF80EC57}.Release|x86.Build.0 = Release|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Debug|x64.ActiveCfg = Debug|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Debug|x64.Build.0 = Debug|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Debug|x86.ActiveCfg = Debug|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Debug|x86.Build.0 = Debug|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Release|Any CPU.Build.0 = Release|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Release|x64.ActiveCfg = Release|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Release|x64.Build.0 = Release|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Release|x86.ActiveCfg = Release|Any CPU + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D}.Release|x86.Build.0 = Release|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Debug|x64.ActiveCfg = Debug|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Debug|x64.Build.0 = Debug|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Debug|x86.ActiveCfg = Debug|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Debug|x86.Build.0 = Debug|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Release|Any CPU.Build.0 = Release|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Release|x64.ActiveCfg = Release|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Release|x64.Build.0 = Release|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Release|x86.ActiveCfg = Release|Any CPU + {F26156B5-9FDC-46CA-9152-F888932238E1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -349,6 +377,8 @@ Global {AF971B90-A335-49AF-8AB6-F387CAED12E4} = {9B9A162C-C5B8-495C-A6D0-8C3135E283B9} {2D19706F-4199-46BD-B047-C4ED3AEDD90A} = {9B9A162C-C5B8-495C-A6D0-8C3135E283B9} {7855D034-57F4-4CF0-8EB4-5677EF80EC57} = {AE206253-B766-4B6A-8C08-9E70605A2B27} + {A39682A7-CD1A-40A2-9F5B-F1E045C11F8D} = {3C48157B-5E90-489E-9444-E01F51D59F86} + {F26156B5-9FDC-46CA-9152-F888932238E1} = {336D72A1-8E5E-49DE-83D9-DF6BE458BA24} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F673635D-58CE-48A5-9AE4-31F4484BED9E} diff --git a/README.md b/README.md index c39b1d40..1e3d8b8d 100644 --- a/README.md +++ b/README.md @@ -97,11 +97,36 @@ await Pipeline `InvalidOperationException`. Construct a new pipeline per run. - **Raw exception propagation** — stage exceptions bubble up unchanged; stage instances retain their `CurrentItemCount` and other state for post-mortem inspection. -- **Caller-owned lifetimes** — the pipeline never disposes the stages you hand it. +- **Caller-owned lifetimes by default** — the pipeline does not dispose the stages you + hand it unless you opt in with `.DisposeStagesOnCompletion()` (see below). The pipeline is syntactic sugar over the existing `IAsyncEnumerable` composition — there is no new runtime behavior, no buffering, and no additional allocations per item. +### Disposing stages + +By default the caller owns stage lifetimes. When the stages are owned by the call site and +should not outlive the run — the common short-lived case — opt into automatic disposal with +`.DisposeStagesOnCompletion()` instead of wrapping every stage in its own `using`: + +```csharp +await Pipeline + .Extract(csvExtractor) + .Transform(parseRecord) + .Load(sqlLoader) + .DisposeStagesOnCompletion() + .RunAsync(cancellationToken); +``` + +- Each stage that implements `IAsyncDisposable` is disposed via `DisposeAsync`; otherwise, if + it implements `IDisposable`, via `Dispose`. Stages that implement neither are skipped. +- Stages are disposed in **reverse construction order** (loader → transformers → extractor), + matching the LIFO convention of nested `using`/`await using` blocks and DI-scope disposal. +- Disposal runs whether the run succeeded or threw, and every stage is disposed even if an + earlier disposal throws. If the run **succeeded**, any disposal failures surface together as + an `AggregateException`. If the run **threw**, that exception propagates unchanged (disposal + still runs, but its own failures are suppressed so the run's failure stays the primary signal). + --- ## ✨ Features diff --git a/docfx_project/docs/getting-started.md b/docfx_project/docs/getting-started.md index dab43578..6a8d5280 100644 --- a/docfx_project/docs/getting-started.md +++ b/docfx_project/docs/getting-started.md @@ -4,10 +4,9 @@ This guide will help you quickly get up and running with Wolfgang.Etl.Abstractio ## Prerequisites - +- A project targeting any framework the package supports — .NET Framework 4.6.2+, + .NET Standard 2.0+, or .NET 5.0 through .NET 10.0. +- Any editor: Visual Studio 2022, Visual Studio Code, or JetBrains Rider. ## Installation @@ -25,17 +24,93 @@ Install-Package Wolfgang.Etl.Abstractions ## Quick Start - +An ETL flow has three kinds of stage — an **extractor** (produces items), zero or +more **transformers** (reshape items), and a **loader** (consumes items). Implement +the matching async interface for each: ```csharp -// Add your quick start code example here -// This should show the simplest way to use your library - using Wolfgang.Etl.Abstractions; -// Example usage +// Extract: produce a stream of items. +internal sealed class NumberExtractor : IExtractAsync +{ + public async IAsyncEnumerable ExtractAsync() + { + for (var i = 1; i <= 5; i++) + { + yield return i; + await Task.Yield(); + } + } +} + +// Transform: map each item to a new shape or type. +internal sealed class ToStringTransformer : ITransformAsync +{ + public async IAsyncEnumerable TransformAsync(IAsyncEnumerable items) + { + await foreach (var item in items) + { + yield return $"Item {item}"; + } + } +} + +// Load: consume the final stream. +internal sealed class ConsoleLoader : ILoadAsync +{ + public async Task LoadAsync(IAsyncEnumerable items) + { + await foreach (var item in items) + { + Console.WriteLine(item); + } + } +} ``` +Then compose them into a single, strongly-typed pipeline with the fluent +`Pipeline` API and run it: + +```csharp +await Pipeline + .Extract(new NumberExtractor()) + .Transform(new ToStringTransformer()) + .Load(new ConsoleLoader()) + .RunAsync(); +``` + +The compiler enforces that each stage's output type matches the next stage's +input, so a mismatch is a build error rather than a runtime surprise. + +### Optional capabilities + +The chain grows to fit the run without changing the stages: + +- **Cancellation** — pass a token to `RunAsync(token)`; it is forwarded to every + stage that supports cancellation. +- **Progress** — for stages that implement a progress-capable interface, call + `.WithProgress(progress)` to receive `IProgress` reports. Calling it on a + stage that doesn't support progress is a compile error. +- **Naming** — `.WithName("nightly-import")` labels the pipeline for diagnostics. +- **Stage disposal** — `.DisposeStagesOnCompletion()` disposes each stage (in + reverse construction order) once the run finishes, so short-lived stages the + call site owns don't each need their own `using`. + +```csharp +await Pipeline + .Extract(extractor).WithProgress(extractProgress) + .Transform(transformer) + .Load(loader).WithProgress(loadProgress) + .WithName("nightly-import") + .DisposeStagesOnCompletion() + .RunAsync(cancellationToken); +``` + +A complete, runnable version of the basic walkthrough lives in +[`examples/Net8.0/Example7-FluentPipeline`](https://github.com/Chris-Wolfgang/ETL-Abstractions/tree/main/examples/Net8.0/Example7-FluentPipeline) +(the `Net4.8` folder has the same example for .NET Framework). + ## Next Steps - Explore the [API Reference](../api/index.md) for detailed documentation diff --git a/docfx_project/docs/introduction.md b/docfx_project/docs/introduction.md index ad1909f5..ffd8feec 100644 --- a/docfx_project/docs/introduction.md +++ b/docfx_project/docs/introduction.md @@ -4,17 +4,28 @@ Welcome to Wolfgang.Etl.Abstractions! ## Overview -Interface and base classes for building ETLs - - +Wolfgang.Etl.Abstractions provides the interfaces and base classes for building +Extract–Transform–Load flows on top of `IAsyncEnumerable`. You implement an +extractor, zero or more transformers, and a loader, then compose them into a +single strongly-typed, streaming pipeline. ## Key Features - +- **Fluent, type-safe pipeline** — `Pipeline.Extract(...).Transform(...).Load(...).RunAsync()` + composes stages into one runnable flow; the compiler enforces that each stage's + output type matches the next stage's input. +- **Async streaming** — built on `IAsyncEnumerable`, so items flow through the + pipeline without buffering the whole set in memory. +- **Opt-in progress reporting** — progress-capable stages surface `IProgress` + reports with throughput and ETA, on a configurable interval. +- **Cooperative cancellation** — a `CancellationToken` passed to `RunAsync` is + forwarded to every stage that supports it. +- **Optional stage disposal** — `.DisposeStagesOnCompletion()` tears stages down + in reverse order after the run, for call sites that own short-lived stages. +- **Broad target coverage** — .NET Framework 4.6.2+, .NET Standard 2.0+, and + .NET 5.0 through .NET 10.0. + +See the [Getting Started](getting-started.md) guide for a full walkthrough. ## Getting Help diff --git a/examples/Net4.8/Example7-FluentPipeline/ConsoleColors.cs b/examples/Net4.8/Example7-FluentPipeline/ConsoleColors.cs new file mode 100644 index 00000000..9a4d00cb --- /dev/null +++ b/examples/Net4.8/Example7-FluentPipeline/ConsoleColors.cs @@ -0,0 +1,9 @@ +namespace Example7_FluentPipeline +{ + internal class ConsoleColors + { + public const string Green = "\u001b[32m"; + public const string Yellow = "\u001b[33m"; + public const string Reset = "\u001b[0m"; + } +} diff --git a/examples/Net4.8/Example7-FluentPipeline/ETL/ConsoleLoader.cs b/examples/Net4.8/Example7-FluentPipeline/ETL/ConsoleLoader.cs new file mode 100644 index 00000000..9829977c --- /dev/null +++ b/examples/Net4.8/Example7-FluentPipeline/ETL/ConsoleLoader.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Wolfgang.Etl.Abstractions; + +namespace Example7_FluentPipeline.ETL +{ + internal class ConsoleLoader : ILoadAsync + { + public async Task LoadAsync(IAsyncEnumerable items) + { + Console.WriteLine($"{ConsoleColors.Green}Loading{ConsoleColors.Reset} data to console asynchronously...\n"); + + await foreach (var item in items) + { + Console.WriteLine($"Loading item: {item}\n"); + await Task.Delay(50); // Simulate some delay for loading + } + + Console.WriteLine($"{ConsoleColors.Green}Loading{ConsoleColors.Reset} completed.\n"); + } + } +} diff --git a/examples/Net4.8/Example7-FluentPipeline/ETL/FibonacciExtractor.cs b/examples/Net4.8/Example7-FluentPipeline/ETL/FibonacciExtractor.cs new file mode 100644 index 00000000..60163b6a --- /dev/null +++ b/examples/Net4.8/Example7-FluentPipeline/ETL/FibonacciExtractor.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Wolfgang.Etl.Abstractions; + +namespace Example7_FluentPipeline.ETL +{ + internal class FibonacciExtractor : IExtractAsync + { + public async IAsyncEnumerable ExtractAsync() + { + Console.WriteLine($"{ConsoleColors.Green}Extracting{ConsoleColors.Reset} Fibonacci numbers asynchronously...\n"); + + var current = 1; + var previous = 0; + for (var x = 0; x < 10; ++x) + { + Console.WriteLine($"Extracting Fibonacci number {x + 1}: {current}"); + yield return current; + var temp = current; + current += previous; + previous = temp; + await Task.Yield(); // Simulate asynchronous operation + } + + Console.WriteLine($"{ConsoleColors.Green}Extraction{ConsoleColors.Reset} completed.\n"); + } + } +} diff --git a/examples/Net4.8/Example7-FluentPipeline/ETL/IntToStringTransformer.cs b/examples/Net4.8/Example7-FluentPipeline/ETL/IntToStringTransformer.cs new file mode 100644 index 00000000..9811367f --- /dev/null +++ b/examples/Net4.8/Example7-FluentPipeline/ETL/IntToStringTransformer.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Wolfgang.Etl.Abstractions; + +namespace Example7_FluentPipeline.ETL +{ + internal class IntToStringTransformer : ITransformAsync + { + public async IAsyncEnumerable TransformAsync(IAsyncEnumerable items) + { + Console.WriteLine($"{ConsoleColors.Green}Transforming{ConsoleColors.Reset} integers to strings asynchronously...\n"); + + await foreach (var item in items) + { + Console.WriteLine($"Transforming integer {item} to string."); + await Task.Delay(50); // Simulate some delay for transformation + yield return item.ToString(); + } + + Console.WriteLine($"{ConsoleColors.Green}Transformation{ConsoleColors.Reset} completed.\n"); + } + } +} diff --git a/examples/Net4.8/Example7-FluentPipeline/Example7-FluentPipeline.csproj b/examples/Net4.8/Example7-FluentPipeline/Example7-FluentPipeline.csproj new file mode 100644 index 00000000..8eb2605f --- /dev/null +++ b/examples/Net4.8/Example7-FluentPipeline/Example7-FluentPipeline.csproj @@ -0,0 +1,26 @@ + + + + Exe + net48 + Example7_FluentPipeline + Example7-FluentPipeline + 8 + true + true + true + false + $(NoWarn);CA2007 + + + + + + + + + + + + + diff --git a/examples/Net4.8/Example7-FluentPipeline/Program.cs b/examples/Net4.8/Example7-FluentPipeline/Program.cs new file mode 100644 index 00000000..a3ae88e3 --- /dev/null +++ b/examples/Net4.8/Example7-FluentPipeline/Program.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using Example7_FluentPipeline.ETL; +using Wolfgang.Etl.Abstractions; + +namespace Example7_FluentPipeline +{ + internal class Program + { + private static async Task Main() + { + Console.WriteLine($"{ConsoleColors.Green}.NET Version: {Environment.Version}{ConsoleColors.Reset}\n"); + + Console.WriteLine($"{ConsoleColors.Yellow}Starting ETL process via the fluent Pipeline API...{ConsoleColors.Reset}\n\n"); + + // Example1 wires the extractor, transformer, and loader together by hand: + // + // var source = extractor.ExtractAsync(); + // var transformed = transformer.TransformAsync(source); + // await loader.LoadAsync(transformed); + // + // The fluent Pipeline API composes the same three stages into a single + // strongly-typed chain. The compiler enforces that each stage's output + // type matches the next stage's input — a mismatch is a build error, not + // a runtime surprise. Each stage is passed via its most-derived interface + // so the right overload is selected (see the Pipeline XML docs). + await Pipeline + .Extract(new FibonacciExtractor()) // IExtractAsync + .Transform(new IntToStringTransformer()) // ITransformAsync + .Load(new ConsoleLoader()) // ILoadAsync + .WithName("fibonacci-demo") // optional, purely informational + .RunAsync(); + + Console.WriteLine($"\n\n{ConsoleColors.Yellow}ETL process completed.{ConsoleColors.Reset}"); + } + } +} diff --git a/examples/Net4.8/Example7-FluentPipeline/Properties/AssemblyInfo.cs b/examples/Net4.8/Example7-FluentPipeline/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..4ddd6a21 --- /dev/null +++ b/examples/Net4.8/Example7-FluentPipeline/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Example7-FluentPipeline")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Example7-FluentPipeline")] +[assembly: AssemblyCopyright("Copyright © 2026")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3815ad09-2ad5-4d38-8259-f521e42f1702")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/examples/Net8.0/Example7-FluentPipeline/ConsoleColors.cs b/examples/Net8.0/Example7-FluentPipeline/ConsoleColors.cs new file mode 100644 index 00000000..1e243a93 --- /dev/null +++ b/examples/Net8.0/Example7-FluentPipeline/ConsoleColors.cs @@ -0,0 +1,8 @@ +namespace Example7_FluentPipeline; + +internal class ConsoleColors +{ + public const string Green = "\e[32m"; + public const string Yellow = "\e[33m"; + public const string Reset = "\e[0m"; +} diff --git a/examples/Net8.0/Example7-FluentPipeline/ETL/ConsoleLoader.cs b/examples/Net8.0/Example7-FluentPipeline/ETL/ConsoleLoader.cs new file mode 100644 index 00000000..c0c20f95 --- /dev/null +++ b/examples/Net8.0/Example7-FluentPipeline/ETL/ConsoleLoader.cs @@ -0,0 +1,19 @@ +using Wolfgang.Etl.Abstractions; + +namespace Example7_FluentPipeline.ETL; + +internal class ConsoleLoader : ILoadAsync +{ + public async Task LoadAsync(IAsyncEnumerable items) + { + Console.WriteLine($"{ConsoleColors.Green}Loading{ConsoleColors.Reset} data to console asynchronously...\n"); + + await foreach (var item in items) + { + Console.WriteLine($"Loading item: {item}\n"); + await Task.Delay(50); // Simulate some delay for loading + } + + Console.WriteLine($"{ConsoleColors.Green}Loading{ConsoleColors.Reset} completed.\n"); + } +} diff --git a/examples/Net8.0/Example7-FluentPipeline/ETL/FibonacciExtractor.cs b/examples/Net8.0/Example7-FluentPipeline/ETL/FibonacciExtractor.cs new file mode 100644 index 00000000..a94f152a --- /dev/null +++ b/examples/Net8.0/Example7-FluentPipeline/ETL/FibonacciExtractor.cs @@ -0,0 +1,25 @@ +using Wolfgang.Etl.Abstractions; + +namespace Example7_FluentPipeline.ETL; + +internal class FibonacciExtractor : IExtractAsync +{ + public async IAsyncEnumerable ExtractAsync() + { + Console.WriteLine($"{ConsoleColors.Green}Extracting{ConsoleColors.Reset} Fibonacci numbers asynchronously...\n"); + + var current = 1; + var previous = 0; + for (var x = 0; x < 10; ++x) + { + Console.WriteLine($"Extracting Fibonacci number {x + 1}: {current}"); + yield return current; + var temp = current; + current += previous; + previous = temp; + await Task.Delay(100); // Simulate asynchronous operation + } + + Console.WriteLine($"{ConsoleColors.Green}Extraction{ConsoleColors.Reset} completed.\n"); + } +} diff --git a/examples/Net8.0/Example7-FluentPipeline/ETL/IntToStringTransformer.cs b/examples/Net8.0/Example7-FluentPipeline/ETL/IntToStringTransformer.cs new file mode 100644 index 00000000..b472d974 --- /dev/null +++ b/examples/Net8.0/Example7-FluentPipeline/ETL/IntToStringTransformer.cs @@ -0,0 +1,20 @@ +using Wolfgang.Etl.Abstractions; + +namespace Example7_FluentPipeline.ETL; + +internal class IntToStringTransformer : ITransformAsync +{ + public async IAsyncEnumerable TransformAsync(IAsyncEnumerable items) + { + Console.WriteLine($"{ConsoleColors.Green}Transforming{ConsoleColors.Reset} integers to strings asynchronously...\n"); + + await foreach (var item in items) + { + Console.WriteLine($"Transforming integer {item} to string."); + await Task.Delay(50); // Simulate some delay for transformation + yield return item.ToString(); + } + + Console.WriteLine($"{ConsoleColors.Green}Transformation{ConsoleColors.Reset} completed.\n"); + } +} diff --git a/examples/Net8.0/Example7-FluentPipeline/Example7-FluentPipeline.csproj b/examples/Net8.0/Example7-FluentPipeline/Example7-FluentPipeline.csproj new file mode 100644 index 00000000..2915e775 --- /dev/null +++ b/examples/Net8.0/Example7-FluentPipeline/Example7-FluentPipeline.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + Example7_FluentPipeline + enable + CA2007 + 1.0.0 + Copyright {copyright year} {author} + + + + + + + + + diff --git a/examples/Net8.0/Example7-FluentPipeline/Program.cs b/examples/Net8.0/Example7-FluentPipeline/Program.cs new file mode 100644 index 00000000..cf318e62 --- /dev/null +++ b/examples/Net8.0/Example7-FluentPipeline/Program.cs @@ -0,0 +1,34 @@ +using Example7_FluentPipeline.ETL; +using Wolfgang.Etl.Abstractions; + +namespace Example7_FluentPipeline; + +internal class Program +{ + private static async Task Main() + { + Console.WriteLine($"{ConsoleColors.Green}.NET Version: {Environment.Version}{ConsoleColors.Reset}\n"); + + Console.WriteLine($"{ConsoleColors.Yellow}Starting ETL process via the fluent Pipeline API...{ConsoleColors.Reset}\n\n"); + + // Example1 wires the extractor, transformer, and loader together by hand: + // + // var source = extractor.ExtractAsync(); + // var transformed = transformer.TransformAsync(source); + // await loader.LoadAsync(transformed); + // + // The fluent Pipeline API composes the same three stages into a single + // strongly-typed chain. The compiler enforces that each stage's output + // type matches the next stage's input — a mismatch is a build error, not + // a runtime surprise. Each stage is passed via its most-derived interface + // so the right overload is selected (see the Pipeline XML docs). + await Pipeline + .Extract(new FibonacciExtractor()) // IExtractAsync + .Transform(new IntToStringTransformer()) // ITransformAsync + .Load(new ConsoleLoader()) // ILoadAsync + .WithName("fibonacci-demo") // optional, purely informational + .RunAsync(); + + Console.WriteLine($"\n\n{ConsoleColors.Yellow}ETL process completed.{ConsoleColors.Reset}"); + } +}