Skip to content

Commit

Permalink
Improve error handling (#41)
Browse files Browse the repository at this point in the history
* Introduces error filters
* Converts existing error policies to error filters
  • Loading branch information
Kralizek committed Jan 20, 2019
1 parent c639730 commit fe00ae8
Show file tree
Hide file tree
Showing 31 changed files with 1,134 additions and 595 deletions.
11 changes: 8 additions & 3 deletions src/Nybus.Abstractions/Configuration/INybusConfigurator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Nybus.Policies;
using Nybus.Filters;

namespace Nybus.Configuration
{
Expand All @@ -20,15 +21,19 @@ public interface INybusConfigurator

public interface INybusConfiguration
{
IErrorPolicy ErrorPolicy { get; set; }
IErrorFilter FallbackErrorFilter { get; }

IReadOnlyList<IErrorFilter> CommandErrorFilters { get; set; }

IReadOnlyList<IErrorFilter> EventErrorFilters { get; set; }
}

public static class NybusConfiguratorExtensions
{
public static void UseBusEngine<TEngine>(this INybusConfigurator configurator, Action<IServiceCollection> configureServices = null)
where TEngine : class, IBusEngine
{
configurator.AddServiceConfiguration(svcs => svcs.AddSingleton<IBusEngine, TEngine>());
configurator.AddServiceConfiguration(services => services.AddSingleton<IBusEngine, TEngine>());

if (configureServices != null)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;

namespace Nybus.Configuration
namespace Nybus
{
public class ConfigurationException : Exception
{
Expand All @@ -14,4 +14,14 @@ public ConfigurationException(string message) : base (message)

}
}

public class MissingHandlerException : Exception
{
public MissingHandlerException(Type handlerType, string message) : base(message)
{
HandlerType = handlerType;
}

public Type HandlerType { get; }
}
}
24 changes: 24 additions & 0 deletions src/Nybus.Abstractions/Filters/IErrorFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;

namespace Nybus.Filters
{
public interface IErrorFilter
{
Task HandleErrorAsync<TCommand>(ICommandContext<TCommand> context, Exception exception, CommandErrorDelegate<TCommand> next) where TCommand : class, ICommand;

Task HandleErrorAsync<TEvent>(IEventContext<TEvent> context, Exception exception, EventErrorDelegate<TEvent> next) where TEvent : class, IEvent;
}

public delegate Task CommandErrorDelegate<TCommand>(ICommandContext<TCommand> context, Exception exception) where TCommand : class, ICommand;

public delegate Task EventErrorDelegate<TEvent>(IEventContext<TEvent> context, Exception exception) where TEvent : class, IEvent;

public interface IErrorFilterProvider
{
string ProviderName { get; }

IErrorFilter CreateErrorFilter(IConfigurationSection settings);
}
}
23 changes: 0 additions & 23 deletions src/Nybus.Abstractions/Policies/IErrorPolicy.cs

This file was deleted.

7 changes: 0 additions & 7 deletions src/Nybus.Abstractions/Policies/IPolicy.cs

This file was deleted.

16 changes: 16 additions & 0 deletions src/Nybus.Abstractions/Utils/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Nybus.Utils
{
public static class EnumerableExtensions
{
public static IEnumerable<T> NotNull<T>(this IEnumerable<T> items) where T : class => items.Where(i => i != null);

public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> items) => items ?? Array.Empty<T>();

public static IEnumerable<T> IfNull<T>(this IEnumerable<T> items, IEnumerable<T> alternative) => items ?? alternative;
}
}
53 changes: 35 additions & 18 deletions src/Nybus/Configuration/INybusHostConfigurationFactory.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Nybus.Policies;
using Nybus.Filters;
using Nybus.Utils;

namespace Nybus.Configuration
{
public interface INybusHostConfigurationFactory
{
INybusConfiguration CreateConfiguration(NybusHostOptions options);
NybusConfiguration CreateConfiguration(NybusHostOptions options);
}

public class NybusHostOptions
{
public IConfigurationSection ErrorPolicy { get; set; }
public IConfigurationSection[] ErrorFilters { get; set; }

public IConfigurationSection[] CommandErrorFilters { get; set; }

public IConfigurationSection[] EventErrorFilters { get; set; }
}

public class NybusHostConfigurationFactory : INybusHostConfigurationFactory
{
private readonly IReadOnlyDictionary<string, IErrorPolicyProvider> _errorPolicyProviders;
private readonly IErrorFilter _fallbackErrorFilter;
private readonly IReadOnlyDictionary<string, IErrorFilterProvider> _errorFilterProvidersByName;

public NybusHostConfigurationFactory(IEnumerable<IErrorPolicyProvider> errorPolicyProviders)
public NybusHostConfigurationFactory(IEnumerable<IErrorFilterProvider> errorFilterProviders, FallbackErrorFilter fallbackErrorFilter)
{
_errorPolicyProviders = CreateDictionary(errorPolicyProviders ?? throw new ArgumentNullException(nameof(errorPolicyProviders)));
_fallbackErrorFilter = fallbackErrorFilter ?? throw new ArgumentNullException(nameof(fallbackErrorFilter));
_errorFilterProvidersByName = CreateDictionary(errorFilterProviders ?? throw new ArgumentNullException(nameof(errorFilterProviders)));
}

private IReadOnlyDictionary<string, IErrorPolicyProvider> CreateDictionary(IEnumerable<IErrorPolicyProvider> providers)
private static IReadOnlyDictionary<string, IErrorFilterProvider> CreateDictionary(IEnumerable<IErrorFilterProvider> providers)
{
var result = new Dictionary<string, IErrorPolicyProvider>(StringComparer.OrdinalIgnoreCase);
var result = new Dictionary<string, IErrorFilterProvider>(StringComparer.OrdinalIgnoreCase);

foreach (var provider in providers)
{
Expand All @@ -40,30 +47,40 @@ public NybusHostConfigurationFactory(IEnumerable<IErrorPolicyProvider> errorPoli
return result;
}

public INybusConfiguration CreateConfiguration(NybusHostOptions options)
public NybusConfiguration CreateConfiguration(NybusHostOptions options)
{
var errorPolicy = GetErrorPolicy(options.ErrorPolicy);

return new NybusConfiguration
{
ErrorPolicy = errorPolicy
FallbackErrorFilter = _fallbackErrorFilter,
CommandErrorFilters = GetErrorFilters(options.CommandErrorFilters),
EventErrorFilters = GetErrorFilters(options.EventErrorFilters)
};

IErrorPolicy GetErrorPolicy(IConfigurationSection section)
IReadOnlyList<IErrorFilter> GetErrorFilters(IEnumerable<IConfigurationSection> sections) => sections
.IfNull(options.ErrorFilters)
.EmptyIfNull()
.Select(GetErrorFilter)
.NotNull()
.ToArray();

IErrorFilter GetErrorFilter(IConfigurationSection section)
{
if (section != null && section.TryGetValue("ProviderName", out var providerName) && _errorPolicyProviders.TryGetValue(providerName, out var provider))
if (section != null && section.TryGetValue("type", out var providerName) && _errorFilterProvidersByName.TryGetValue(providerName, out var provider))
{
return provider.CreatePolicy(section);
return provider.CreateErrorFilter(section);
}

return new NoopErrorPolicy();
return null;
}
}
}

public class NybusConfiguration : INybusConfiguration
{
public IErrorPolicy ErrorPolicy { get; set; }
}
public IReadOnlyList<IErrorFilter> CommandErrorFilters { get; set; } = new IErrorFilter[0];

public IReadOnlyList<IErrorFilter> EventErrorFilters { get; set; } = new IErrorFilter[0];

public IErrorFilter FallbackErrorFilter { get; set; }
}
}
70 changes: 70 additions & 0 deletions src/Nybus/Filters/DiscardErrorFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Nybus.Filters
{
public class DiscardErrorFilterProvider : IErrorFilterProvider
{
private readonly IServiceProvider _serviceProvider;

public DiscardErrorFilterProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}

public string ProviderName { get; } = "discard";

public IErrorFilter CreateErrorFilter(IConfigurationSection settings)
{
var engine = _serviceProvider.GetRequiredService<IBusEngine>();
var logger = _serviceProvider.GetRequiredService<ILogger<DiscardErrorFilter>>();

return new DiscardErrorFilter(engine, logger);
}
}

public class DiscardErrorFilter : IErrorFilter
{
private readonly IBusEngine _engine;
private readonly ILogger<DiscardErrorFilter> _logger;

public DiscardErrorFilter(IBusEngine engine, ILogger<DiscardErrorFilter> logger)
{
_engine = engine ?? throw new ArgumentNullException(nameof(engine));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public async Task HandleErrorAsync<TCommand>(ICommandContext<TCommand> context, Exception exception, CommandErrorDelegate<TCommand> next)
where TCommand : class, ICommand
{
try
{
await _engine.NotifyFailAsync(context.Message).ConfigureAwait(false);
}
catch (Exception discardException)
{
_logger.LogError(discardException, ex => $"Unable to discard message: {ex.Message}");
await next(context, exception).ConfigureAwait(false);
}
}

public async Task HandleErrorAsync<TEvent>(IEventContext<TEvent> context, Exception exception, EventErrorDelegate<TEvent> next)
where TEvent : class, IEvent
{
try
{
await _engine.NotifyFailAsync(context.Message).ConfigureAwait(false);
}
catch (Exception discardException)
{
_logger.LogError(discardException, ex => $"Unable to discard message: {ex.Message}");
await next(context, exception).ConfigureAwait(false);
}
}
}
}
27 changes: 27 additions & 0 deletions src/Nybus/Filters/FallbackErrorFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Threading.Tasks;

namespace Nybus.Filters
{
public class FallbackErrorFilter : IErrorFilter
{
private readonly IBusEngine _engine;

public FallbackErrorFilter(IBusEngine engine)
{
_engine = engine ?? throw new ArgumentNullException(nameof(engine));
}

public Task HandleErrorAsync<TCommand>(ICommandContext<TCommand> context, Exception exception, CommandErrorDelegate<TCommand> next)
where TCommand : class, ICommand
{
return _engine.NotifyFailAsync(context.Message);
}

public Task HandleErrorAsync<TEvent>(IEventContext<TEvent> context, Exception exception, EventErrorDelegate<TEvent> next)
where TEvent : class, IEvent
{
return _engine.NotifyFailAsync(context.Message);
}
}
}
Loading

0 comments on commit fe00ae8

Please sign in to comment.