Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ 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).

## [3.1.0] - 2025-04-23
### Added
- Added new extension methods for dependency injection:
- `AddWorkflows()` - Registers all workflow components (validations, activities, and workflows)
- `AddWorkflowValidations()` - Registers workflow validations only
- `AddWorkflowActivities()` - Registers workflow activities only

- Added support for automatic assembly scanning to discover workflow components
- Added the ability to specify service lifetime for workflow registrations

## [3.0.0] - 2025-05-01

### Added
Expand Down Expand Up @@ -34,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Compatibility

- All existing code using the deprecated methods will continue to work, but will show deprecation warnings
- All existing code using the deprecated methods will continue to work but will show deprecation warnings
- To migrate, replace:

```csharp
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<Description>A .NET library for building robust, functional workflows and processing pipelines.</Description>

<!-- Version information -->
<Version>3.0.0</Version>
<Version>3.1.0</Version>

<!-- Source linking -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
Expand Down
55 changes: 47 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@
[![NuGet Version](https://img.shields.io/nuget/v/Zooper.Bee.svg)](https://www.nuget.org/packages/Zooper.Bee/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A flexible and powerful workflow library for .NET that allows you to define complex business processes with a fluent API.
A flexible and powerful workflow library for .NET that allows you to define complex business processes with a fluent
API.

## Overview

Zooper.Bee lets you create workflows that process requests and produce either successful results or meaningful errors. The library uses a builder pattern to construct workflows with various execution patterns including sequential, conditional, parallel, and detached operations.
Zooper.Bee lets you create workflows that process requests and produce either successful results or meaningful errors.
The library uses a builder pattern to construct workflows with various execution patterns including sequential,
conditional, parallel, and detached operations.

## Key Concepts

- **Workflow**: A sequence of operations that process a request to produce a result or error
- **Request**: The input data to the workflow
- **Payload**: Data that passes through and gets modified by workflow activities
- **Success**: The successful result of the workflow
- **Error**: The error result if the workflow fails
- **Error**: The errors result if the workflow fails

## Installation

Expand Down Expand Up @@ -90,7 +93,8 @@ Validates the incoming request before processing begins.

#### Activities

Activities are the building blocks of a workflow. They process the payload and can produce either a success (with modified payload) or an error.
Activities are the building blocks of a workflow. They process the payload and can produce either a success (with
the modified payload) or an error.

```csharp
// Asynchronous activity
Expand Down Expand Up @@ -135,7 +139,8 @@ Activities that only execute if a condition is met.

#### Groups

Organize related activities into logical groups. Groups can have conditions and always merge their results back to the main workflow.
Organize related activities into logical groups. Groups can have conditions and always merge their results back to the
main workflow.

```csharp
.Group(
Expand All @@ -149,7 +154,8 @@ Organize related activities into logical groups. Groups can have conditions and

#### Contexts with Local State

Create a context with local state that is accessible to all activities within the context. This helps encapsulate related operations.
Create a context with the local state that is accessible to all activities within the context. This helps encapsulate
related operations.

```csharp
.WithContext(
Expand Down Expand Up @@ -188,7 +194,8 @@ Execute multiple groups of activities in parallel and merge the results.

#### Detached Execution

Execute activities in the background without waiting for their completion. Results from detached activities are not merged back into the main workflow.
Execute activities in the background without waiting for their completion. Results from detached activities are not
merged back into the main workflow.

```csharp
.Detach(
Expand Down Expand Up @@ -269,6 +276,38 @@ Use conditions to determine which path to take in a workflow.
)
```

## Dependency Injection Integration

Zooper.Bee integrates seamlessly with .NET's dependency injection system. You can register all workflow components with
a single extension method:

```csharp
// In Startup.cs or Program.cs
services.AddWorkflows();
```

This will scan all assemblies and register:

- All workflow validations
- All workflow activities
- All concrete workflow classes (classes ending with "Workflow")

You can also register specific components:

```csharp
// Register only validations
services.AddWorkflowValidations();

// Register only activities
services.AddWorkflowActivities();

// Specify which assemblies to scan
services.AddWorkflows(new[] { typeof(Program).Assembly });

// Specify service lifetime (Singleton, Scoped, Transient)
services.AddWorkflows(lifetime: ServiceLifetime.Singleton);
```

## Performance Considerations

- Use `Parallel` for CPU-bound operations that can benefit from parallel execution
Expand All @@ -288,4 +327,4 @@ Use conditions to determine which path to take in a workflow.

## License

MIT License (Copyright details here)
MIT License (Copyright details here)
2 changes: 1 addition & 1 deletion Zooper.Bee.Example/BranchingExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private static Workflow<RegistrationRequest, RegistrationSuccess, RegistrationEr
request.Password,
request.IsVipMember),

// Create result from final payload
// Create the result from the final payload
payload => new RegistrationSuccess(
payload.UserId,
payload.Email,
Expand Down
194 changes: 194 additions & 0 deletions Zooper.Bee/Extensions/WorkflowActivitiesExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Extension methods for registering workflow activities with dependency injection
/// </summary>
public static class WorkflowActivitiesExtensions
{
/// <summary>
/// Adds all workflow activities from the specified assemblies, or from all loaded assemblies if none specified
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="assemblies">Optional list of assemblies to scan. If null or empty, scans all loaded assemblies</param>
/// <param name="lifetime">The service lifetime (defaults to Scoped)</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddWorkflowActivities(
this IServiceCollection services,
IEnumerable<Assembly>? 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;
}

/// <summary>
/// Adds all workflow activities from the assemblies containing the specified marker types
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="markerTypes">Types whose assemblies will be scanned</param>
/// <param name="lifetime">The service lifetime (defaults to Scoped)</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining(
this IServiceCollection services,
IEnumerable<Type> 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);
}

/// <summary>
/// Adds all workflow activities from the assemblies containing the specified marker types
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="lifetime">The service lifetime (defaults to Scoped)</param>
/// <param name="markerTypes">Types whose assemblies will be scanned</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining(
this IServiceCollection services,
ServiceLifetime lifetime,
params Type[] markerTypes)
{
return services.AddWorkflowActivitiesFromAssembliesContaining(markerTypes, lifetime);
}

/// <summary>
/// Adds all workflow activities from the assemblies containing the specified marker types
/// </summary>
/// <param name="services">The service collection</param>
/// <param name="markerTypes">Types whose assemblies will be scanned</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining(
this IServiceCollection services,
params Type[] markerTypes)
{
return services.AddWorkflowActivitiesFromAssembliesContaining(markerTypes, ServiceLifetime.Scoped);
}

/// <summary>
/// Adds all workflow activities from the assemblies containing the specified marker types
/// </summary>
/// <typeparam name="T1">First marker type whose assembly will be scanned</typeparam>
/// <param name="services">The service collection</param>
/// <param name="lifetime">The service lifetime (defaults to Scoped)</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining<T1>(
this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
return services.AddWorkflowActivitiesFromAssembliesContaining(
[
typeof(T1)
],
lifetime
);
}

/// <summary>
/// Adds all workflow activities from the assemblies containing the specified marker types
/// </summary>
/// <typeparam name="T1">First marker type whose assembly will be scanned</typeparam>
/// <typeparam name="T2">Second marker type whose assembly will be scanned</typeparam>
/// <param name="services">The service collection</param>
/// <param name="lifetime">The service lifetime (defaults to Scoped)</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining<T1, T2>(
this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
return services.AddWorkflowActivitiesFromAssembliesContaining(
[
typeof(T1), typeof(T2)
],
lifetime
);
}

/// <summary>
/// Adds all workflow activities from the assemblies containing the specified marker types
/// </summary>
/// <typeparam name="T1">First marker type whose assembly will be scanned</typeparam>
/// <typeparam name="T2">Second marker type whose assembly will be scanned</typeparam>
/// <typeparam name="T3">Third marker type whose assembly will be scanned</typeparam>
/// <param name="services">The service collection</param>
/// <param name="lifetime">The service lifetime (defaults to Scoped)</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddWorkflowActivitiesFromAssembliesContaining<T1, T2, T3>(
this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
return services.AddWorkflowActivitiesFromAssembliesContaining(
[
typeof(T1), typeof(T2), typeof(T3)
],
lifetime
);
}

/// <summary>
/// Adds all workflow activities of specific types from the specified assemblies, or from all loaded assemblies if none specified
/// </summary>
/// <typeparam name="TPayload">The type of payload the activities process</typeparam>
/// <typeparam name="TError">The type of error the activities might return</typeparam>
/// <param name="services">The service collection</param>
/// <param name="assemblies">Optional list of assemblies to scan. If null or empty, scans all loaded assemblies</param>
/// <param name="lifetime">The service lifetime (defaults to Scoped)</param>
/// <returns>The service collection for chaining</returns>
public static IServiceCollection AddWorkflowActivities<TPayload, TError>(
this IServiceCollection services,
IEnumerable<Assembly>? 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<TPayload, TError> implementations
services.Scan(scan => scan
.FromAssemblies(assemblies)
.AddClasses(classes => classes.AssignableTo(typeof(IWorkflowActivity<TPayload, TError>)))
.AsImplementedInterfaces()
.WithLifetime(lifetime)
);

// Register all IWorkflowActivities<TPayload, TError> implementations
services.Scan(scan => scan
.FromAssemblies(assemblies)
.AddClasses(classes => classes.AssignableTo(typeof(IWorkflowActivities<TPayload, TError>)))
.AsImplementedInterfaces()
.WithLifetime(lifetime)
);

return services;
}
}
Loading