Skip to content

Releases: petar-m/EventBrokerSlim

3.2.0

14 Sep 11:48
d11abb7
Compare
Choose a tag to compare

What's Changed

New Feature: Dynamic Delegate Event Handlers

Delegate handlers can be added or removed after DI container was built. Dynamic delegate handlers are created using DelegateHandlerRegistryBuilder and support all delegate handler features (retries, wrappers, etc.).

EventBroker registration adds IDynamicEventHandlers which is used for managing handlers. Adding handlers returns IDynamicHandlerClaimTicket used to remove the handlers. Since DelegateHandlerRegistryBuilder can define multiple handlers, all of them will be removed by the IDynamicHandlerClaimTicket instance.

public class DynamicEventHandlerExample : IDisposable
{
    private readonly IDynamicEventHandlers _dynamicEventHandlers;
    private readonly IDynamicHandlerClaimTicket _claimTicket;

    public DynamicEventHandlerExample(IDynamicEventHandlers dynamicEventHandlers)
    {
        _dynamicEventHandlers = dynamicEventHandlers;

        DelegateHandlerRegistryBuilder handlerRegistryBuilder = new();
        
        // Define two handlers for different events
        handlerRegistryBuilder
            .RegisterHandler<Event1>(HandleEvent1)
            .Builder()
            .RegisterHandler<Event2>(HandleEvent2);

        // Register with the event broker and keep a claim ticket
        _claimTicket = _dynamicEventHandlers.Add(handlerRegistryBuilder);
    }

    // All delegate features are available, including injecting services registered in DI
    private async Task HandleEvent1(Event1 event1, IRetryPolicy retryPolicy, ISomeService someService)
    {
        // event processing 
    }

    private async Task HandleEvent2(Event2 event2)
    {
        // event processing 
    }

    public void Dispose()
    {
        // Remove both event handlers using the IDynamicHandlerClaimTicket
        _dynamicEventHandlers.Remove(_claimTicket);
    }
}

Important

Make sure handlers are removed if containing classes are ephemeral.

Commits

Full Changelog: 3.1.0...3.2.0

3.1.0

29 Jun 12:59
2d172eb
Compare
Choose a tag to compare

What's Changed

New Feature: Delegate Handlers

Use DelegateHandlerRegistryBuilder to register delegate as handler:

DelegateHandlerRegistryBuilder builder = new();

builder.RegisterHandler<SomeEvent>(
    static async (SomeEvent someEvent, ISomeService service, CancellationToken cancellationToken) =>
    {
        await service.DoSomething(someEvent, cancellationToken);
    });

Delegate must return Task and can have 0 to 16 parameters. Parameter instances are resolved from DI container scope and passed when the delegate is invoked.
There are few special cases of optional parameters managed by EventBroker (without being registered in DI container):

  • TEvent - an instance of the event being handled. Should match the type of the event the delegate was registered for.
  • IRetryPolicy - the instance of the retry policy for the handler.
  • CancellationToken - the EventBroker cancellation token.
  • INextHandler - used to call the next wrapper in the chain or the handler if no more wrappers available (see below).

Delegate handlers registration has a decorator-like feature allowing to pipeline multiple delegates. The INextHandler instance is used to call the next in the pipeline.

builder.RegisterHandler<SomeEvent>(
            static async (SomeEvent someEvent, ISomeService someService) => await someService.DoSomething())
       .WrapWith(
            static async (INextHandler next, ILogger logger)
            {
                try
                {
                    await next.Execute();
                }
                catch(Exception ex)
                {
                    logger.LogError(ex);
                }
            })
       .WrapWith(
            static async (SomeEvent someEvent, ILogger logger)
            {
                Stopwatch timer = new();
                await next.Execute();
                timer.Stop();
                logger.LogInformation("{event} handling duration {elapsed}", someEvent, timer.Elapsed);
            });            

Delegate wrappers are executed from the last registered moving "inwards" toward the handler.

Commits

Full Changelog: 3.0.0...3.1.0

3.0.0

02 May 11:36
Compare
Choose a tag to compare

What's Changed

Breaking Changes

  • IEventHandler<T> methods now take parameter IRetryPolicy instead of RetryPolicy

before:

public interface IEventHandler<TEvent>
{
    Task Handle(TEvent @event, RetryPolicy retryPolicy, CancellationToken cancellationToken);
    Task OnError(Exception exception, TEvent @event, RetryPolicy retryPolicy, CancellationToken cancellationToken);
}

after:

public interface IEventHandler<TEvent>
{
    Task Handle(TEvent @event, IRetryPolicy retryPolicy, CancellationToken cancellationToken);
    Task OnError(Exception exception, TEvent @event, IRetryPolicy retryPolicy, CancellationToken cancellationToken);
}

Commits

2.0.0

01 May 15:12
Compare
Choose a tag to compare

What's Changed

Breaking Changes

  • IEventHandler<T> methods take additional parameter RetryPolicy

before:

public interface IEventHandler<TEvent>
{
    Task Handle(TEvent @event, CancellationToken cancellationToken);
    Task OnError(Exception exception, TEvent @event, CancellationToken cancellationToken);
}

after:

public interface IEventHandler<TEvent>
{
    Task Handle(TEvent @event, RetryPolicy retryPolicy, CancellationToken cancellationToken);
    Task OnError(Exception exception, TEvent @event, RetryPolicy retryPolicy, CancellationToken cancellationToken);
}
  • EventHandlerRegistryBuilder registration methods renamed to omit 'Keyed' word

before:

services.AddEventHandlers(
            x => x.AddKeyedTransient<Event1, EventHandler1>()
                  .AddKeyedScoped<Event2, EventHandler2>()
                  .AddKeyedSingleton<Event3, EventHandler3>())

after:

services.AddEventHandlers(
            x => x.AddTransient<Event1, EventHandler1>()
                  .AddScoped<Event2, EventHandler2>()
                  .AddSingleton<Event3, EventHandler3>())

New Features

Retries

Retrying within event hadler can become a bottleneck. Imagine EventBroker is restricted to one concurrent handler. An exception is caught in Handle and retry is attempted after given time interval. Since Handle is not completed, there is no available "slot" to run other handlers while Handle is waiting.

Another option will be to use IEventBroker.PublishDeferred. This will eliminate the bottleneck but will itroduce different problems. The same event will be handled again by all handlers, meaning specaial care should be taken to make all handlers idempotent. Any additional information (e.g. number of retries) needs to be known, it should be carried with the event, introducing accidential complexity.

To avoid these problems, both IEventBroker Handle and OnError methods have RetryPolicy parameter.

RetryPolicy.RetryAfter() will schedule a retry only for the handler it is called from, without blocking. After the given time interval an instance of the handler will be resolved from the DI container (from a new scope) and executed with the same event instance.

RetryPolicy.Attempt is the current retry attempt for a given handler and event.
RetryPolicy.LastDelay is the time interval before the retry.

RetryPolicy.RetryRequested is used to coordinate retry request between Handle and OnError. RetryPolicy is passed to both methods to enable error handling and retry request entirely in Handle method. OnError can check RetryPolicy.RetryRequested to know whether Hanlde had called RetryPolicy.RetryAfter().

Caution: the retry will not be exactly after the specified time interval in RetryPolicy.RetryAfter(). Take into account a tolerance of around 50 milliseconds. Additionally, retry executions respect maximum concurrent handlers setting, meaning a high load can cause additional delay.

Commits

1.0.0

12 Jan 17:36
297ef76
Compare
Choose a tag to compare

What's Changed

Full Changelog: 1.0.0-preview3...1.0.0

1.0.0-preview3

06 Jan 13:07
cd8409f
Compare
Choose a tag to compare

What's Changed

  • Add deferred publishing by @petar-m in #5
  • Cancel deferred and pending tasks on shutdown by @petar-m in #6

Full Changelog: 1.0.0-preview2...1.0.0-preview3

1.0.0-preview2

01 Jan 13:05
68c5450
Compare
Choose a tag to compare

What's Changed

  • Enable separate EventBroker and event handlers registration by @petar-m in #2
  • Remove EventHandlerRegistryBuilder.Add method by @petar-m in #3
  • Use Guid instead of string for service key by @petar-m in #4

New Contributors

Full Changelog: 1.0.0-preview1...1.0.0-preview2

1.0.0-preview1

28 Dec 15:48
Compare
Choose a tag to compare

Initial preview release.