diff --git a/.github/workflows/nuget.publish.yml b/.github/workflows/nuget.publish.yml index 844f1d7..7974401 100644 --- a/.github/workflows/nuget.publish.yml +++ b/.github/workflows/nuget.publish.yml @@ -2,85 +2,85 @@ name: Build and Publish on: push: + tags: + - "v*" branches: - - main - develop workflow_dispatch: permissions: - contents: write # Needed for tagging + contents: read env: - DOTNET_VERSION: "8.x" # Using .NET 8 as per Bee project requirements + DOTNET_VERSION: "8.x" jobs: setup: runs-on: ubuntu-latest outputs: - version: ${{ steps.extract_version.outputs.version }} package_version: ${{ steps.set_package_version.outputs.package_version }} + is_release: ${{ steps.set_package_version.outputs.is_release }} steps: - name: Checkout Repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Fetch all history and tags + uses: actions/checkout@v4 - - name: Set up .NET SDK - uses: actions/setup-dotnet@v3 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - - - name: Extract Central Version - id: extract_version + - name: Determine Package Version + id: set_package_version run: | - if [ ! -f "Directory.Build.props" ]; then - echo "Error: Directory.Build.props not found in the repository root." - exit 1 - fi - VERSION=$(grep -oP '(?<=)[^<]+' Directory.Build.props) - if [ -z "$VERSION" ]; then echo "Error: tag not found in Directory.Build.props." exit 1 fi - echo "Central Version: $VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "VERSION=$VERSION" >> $GITHUB_ENV + if [[ "$GITHUB_REF" == refs/tags/v* ]]; then + # Release: use version from Directory.Build.props as-is + PACKAGE_VERSION="${VERSION}" + IS_RELEASE="true" + else + # Pre-release: append preview suffix + PACKAGE_VERSION="${VERSION}-preview.${GITHUB_RUN_NUMBER}" + IS_RELEASE="false" + fi + + echo "Package Version: $PACKAGE_VERSION" + echo "package_version=${PACKAGE_VERSION}" >> $GITHUB_OUTPUT + echo "is_release=${IS_RELEASE}" >> $GITHUB_OUTPUT - name: Validate Semantic Versioning run: | - VERSION_REGEX="^[0-9]+\.[0-9]+\.[0-9]+$" - if [[ "${{ env.VERSION }}" =~ $VERSION_REGEX ]]; then - echo "Version '${{ env.VERSION }}' is valid." + VERSION="${{ steps.set_package_version.outputs.package_version }}" + # Match release (1.2.3) or pre-release (1.2.3-preview.42) + if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then + echo "Version '$VERSION' is valid." else - echo "Error: Version '${{ env.VERSION }}' does not follow semantic versioning (e.g., 1.0.0)." + echo "Error: Version '$VERSION' does not follow semantic versioning." exit 1 fi - - name: Determine Package Version - id: set_package_version - run: | - # Determine the current branch - CURRENT_BRANCH="${GITHUB_REF#refs/heads/}" - echo "Current Branch: $CURRENT_BRANCH" + test: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 - # Define release or pre-release based on branch - if [ "$CURRENT_BRANCH" == "main" ]; then - PACKAGE_VERSION="${VERSION}" - else - BUILD_NUMBER="${GITHUB_RUN_NUMBER}" - PACKAGE_VERSION="${VERSION}-preview.${BUILD_NUMBER}" - fi + - name: Set up .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} - echo "package_version=${PACKAGE_VERSION}" >> $GITHUB_OUTPUT + - name: Restore + run: dotnet restore - # Debug statement - echo "Determined Package Version: $PACKAGE_VERSION" + - name: Build + run: dotnet build --configuration Release --no-restore + + - name: Test + run: dotnet test --configuration Release --no-build --verbosity normal build-pack-publish: - needs: setup + needs: [setup, test] runs-on: ubuntu-latest strategy: matrix: @@ -90,65 +90,21 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 + uses: actions/checkout@v4 - name: Set up .NET SDK - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Create nupkg Directory - run: mkdir -p ./nupkg - - name: Restore run: dotnet restore "${{ matrix.project }}" - name: Build - run: dotnet build "${{ matrix.project }}" --configuration Release /p:PackageVersion=${{ needs.setup.outputs.package_version }} - - - name: Test - run: dotnet test --configuration Release --no-build + run: dotnet build "${{ matrix.project }}" --configuration Release --no-restore /p:PackageVersion=${{ needs.setup.outputs.package_version }} - name: Pack - run: dotnet pack "${{ matrix.project }}" --configuration Release /p:PackageVersion=${{ needs.setup.outputs.package_version }} --output ./nupkg + run: dotnet pack "${{ matrix.project }}" --configuration Release --no-build /p:PackageVersion=${{ needs.setup.outputs.package_version }} --output ./nupkg - name: Publish - if: github.event_name != 'pull_request' - run: dotnet nuget push "./nupkg/*.nupkg" --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json - - tag: - needs: [setup, build-pack-publish] - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' # Only create tags on main branch - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Configure Git - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - - name: Create and Push Git Tag - run: | - PACKAGE_VERSION="${{ needs.setup.outputs.package_version }}" - - echo "PACKAGE_VERSION is '$PACKAGE_VERSION'" - - if [ -z "$PACKAGE_VERSION" ]; then - echo "Error: PACKAGE_VERSION is empty." - exit 1 - fi - - # Check if tag already exists - if git rev-parse "$PACKAGE_VERSION" >/dev/null 2>&1; then - echo "Tag $PACKAGE_VERSION already exists. Skipping tagging." - else - git tag "$PACKAGE_VERSION" - git push origin "$PACKAGE_VERSION" - echo "Tag $PACKAGE_VERSION created and pushed successfully." - fi + run: dotnet nuget push "./nupkg/*.nupkg" --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c1e01df..5569aad 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -12,10 +12,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: "8.x" @@ -26,14 +26,4 @@ jobs: run: dotnet build --configuration Release --no-restore - name: Run tests - run: dotnet test --no-restore --verbosity normal - - - name: Check build warnings as errors - run: | - # This step ensures that TreatWarningsAsErrors is respected - dotnet build --configuration Release --no-restore --verbosity detailed | tee build.log - if grep -q "warning" build.log; then - echo "Build warnings found. Check the build log for details." - cat build.log | grep -i warning - exit 1 - fi + run: dotnet test --configuration Release --no-build --verbosity normal diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ef05f..65cf8df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- New `IWorkflowStep` and `IWorkflowStep` interfaces to replace `IWorkflowActivity` +- New `IWorkflowSteps` and `IWorkflowSteps` interfaces to replace `IWorkflowActivities` +- New `WorkflowStepsExtensions` class with `AddWorkflowSteps*` extension methods for dependency injection + +### Deprecated + +- `IWorkflowActivity` and `IWorkflowActivity` — use `IWorkflowStep` / `IWorkflowStep` instead +- `IWorkflowActivities` and `IWorkflowActivities` — use `IWorkflowSteps` / `IWorkflowSteps` instead +- `WorkflowActivitiesExtensions` class and all `AddWorkflowActivities*` methods — use `WorkflowStepsExtensions` and `AddWorkflowSteps*` instead + +### Compatibility + +- All existing code using the deprecated types and methods will continue to work but will show deprecation warnings +- To migrate, replace: + + ```csharp + services.AddWorkflowActivities(); + ``` + + With: + + ```csharp + services.AddWorkflowSteps(); + ``` + + And replace interface implementations: + + ```csharp + public class MyStep : IWorkflowActivity + ``` + + With: + + ```csharp + public class MyStep : IWorkflowStep + ``` + ## 3.3.0 - 2025.04.24 ### Added diff --git a/Zooper.Bee/BranchBuilder.cs b/Zooper.Bee/BranchBuilder.cs index f402c49..834aec8 100644 --- a/Zooper.Bee/BranchBuilder.cs +++ b/Zooper.Bee/BranchBuilder.cs @@ -29,7 +29,7 @@ internal BranchBuilder( /// The branch builder for fluent chaining public BranchBuilder Do(Func>> activity) { - _branch.Activities.Add(new WorkflowActivity(activity)); + _branch.Activities.Add(new WorkflowStep(activity)); return this; } @@ -40,7 +40,7 @@ public BranchBuilder Do(FuncThe branch builder for fluent chaining public BranchBuilder Do(Func> activity) { - _branch.Activities.Add(new WorkflowActivity((payload, _) => + _branch.Activities.Add(new WorkflowStep((payload, _) => Task.FromResult(activity(payload)) )); return this; @@ -55,7 +55,7 @@ public BranchBuilder DoAll(params Func(activity)); + _branch.Activities.Add(new WorkflowStep(activity)); } return this; } @@ -69,7 +69,7 @@ public BranchBuilder DoAll(params Func((payload, _) => + _branch.Activities.Add(new WorkflowStep((payload, _) => Task.FromResult(activity(payload)) )); } diff --git a/Zooper.Bee/Extensions/WorkflowActivitiesExtensions.cs b/Zooper.Bee/Extensions/WorkflowActivitiesExtensions.cs index e1cfa63..192edf7 100644 --- a/Zooper.Bee/Extensions/WorkflowActivitiesExtensions.cs +++ b/Zooper.Bee/Extensions/WorkflowActivitiesExtensions.cs @@ -1,9 +1,7 @@ -using System; +using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; -using Zooper.Bee.Interfaces; // ReSharper disable MemberCanBePrivate.Global @@ -12,183 +10,98 @@ namespace Zooper.Bee.Extensions; /// /// Extension methods for registering workflow activities with dependency injection /// +[Obsolete("Use WorkflowStepsExtensions instead. This class will be removed in a future version.")] public static class WorkflowActivitiesExtensions { /// /// Adds all workflow activities from the specified assemblies, or from all loaded assemblies if none specified /// - /// The service collection - /// Optional list of assemblies to scan. If null or empty, scans all loaded assemblies - /// The service lifetime (defaults to Scoped) - /// The service collection for chaining + [Obsolete("Use AddWorkflowSteps instead. This method will be removed in a future version.")] public static IServiceCollection AddWorkflowActivities( this IServiceCollection services, IEnumerable? assemblies = null, ServiceLifetime lifetime = ServiceLifetime.Scoped) { - // If no assemblies are specified, use all loaded assemblies - assemblies ??= AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.IsDynamic && !a.FullName.StartsWith("System") && !a.FullName.StartsWith("Microsoft")); - - // Register all IWorkflowActivity implementations - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes.AssignableTo(typeof(IWorkflowActivity))) - .AsImplementedInterfaces() - .WithLifetime(lifetime) - ); - - // Register all IWorkflowActivities implementations - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes.AssignableTo(typeof(IWorkflowActivities))) - .AsImplementedInterfaces() - .WithLifetime(lifetime) - ); - - return services; + return services.AddWorkflowSteps(assemblies, lifetime); } /// /// Adds all workflow activities from the assemblies containing the specified marker types /// - /// The service collection - /// Types whose assemblies will be scanned - /// The service lifetime (defaults to Scoped) - /// The service collection for chaining + [Obsolete("Use AddWorkflowStepsFromAssembliesContaining instead. This method will be removed in a future version.")] public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining( this IServiceCollection services, IEnumerable markerTypes, ServiceLifetime lifetime = ServiceLifetime.Scoped) { - if (markerTypes == null) throw new ArgumentNullException(nameof(markerTypes)); - - var assemblies = markerTypes.Select(t => t.Assembly).Distinct(); - return services.AddWorkflowActivities(assemblies, lifetime); + return services.AddWorkflowStepsFromAssembliesContaining(markerTypes, lifetime); } /// /// Adds all workflow activities from the assemblies containing the specified marker types /// - /// The service collection - /// The service lifetime (defaults to Scoped) - /// Types whose assemblies will be scanned - /// The service collection for chaining + [Obsolete("Use AddWorkflowStepsFromAssembliesContaining instead. This method will be removed in a future version.")] public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining( this IServiceCollection services, ServiceLifetime lifetime, params Type[] markerTypes) { - return services.AddWorkflowActivitiesFromAssembliesContaining(markerTypes, lifetime); + return services.AddWorkflowStepsFromAssembliesContaining(lifetime, markerTypes); } /// /// Adds all workflow activities from the assemblies containing the specified marker types /// - /// The service collection - /// Types whose assemblies will be scanned - /// The service collection for chaining + [Obsolete("Use AddWorkflowStepsFromAssembliesContaining instead. This method will be removed in a future version.")] public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining( this IServiceCollection services, params Type[] markerTypes) { - return services.AddWorkflowActivitiesFromAssembliesContaining(markerTypes, ServiceLifetime.Scoped); + return services.AddWorkflowStepsFromAssembliesContaining(markerTypes); } /// /// Adds all workflow activities from the assemblies containing the specified marker types /// - /// First marker type whose assembly will be scanned - /// The service collection - /// The service lifetime (defaults to Scoped) - /// The service collection for chaining + [Obsolete("Use AddWorkflowStepsFromAssembliesContaining instead. This method will be removed in a future version.")] public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining( this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Scoped) { - return services.AddWorkflowActivitiesFromAssembliesContaining( - [ - typeof(T1) - ], - lifetime - ); + return services.AddWorkflowStepsFromAssembliesContaining(lifetime); } /// /// Adds all workflow activities from the assemblies containing the specified marker types /// - /// First marker type whose assembly will be scanned - /// Second marker type whose assembly will be scanned - /// The service collection - /// The service lifetime (defaults to Scoped) - /// The service collection for chaining + [Obsolete("Use AddWorkflowStepsFromAssembliesContaining instead. This method will be removed in a future version.")] public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining( this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Scoped) { - return services.AddWorkflowActivitiesFromAssembliesContaining( - [ - typeof(T1), typeof(T2) - ], - lifetime - ); + return services.AddWorkflowStepsFromAssembliesContaining(lifetime); } /// /// Adds all workflow activities from the assemblies containing the specified marker types /// - /// First marker type whose assembly will be scanned - /// Second marker type whose assembly will be scanned - /// Third marker type whose assembly will be scanned - /// The service collection - /// The service lifetime (defaults to Scoped) - /// The service collection for chaining + [Obsolete("Use AddWorkflowStepsFromAssembliesContaining instead. This method will be removed in a future version.")] public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining( this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Scoped) { - return services.AddWorkflowActivitiesFromAssembliesContaining( - [ - typeof(T1), typeof(T2), typeof(T3) - ], - lifetime - ); + return services.AddWorkflowStepsFromAssembliesContaining(lifetime); } /// /// Adds all workflow activities of specific types from the specified assemblies, or from all loaded assemblies if none specified /// - /// The type of payload the activities process - /// The type of error the activities might return - /// The service collection - /// Optional list of assemblies to scan. If null or empty, scans all loaded assemblies - /// The service lifetime (defaults to Scoped) - /// The service collection for chaining + [Obsolete("Use AddWorkflowSteps instead. This method will be removed in a future version.")] public static IServiceCollection AddWorkflowActivities( this IServiceCollection services, IEnumerable? assemblies = null, ServiceLifetime lifetime = ServiceLifetime.Scoped) { - // If no assemblies are specified, use all loaded assemblies - assemblies ??= AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.IsDynamic && !a.FullName.StartsWith("System") && !a.FullName.StartsWith("Microsoft")); - - // Register all IWorkflowActivity implementations - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes.AssignableTo(typeof(IWorkflowActivity))) - .AsImplementedInterfaces() - .WithLifetime(lifetime) - ); - - // Register all IWorkflowActivities implementations - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes.AssignableTo(typeof(IWorkflowActivities))) - .AsImplementedInterfaces() - .WithLifetime(lifetime) - ); - - return services; + return services.AddWorkflowSteps(assemblies, lifetime); } -} \ No newline at end of file +} diff --git a/Zooper.Bee/Extensions/WorkflowExtensions.cs b/Zooper.Bee/Extensions/WorkflowExtensions.cs index c526b0e..f45527a 100644 --- a/Zooper.Bee/Extensions/WorkflowExtensions.cs +++ b/Zooper.Bee/Extensions/WorkflowExtensions.cs @@ -79,8 +79,8 @@ public static IServiceCollection AddWorkflows( // Register all workflow guards services.AddWorkflowGuards(assembliesToScan, lifetime); - // Register all workflow activities - services.AddWorkflowActivities(assembliesToScan, lifetime); + // Register all workflow steps + services.AddWorkflowSteps(assembliesToScan, lifetime); // Then register all classes ending with Workflow services.Scan(scan => scan diff --git a/Zooper.Bee/Extensions/WorkflowStepsExtensions.cs b/Zooper.Bee/Extensions/WorkflowStepsExtensions.cs new file mode 100644 index 0000000..40bf666 --- /dev/null +++ b/Zooper.Bee/Extensions/WorkflowStepsExtensions.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Zooper.Bee.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global + +namespace Zooper.Bee.Extensions; + +/// +/// Extension methods for registering workflow steps with dependency injection +/// +public static class WorkflowStepsExtensions +{ + /// + /// Adds all workflow steps from the specified assemblies, or from all loaded assemblies if none specified + /// + /// The service collection + /// Optional list of assemblies to scan. If null or empty, scans all loaded assemblies + /// The service lifetime (defaults to Scoped) + /// The service collection for chaining + public static IServiceCollection AddWorkflowSteps( + this IServiceCollection services, + IEnumerable? assemblies = null, + ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + // If no assemblies are specified, use all loaded assemblies + assemblies ??= AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !a.FullName.StartsWith("System") && !a.FullName.StartsWith("Microsoft")); + + // Register all IWorkflowStep implementations + services.Scan(scan => scan + .FromAssemblies(assemblies) + .AddClasses(classes => classes.AssignableTo(typeof(IWorkflowStep))) + .AsImplementedInterfaces() + .WithLifetime(lifetime) + ); + + // Register all IWorkflowSteps implementations + services.Scan(scan => scan + .FromAssemblies(assemblies) + .AddClasses(classes => classes.AssignableTo(typeof(IWorkflowSteps))) + .AsImplementedInterfaces() + .WithLifetime(lifetime) + ); + + return services; + } + + /// + /// Adds all workflow steps from the assemblies containing the specified marker types + /// + /// The service collection + /// Types whose assemblies will be scanned + /// The service lifetime (defaults to Scoped) + /// The service collection for chaining + public static IServiceCollection AddWorkflowStepsFromAssembliesContaining( + this IServiceCollection services, + IEnumerable markerTypes, + ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + if (markerTypes == null) throw new ArgumentNullException(nameof(markerTypes)); + + var assemblies = markerTypes.Select(t => t.Assembly).Distinct(); + return services.AddWorkflowSteps(assemblies, lifetime); + } + + /// + /// Adds all workflow steps from the assemblies containing the specified marker types + /// + /// The service collection + /// The service lifetime (defaults to Scoped) + /// Types whose assemblies will be scanned + /// The service collection for chaining + public static IServiceCollection AddWorkflowStepsFromAssembliesContaining( + this IServiceCollection services, + ServiceLifetime lifetime, + params Type[] markerTypes) + { + return services.AddWorkflowStepsFromAssembliesContaining(markerTypes, lifetime); + } + + /// + /// Adds all workflow steps from the assemblies containing the specified marker types + /// + /// The service collection + /// Types whose assemblies will be scanned + /// The service collection for chaining + public static IServiceCollection AddWorkflowStepsFromAssembliesContaining( + this IServiceCollection services, + params Type[] markerTypes) + { + return services.AddWorkflowStepsFromAssembliesContaining(markerTypes, ServiceLifetime.Scoped); + } + + /// + /// Adds all workflow steps from the assemblies containing the specified marker types + /// + /// First marker type whose assembly will be scanned + /// The service collection + /// The service lifetime (defaults to Scoped) + /// The service collection for chaining + public static IServiceCollection AddWorkflowStepsFromAssembliesContaining( + this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + return services.AddWorkflowStepsFromAssembliesContaining( + [ + typeof(T1) + ], + lifetime + ); + } + + /// + /// Adds all workflow steps from the assemblies containing the specified marker types + /// + /// First marker type whose assembly will be scanned + /// Second marker type whose assembly will be scanned + /// The service collection + /// The service lifetime (defaults to Scoped) + /// The service collection for chaining + public static IServiceCollection AddWorkflowStepsFromAssembliesContaining( + this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + return services.AddWorkflowStepsFromAssembliesContaining( + [ + typeof(T1), typeof(T2) + ], + lifetime + ); + } + + /// + /// Adds all workflow steps from the assemblies containing the specified marker types + /// + /// First marker type whose assembly will be scanned + /// Second marker type whose assembly will be scanned + /// Third marker type whose assembly will be scanned + /// The service collection + /// The service lifetime (defaults to Scoped) + /// The service collection for chaining + public static IServiceCollection AddWorkflowStepsFromAssembliesContaining( + this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + return services.AddWorkflowStepsFromAssembliesContaining( + [ + typeof(T1), typeof(T2), typeof(T3) + ], + lifetime + ); + } + + /// + /// Adds all workflow steps of specific types from the specified assemblies, or from all loaded assemblies if none specified + /// + /// The type of payload the steps process + /// The type of error the steps might return + /// The service collection + /// Optional list of assemblies to scan. If null or empty, scans all loaded assemblies + /// The service lifetime (defaults to Scoped) + /// The service collection for chaining + public static IServiceCollection AddWorkflowSteps( + this IServiceCollection services, + IEnumerable? assemblies = null, + ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + // If no assemblies are specified, use all loaded assemblies + assemblies ??= AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !a.FullName.StartsWith("System") && !a.FullName.StartsWith("Microsoft")); + + // Register all IWorkflowStep implementations + services.Scan(scan => scan + .FromAssemblies(assemblies) + .AddClasses(classes => classes.AssignableTo(typeof(IWorkflowStep))) + .AsImplementedInterfaces() + .WithLifetime(lifetime) + ); + + // Register all IWorkflowSteps implementations + services.Scan(scan => scan + .FromAssemblies(assemblies) + .AddClasses(classes => classes.AssignableTo(typeof(IWorkflowSteps))) + .AsImplementedInterfaces() + .WithLifetime(lifetime) + ); + + return services; + } +} diff --git a/Zooper.Bee/Features/Detached/Detached.cs b/Zooper.Bee/Features/Detached/Detached.cs index 1a9fa44..c4549ae 100644 --- a/Zooper.Bee/Features/Detached/Detached.cs +++ b/Zooper.Bee/Features/Detached/Detached.cs @@ -24,7 +24,7 @@ internal sealed class Detached : IWorkflowFeature /// The list of activities in this detached group. /// - public List> Activities { get; } = new(); + public List> Activities { get; } = new(); /// /// Creates a new detached group with an optional condition. diff --git a/Zooper.Bee/Features/Detached/DetachedBuilder.cs b/Zooper.Bee/Features/Detached/DetachedBuilder.cs index bf395b2..aaf5602 100644 --- a/Zooper.Bee/Features/Detached/DetachedBuilder.cs +++ b/Zooper.Bee/Features/Detached/DetachedBuilder.cs @@ -34,7 +34,7 @@ internal DetachedBuilder( public DetachedBuilder Do( Func>> activity) { - _detached.Activities.Add(new WorkflowActivity(activity)); + _detached.Activities.Add(new WorkflowStep(activity)); return this; } @@ -46,7 +46,7 @@ public DetachedBuilder Do( public DetachedBuilder Do( Func> activity) { - _detached.Activities.Add(new WorkflowActivity( + _detached.Activities.Add(new WorkflowStep( (payload, _) => Task.FromResult(activity(payload)) )); return this; @@ -62,7 +62,7 @@ public DetachedBuilder DoAll( { foreach (var activity in activities) { - _detached.Activities.Add(new WorkflowActivity(activity)); + _detached.Activities.Add(new WorkflowStep(activity)); } return this; } @@ -77,7 +77,7 @@ public DetachedBuilder DoAll( { foreach (var activity in activities) { - _detached.Activities.Add(new WorkflowActivity( + _detached.Activities.Add(new WorkflowStep( (payload, _) => Task.FromResult(activity(payload)) )); } diff --git a/Zooper.Bee/Features/Group/Group.cs b/Zooper.Bee/Features/Group/Group.cs index 8c2d225..3c40c9d 100644 --- a/Zooper.Bee/Features/Group/Group.cs +++ b/Zooper.Bee/Features/Group/Group.cs @@ -24,7 +24,7 @@ internal sealed class Group : IWorkflowFeature /// The list of activities in this group. /// - public List> Activities { get; } = new(); + public List> Activities { get; } = new(); /// /// Creates a new group with an optional condition. diff --git a/Zooper.Bee/Features/Group/GroupBuilder.cs b/Zooper.Bee/Features/Group/GroupBuilder.cs index c81e983..6dc9f61 100644 --- a/Zooper.Bee/Features/Group/GroupBuilder.cs +++ b/Zooper.Bee/Features/Group/GroupBuilder.cs @@ -34,7 +34,7 @@ internal GroupBuilder( public GroupBuilder Do( Func>> activity) { - _group.Activities.Add(new WorkflowActivity(activity)); + _group.Activities.Add(new WorkflowStep(activity)); return this; } @@ -46,7 +46,7 @@ public GroupBuilder Do( public GroupBuilder Do( Func> activity) { - _group.Activities.Add(new WorkflowActivity( + _group.Activities.Add(new WorkflowStep( (payload, _) => Task.FromResult(activity(payload)) )); return this; @@ -62,7 +62,7 @@ public GroupBuilder DoAll( { foreach (var activity in activities) { - _group.Activities.Add(new WorkflowActivity(activity)); + _group.Activities.Add(new WorkflowStep(activity)); } return this; } @@ -77,7 +77,7 @@ public GroupBuilder DoAll( { foreach (var activity in activities) { - _group.Activities.Add(new WorkflowActivity( + _group.Activities.Add(new WorkflowStep( (payload, _) => Task.FromResult(activity(payload)) )); } diff --git a/Zooper.Bee/Interfaces/IWorkflowActivities.cs b/Zooper.Bee/Interfaces/IWorkflowActivities.cs index ca51539..7c40104 100644 --- a/Zooper.Bee/Interfaces/IWorkflowActivities.cs +++ b/Zooper.Bee/Interfaces/IWorkflowActivities.cs @@ -1,13 +1,17 @@ -namespace Zooper.Bee.Interfaces; +using System; + +namespace Zooper.Bee.Interfaces; /// /// Base marker interface for workflow activity collections /// -public interface IWorkflowActivities; +[Obsolete("Use IWorkflowSteps instead. This interface will be removed in a future version.")] +public interface IWorkflowActivities : IWorkflowSteps; /// /// Interface for a collection of workflow activities operating on the same payload and error types /// /// The type of payload the activities process /// The type of error the activities might return -public interface IWorkflowActivities : IWorkflowActivities; \ No newline at end of file +[Obsolete("Use IWorkflowSteps instead. This interface will be removed in a future version.")] +public interface IWorkflowActivities : IWorkflowSteps; diff --git a/Zooper.Bee/Interfaces/IWorkflowActivity.cs b/Zooper.Bee/Interfaces/IWorkflowActivity.cs index 8e359f9..54c46bd 100644 --- a/Zooper.Bee/Interfaces/IWorkflowActivity.cs +++ b/Zooper.Bee/Interfaces/IWorkflowActivity.cs @@ -1,26 +1,17 @@ -using System.Threading; -using System.Threading.Tasks; -using Zooper.Fox; +using System; namespace Zooper.Bee.Interfaces; /// /// Base marker interface for all workflow activities /// -public interface IWorkflowActivity; +[Obsolete("Use IWorkflowStep instead. This interface will be removed in a future version.")] +public interface IWorkflowActivity : IWorkflowStep; /// /// Interface for an activity that works with a specific payload and error type /// /// The type of payload the activity processes /// The type of error the activity might return -public interface IWorkflowActivity : IWorkflowActivity -{ - /// - /// Executes the activity with the given payload - /// - /// The input payload - /// Cancellation token - /// Either the error or the updated payload - Task> Execute(TPayload payload, CancellationToken cancellationToken); -} +[Obsolete("Use IWorkflowStep instead. This interface will be removed in a future version.")] +public interface IWorkflowActivity : IWorkflowStep; diff --git a/Zooper.Bee/Interfaces/IWorkflowStep.cs b/Zooper.Bee/Interfaces/IWorkflowStep.cs new file mode 100644 index 0000000..bb556e4 --- /dev/null +++ b/Zooper.Bee/Interfaces/IWorkflowStep.cs @@ -0,0 +1,26 @@ +using System.Threading; +using System.Threading.Tasks; +using Zooper.Fox; + +namespace Zooper.Bee.Interfaces; + +/// +/// Base marker interface for all workflow steps +/// +public interface IWorkflowStep; + +/// +/// Interface for a step that works with a specific payload and error type +/// +/// The type of payload the step processes +/// The type of error the step might return +public interface IWorkflowStep : IWorkflowStep +{ + /// + /// Executes the step with the given payload + /// + /// The input payload + /// Cancellation token + /// Either the error or the updated payload + Task> Execute(TPayload payload, CancellationToken cancellationToken); +} diff --git a/Zooper.Bee/Interfaces/IWorkflowSteps.cs b/Zooper.Bee/Interfaces/IWorkflowSteps.cs new file mode 100644 index 0000000..9ad04ec --- /dev/null +++ b/Zooper.Bee/Interfaces/IWorkflowSteps.cs @@ -0,0 +1,13 @@ +namespace Zooper.Bee.Interfaces; + +/// +/// Base marker interface for workflow step collections +/// +public interface IWorkflowSteps; + +/// +/// Interface for a collection of workflow steps operating on the same payload and error types +/// +/// The type of payload the steps process +/// The type of error the steps might return +public interface IWorkflowSteps : IWorkflowSteps; diff --git a/Zooper.Bee/Internal/Branch.cs b/Zooper.Bee/Internal/Branch.cs index 9c9b9c3..70e00ed 100644 --- a/Zooper.Bee/Internal/Branch.cs +++ b/Zooper.Bee/Internal/Branch.cs @@ -11,7 +11,7 @@ namespace Zooper.Bee.Internal; internal sealed class Branch { public Func Condition { get; } - public List> Activities { get; } = []; + public List> Activities { get; } = []; public Branch(Func condition) { diff --git a/Zooper.Bee/Internal/ConditionalWorkflowActivity.cs b/Zooper.Bee/Internal/ConditionalWorkflowStep.cs similarity index 57% rename from Zooper.Bee/Internal/ConditionalWorkflowActivity.cs rename to Zooper.Bee/Internal/ConditionalWorkflowStep.cs index 0089896..0625ed1 100644 --- a/Zooper.Bee/Internal/ConditionalWorkflowActivity.cs +++ b/Zooper.Bee/Internal/ConditionalWorkflowStep.cs @@ -3,19 +3,19 @@ namespace Zooper.Bee.Internal; /// -/// Represents a conditional activity in the workflow that only executes if a condition is met. +/// Represents a conditional step in the workflow that only executes if a condition is met. /// /// Type of the payload /// Type of the error -internal sealed class ConditionalWorkflowActivity +internal sealed class ConditionalWorkflowStep { private readonly Func _condition; - public WorkflowActivity Activity { get; } + public WorkflowStep Activity { get; } - public ConditionalWorkflowActivity( + public ConditionalWorkflowStep( Func condition, - WorkflowActivity activity) + WorkflowStep activity) { _condition = condition; Activity = activity; @@ -25,4 +25,4 @@ public bool ShouldExecute(TPayload payload) { return _condition(payload); } -} \ No newline at end of file +} diff --git a/Zooper.Bee/Internal/Executors/ParallelExecutor.cs b/Zooper.Bee/Internal/Executors/ParallelExecutor.cs index 777ac13..a32d62a 100644 --- a/Zooper.Bee/Internal/Executors/ParallelExecutor.cs +++ b/Zooper.Bee/Internal/Executors/ParallelExecutor.cs @@ -169,7 +169,7 @@ protected override async Task> ExecuteTyped( // Helper method to execute a group's activities private async Task> ExecuteGroupActivities( - List> activities, + List> activities, TPayload payload, CancellationToken cancellationToken) { diff --git a/Zooper.Bee/Internal/WorkflowActivity.cs b/Zooper.Bee/Internal/WorkflowStep.cs similarity index 80% rename from Zooper.Bee/Internal/WorkflowActivity.cs rename to Zooper.Bee/Internal/WorkflowStep.cs index 2158f23..779f75b 100644 --- a/Zooper.Bee/Internal/WorkflowActivity.cs +++ b/Zooper.Bee/Internal/WorkflowStep.cs @@ -6,16 +6,16 @@ namespace Zooper.Bee.Internal; /// -/// Represents an activity (step) in the workflow that operates on a payload. +/// Represents a step in the workflow that operates on a payload. /// /// Type of the payload /// Type of the error -internal sealed class WorkflowActivity +internal sealed class WorkflowStep { private readonly Func>> _activity; private readonly string? _name; - public WorkflowActivity( + public WorkflowStep( Func>> activity, string? name = null) { @@ -27,4 +27,4 @@ public Task> Execute(TPayload payload, CancellationToke { return _activity(payload, token); } -} \ No newline at end of file +} diff --git a/Zooper.Bee/WorkflowBuilder.cs b/Zooper.Bee/WorkflowBuilder.cs index 0ceec49..1bfae4c 100644 --- a/Zooper.Bee/WorkflowBuilder.cs +++ b/Zooper.Bee/WorkflowBuilder.cs @@ -31,9 +31,9 @@ public sealed class WorkflowBuilder private readonly List> _guards = []; private readonly List> _validations = []; - private readonly List> _activities = []; - private readonly List> _conditionalActivities = []; - private readonly List> _finallyActivities = []; + private readonly List> _activities = []; + private readonly List> _conditionalActivities = []; + private readonly List> _finallyActivities = []; private readonly List> _branches = []; private readonly List _branchesWithLocalPayload = [];