[ci skip] Refactored how consume result is handled
Reimplemented the exception strategy and logging Much XML documentation, such wow
This commit is contained in:
parent
f8fca5879c
commit
6c32665c8a
@ -8,11 +8,6 @@ namespace Tapeti.Annotations
|
|||||||
/// Binds to an existing durable queue to receive messages. Can be used
|
/// Binds to an existing durable queue to receive messages. Can be used
|
||||||
/// on an entire MessageController class or on individual methods.
|
/// on an entire MessageController class or on individual methods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// At the moment there is no support for creating a durable queue and managing the
|
|
||||||
/// bindings. The author recommends https://git.x2software.net/pub/RabbitMetaQueue
|
|
||||||
/// for deploy-time management of durable queues (shameless plug intended).
|
|
||||||
/// </remarks>
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||||
[MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
|
[MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
|
||||||
public class DurableQueueAttribute : Attribute
|
public class DurableQueueAttribute : Attribute
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<NoWarn>1701;1702;1591</NoWarn>
|
<NoWarn>1701;1702</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Globalization;
|
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
|
||||||
namespace Tapeti.DataAnnotations.Extensions
|
namespace Tapeti.DataAnnotations.Extensions
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can be used on Guid fields which are supposed to be Required, as the Required attribute does
|
/// Can be used on Guid fields which are supposed to be Required, as the Required attribute does
|
||||||
/// not work for Guids and making them Nullable is counter-intuitive.
|
/// not work for Guids and making them Nullable is counter-intuitive.
|
||||||
@ -13,10 +15,12 @@ namespace Tapeti.DataAnnotations.Extensions
|
|||||||
private const string DefaultErrorMessage = "'{0}' does not contain a valid guid";
|
private const string DefaultErrorMessage = "'{0}' does not contain a valid guid";
|
||||||
private const string InvalidTypeErrorMessage = "'{0}' is not of type Guid";
|
private const string InvalidTypeErrorMessage = "'{0}' is not of type Guid";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public RequiredGuidAttribute() : base(DefaultErrorMessage)
|
public RequiredGuidAttribute() : base(DefaultErrorMessage)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value == null)
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<NoWarn>1701;1702;1591</NoWarn>
|
<NoWarn>1701;1702</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
namespace Tapeti.DataAnnotations
|
using Tapeti.Config;
|
||||||
|
|
||||||
|
namespace Tapeti.DataAnnotations
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extends ITapetiConfigBuilder to enable DataAnnotations.
|
||||||
|
/// </summary>
|
||||||
public static class ConfigExtensions
|
public static class ConfigExtensions
|
||||||
{
|
{
|
||||||
public static TapetiConfig WithDataAnnotations(this TapetiConfig config)
|
/// <summary>
|
||||||
|
/// Enables the DataAnnotations validation middleware.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config"></param>
|
||||||
|
public static ITapetiConfigBuilder WithDataAnnotations(this ITapetiConfigBuilder config)
|
||||||
{
|
{
|
||||||
config.Use(new DataAnnotationsMiddleware());
|
config.Use(new DataAnnotationsExtension());
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,18 @@ using Tapeti.Config;
|
|||||||
|
|
||||||
namespace Tapeti.DataAnnotations
|
namespace Tapeti.DataAnnotations
|
||||||
{
|
{
|
||||||
public class DataAnnotationsMiddleware : ITapetiExtension
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the DataAnnotations validation middleware.
|
||||||
|
/// </summary>
|
||||||
|
public class DataAnnotationsExtension : ITapetiExtension
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public void RegisterDefaults(IDependencyContainer container)
|
public void RegisterDefaults(IDependencyContainer container)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IEnumerable<object> GetMiddleware(IDependencyResolver dependencyResolver)
|
public IEnumerable<object> GetMiddleware(IDependencyResolver dependencyResolver)
|
||||||
{
|
{
|
||||||
return new object[]
|
return new object[]
|
@ -5,8 +5,13 @@ using Tapeti.Config;
|
|||||||
|
|
||||||
namespace Tapeti.DataAnnotations
|
namespace Tapeti.DataAnnotations
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Validates consumed messages using System.ComponentModel.DataAnnotations
|
||||||
|
/// </summary>
|
||||||
public class DataAnnotationsMessageMiddleware : IMessageMiddleware
|
public class DataAnnotationsMessageMiddleware : IMessageMiddleware
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public Task Handle(IMessageContext context, Func<Task> next)
|
public Task Handle(IMessageContext context, Func<Task> next)
|
||||||
{
|
{
|
||||||
var validationContext = new ValidationContext(context.Message);
|
var validationContext = new ValidationContext(context.Message);
|
||||||
|
@ -5,8 +5,13 @@ using Tapeti.Config;
|
|||||||
|
|
||||||
namespace Tapeti.DataAnnotations
|
namespace Tapeti.DataAnnotations
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Validates published messages using System.ComponentModel.DataAnnotations
|
||||||
|
/// </summary>
|
||||||
public class DataAnnotationsPublishMiddleware : IPublishMiddleware
|
public class DataAnnotationsPublishMiddleware : IPublishMiddleware
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public Task Handle(IPublishContext context, Func<Task> next)
|
public Task Handle(IPublishContext context, Func<Task> next)
|
||||||
{
|
{
|
||||||
var validationContext = new ValidationContext(context.Message);
|
var validationContext = new ValidationContext(context.Message);
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<NoWarn>1701;1702;1591</NoWarn>
|
<NoWarn>1701;1702</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -5,23 +5,32 @@ using Tapeti.Config;
|
|||||||
|
|
||||||
namespace Tapeti.Flow.SQL
|
namespace Tapeti.Flow.SQL
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extends ITapetiConfigBuilder to enable Flow SQL.
|
||||||
|
/// </summary>
|
||||||
public static class ConfigExtensions
|
public static class ConfigExtensions
|
||||||
{
|
{
|
||||||
public static TapetiConfig WithFlowSqlRepository(this TapetiConfig config, string connectionString, string tableName = "Flow")
|
/// <summary>
|
||||||
|
/// Enables the Flow SQL repository.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config"></param>
|
||||||
|
/// <param name="connectionString"></param>
|
||||||
|
/// <param name="tableName"></param>
|
||||||
|
public static ITapetiConfigBuilder WithFlowSqlRepository(this ITapetiConfigBuilder config, string connectionString, string tableName = "Flow")
|
||||||
{
|
{
|
||||||
config.Use(new FlowSqlRepositoryBundle(connectionString, tableName));
|
config.Use(new FlowSqlRepositoryExtension(connectionString, tableName));
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class FlowSqlRepositoryBundle : ITapetiExtension
|
internal class FlowSqlRepositoryExtension : ITapetiExtension
|
||||||
{
|
{
|
||||||
private readonly string connectionString;
|
private readonly string connectionString;
|
||||||
private readonly string tableName;
|
private readonly string tableName;
|
||||||
|
|
||||||
|
|
||||||
public FlowSqlRepositoryBundle(string connectionString, string tableName)
|
public FlowSqlRepositoryExtension(string connectionString, string tableName)
|
||||||
{
|
{
|
||||||
this.connectionString = connectionString;
|
this.connectionString = connectionString;
|
||||||
this.tableName = tableName;
|
this.tableName = tableName;
|
||||||
|
@ -7,25 +7,27 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace Tapeti.Flow.SQL
|
namespace Tapeti.Flow.SQL
|
||||||
{
|
{
|
||||||
/*
|
/// <summary>
|
||||||
Assumes the following table layout (table name configurable and may include schema):
|
/// IFlowRepository implementation for SQL server.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
create table Flow
|
/// Assumes the following table layout (table name configurable and may include schema):
|
||||||
(
|
///
|
||||||
FlowID uniqueidentifier not null,
|
/// create table Flow
|
||||||
CreationTime datetime2(3) not null,
|
/// (
|
||||||
StateJson nvarchar(max) null,
|
/// FlowID uniqueidentifier not null,
|
||||||
|
/// CreationTime datetime2(3) not null,
|
||||||
constraint PK_Flow primary key clustered (FlowID)
|
/// StateJson nvarchar(max) null,
|
||||||
);
|
/// constraint PK_Flow primary key clustered(FlowID)
|
||||||
*/
|
/// );
|
||||||
|
/// </remarks>
|
||||||
public class SqlConnectionFlowRepository : IFlowRepository
|
public class SqlConnectionFlowRepository : IFlowRepository
|
||||||
{
|
{
|
||||||
private readonly string connectionString;
|
private readonly string connectionString;
|
||||||
private readonly string tableName;
|
private readonly string tableName;
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public SqlConnectionFlowRepository(string connectionString, string tableName = "Flow")
|
public SqlConnectionFlowRepository(string connectionString, string tableName = "Flow")
|
||||||
{
|
{
|
||||||
this.connectionString = connectionString;
|
this.connectionString = connectionString;
|
||||||
@ -33,6 +35,7 @@ namespace Tapeti.Flow.SQL
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<List<KeyValuePair<Guid, T>>> GetStates<T>()
|
public async Task<List<KeyValuePair<Guid, T>>> GetStates<T>()
|
||||||
{
|
{
|
||||||
using (var connection = await GetConnection())
|
using (var connection = await GetConnection())
|
||||||
@ -56,6 +59,7 @@ namespace Tapeti.Flow.SQL
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task CreateState<T>(Guid flowID, T state, DateTime timestamp)
|
public async Task CreateState<T>(Guid flowID, T state, DateTime timestamp)
|
||||||
{
|
{
|
||||||
using (var connection = await GetConnection())
|
using (var connection = await GetConnection())
|
||||||
@ -76,6 +80,7 @@ namespace Tapeti.Flow.SQL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task UpdateState<T>(Guid flowID, T state)
|
public async Task UpdateState<T>(Guid flowID, T state)
|
||||||
{
|
{
|
||||||
using (var connection = await GetConnection())
|
using (var connection = await GetConnection())
|
||||||
@ -92,6 +97,7 @@ namespace Tapeti.Flow.SQL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task DeleteState(Guid flowID)
|
public async Task DeleteState(Guid flowID)
|
||||||
{
|
{
|
||||||
using (var connection = await GetConnection())
|
using (var connection = await GetConnection())
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<NoWarn>1701;1702;1591</NoWarn>
|
<NoWarn>1701;1702</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
namespace Tapeti.Flow
|
using Tapeti.Config;
|
||||||
|
|
||||||
|
namespace Tapeti.Flow
|
||||||
{
|
{
|
||||||
public static class ConfigExtensions
|
public static class ConfigExtensions
|
||||||
{
|
{
|
||||||
public static TapetiConfig WithFlow(this TapetiConfig config, IFlowRepository flowRepository = null)
|
public static ITapetiConfigBuilder WithFlow(this ITapetiConfigBuilder config, IFlowRepository flowRepository = null)
|
||||||
{
|
{
|
||||||
config.Use(new FlowMiddleware(flowRepository));
|
config.Use(new FlowMiddleware(flowRepository));
|
||||||
return config;
|
return config;
|
||||||
|
@ -44,7 +44,7 @@ namespace Tapeti.Flow.Default
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task Cleanup(IControllerMessageContext context, HandlingResult handlingResult, Func<Task> next)
|
public async Task Cleanup(IControllerMessageContext context, ConsumeResult consumeResult, Func<Task> next)
|
||||||
{
|
{
|
||||||
await next();
|
await next();
|
||||||
|
|
||||||
@ -53,11 +53,9 @@ namespace Tapeti.Flow.Default
|
|||||||
|
|
||||||
if (flowContext?.FlowStateLock != null)
|
if (flowContext?.FlowStateLock != null)
|
||||||
{
|
{
|
||||||
if (handlingResult.ConsumeResponse == ConsumeResponse.Nack
|
if (consumeResult == ConsumeResult.Error)
|
||||||
|| handlingResult.MessageAction == MessageAction.ErrorLog)
|
|
||||||
{
|
|
||||||
await flowContext.FlowStateLock.DeleteFlowState();
|
await flowContext.FlowStateLock.DeleteFlowState();
|
||||||
}
|
|
||||||
flowContext.FlowStateLock.Dispose();
|
flowContext.FlowStateLock.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ namespace Tapeti.Flow.Default
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task CallControllerMethod<TController>(MethodInfo method, Func<object, Task<IYieldPoint>> getYieldPointResult, object[] parameters) where TController : class
|
private async Task CallControllerMethod<TController>(MethodBase method, Func<object, Task<IYieldPoint>> getYieldPointResult, object[] parameters) where TController : class
|
||||||
{
|
{
|
||||||
var controller = config.DependencyResolver.Resolve<TController>();
|
var controller = config.DependencyResolver.Resolve<TController>();
|
||||||
var yieldPoint = await getYieldPointResult(method.Invoke(controller, parameters));
|
var yieldPoint = await getYieldPointResult(method.Invoke(controller, parameters));
|
||||||
@ -55,24 +55,20 @@ namespace Tapeti.Flow.Default
|
|||||||
|
|
||||||
var flowHandler = config.DependencyResolver.Resolve<IFlowHandler>();
|
var flowHandler = config.DependencyResolver.Resolve<IFlowHandler>();
|
||||||
|
|
||||||
HandlingResultBuilder handlingResult = new HandlingResultBuilder
|
|
||||||
{
|
|
||||||
ConsumeResponse = ConsumeResponse.Nack,
|
|
||||||
};
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await flowHandler.Execute(context, yieldPoint);
|
await flowHandler.Execute(context, yieldPoint);
|
||||||
handlingResult.ConsumeResponse = ConsumeResponse.Ack;
|
//handlingResult.ConsumeResponse = ConsumeResponse.Ack;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await RunCleanup(context, handlingResult.ToHandlingResult());
|
//await RunCleanup(context, handlingResult.ToHandlingResult());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
private async Task RunCleanup(MessageContext context, HandlingResult handlingResult)
|
private async Task RunCleanup(MessageContext context, HandlingResult handlingResult)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
foreach (var handler in config.CleanupMiddleware)
|
foreach (var handler in config.CleanupMiddleware)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -84,8 +80,8 @@ namespace Tapeti.Flow.Default
|
|||||||
logger.HandlerException(eCleanup);
|
logger.HandlerException(eCleanup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
private static MethodInfo GetExpressionMethod<TController, TResult>(Expression<Func<TController, Func<TResult>>> methodSelector)
|
private static MethodInfo GetExpressionMethod<TController, TResult>(Expression<Func<TController, Func<TResult>>> methodSelector)
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<NoWarn>1701;1702;1591</NoWarn>
|
<NoWarn>1701;1702</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,27 +1,39 @@
|
|||||||
using System;
|
using System;
|
||||||
using ISeriLogger = Serilog.ILogger;
|
using Tapeti.Config;
|
||||||
|
using ISerilogLogger = Serilog.ILogger;
|
||||||
|
|
||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
|
|
||||||
namespace Tapeti.Serilog
|
namespace Tapeti.Serilog
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Implements the Tapeti ILogger interface for Serilog output.
|
||||||
|
/// </summary>
|
||||||
public class TapetiSeriLogger: ILogger
|
public class TapetiSeriLogger: ILogger
|
||||||
{
|
{
|
||||||
private readonly ISeriLogger seriLogger;
|
private readonly ISerilogLogger seriLogger;
|
||||||
|
|
||||||
public TapetiSeriLogger(ISeriLogger seriLogger)
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public TapetiSeriLogger(ISerilogLogger seriLogger)
|
||||||
{
|
{
|
||||||
this.seriLogger = seriLogger;
|
this.seriLogger = seriLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Connect(TapetiConnectionParams connectionParams)
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Connect(TapetiConnectionParams connectionParams, bool isReconnect)
|
||||||
{
|
{
|
||||||
seriLogger.Information("Tapeti: trying to connect to {host}:{port}/{virtualHost}",
|
seriLogger
|
||||||
connectionParams.HostName,
|
.ForContext("isReconnect", isReconnect)
|
||||||
connectionParams.Port,
|
.Information("Tapeti: trying to connect to {host}:{port}/{virtualHost}",
|
||||||
connectionParams.VirtualHost);
|
connectionParams.HostName,
|
||||||
|
connectionParams.Port,
|
||||||
|
connectionParams.VirtualHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void ConnectFailed(TapetiConnectionParams connectionParams, Exception exception)
|
public void ConnectFailed(TapetiConnectionParams connectionParams, Exception exception)
|
||||||
{
|
{
|
||||||
seriLogger.Error(exception, "Tapeti: could not connect to {host}:{port}/{virtualHost}",
|
seriLogger.Error(exception, "Tapeti: could not connect to {host}:{port}/{virtualHost}",
|
||||||
@ -30,17 +42,34 @@ namespace Tapeti.Serilog
|
|||||||
connectionParams.VirtualHost);
|
connectionParams.VirtualHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectSuccess(TapetiConnectionParams connectionParams)
|
/// <inheritdoc />
|
||||||
|
public void ConnectSuccess(TapetiConnectionParams connectionParams, bool isReconnect)
|
||||||
{
|
{
|
||||||
seriLogger.Information("Tapeti: successfully connected to {host}:{port}/{virtualHost}",
|
seriLogger
|
||||||
connectionParams.HostName,
|
.ForContext("isReconnect", isReconnect)
|
||||||
connectionParams.Port,
|
.Information("Tapeti: successfully connected to {host}:{port}/{virtualHost}",
|
||||||
connectionParams.VirtualHost);
|
connectionParams.HostName,
|
||||||
|
connectionParams.Port,
|
||||||
|
connectionParams.VirtualHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandlerException(Exception e)
|
/// <inheritdoc />
|
||||||
|
public void ConsumeException(Exception exception, IMessageContext messageContext, ConsumeResult consumeResult)
|
||||||
{
|
{
|
||||||
seriLogger.Error(e, "Tapeti: exception in message handler");
|
var contextLogger = seriLogger
|
||||||
|
.ForContext("consumeResult", consumeResult)
|
||||||
|
.ForContext("exchange", messageContext.Exchange)
|
||||||
|
.ForContext("queue", messageContext.Queue)
|
||||||
|
.ForContext("routingKey", messageContext.RoutingKey);
|
||||||
|
|
||||||
|
if (messageContext is IControllerMessageContext controllerMessageContext)
|
||||||
|
{
|
||||||
|
contextLogger = contextLogger
|
||||||
|
.ForContext("controller", controllerMessageContext.Binding.Controller.FullName)
|
||||||
|
.ForContext("method", controllerMessageContext.Binding.Method.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
contextLogger.Error(exception, "Tapeti: exception in message handler");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,17 @@ using SimpleInjector;
|
|||||||
|
|
||||||
namespace Tapeti.SimpleInjector
|
namespace Tapeti.SimpleInjector
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Dependency resolver and container implementation for SimpleInjector.
|
||||||
|
/// </summary>
|
||||||
public class SimpleInjectorDependencyResolver : IDependencyContainer
|
public class SimpleInjectorDependencyResolver : IDependencyContainer
|
||||||
{
|
{
|
||||||
private readonly Container container;
|
private readonly Container container;
|
||||||
private readonly Lifestyle defaultsLifestyle;
|
private readonly Lifestyle defaultsLifestyle;
|
||||||
private readonly Lifestyle controllersLifestyle;
|
private readonly Lifestyle controllersLifestyle;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public SimpleInjectorDependencyResolver(Container container, Lifestyle defaultsLifestyle = null, Lifestyle controllersLifestyle = null)
|
public SimpleInjectorDependencyResolver(Container container, Lifestyle defaultsLifestyle = null, Lifestyle controllersLifestyle = null)
|
||||||
{
|
{
|
||||||
this.container = container;
|
this.container = container;
|
||||||
@ -17,17 +22,21 @@ namespace Tapeti.SimpleInjector
|
|||||||
this.controllersLifestyle = controllersLifestyle;
|
this.controllersLifestyle = controllersLifestyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public T Resolve<T>() where T : class
|
public T Resolve<T>() where T : class
|
||||||
{
|
{
|
||||||
return container.GetInstance<T>();
|
return container.GetInstance<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public object Resolve(Type type)
|
public object Resolve(Type type)
|
||||||
{
|
{
|
||||||
return container.GetInstance(type);
|
return container.GetInstance(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void RegisterDefault<TService, TImplementation>() where TService : class where TImplementation : class, TService
|
public void RegisterDefault<TService, TImplementation>() where TService : class where TImplementation : class, TService
|
||||||
{
|
{
|
||||||
if (!CanRegisterDefault<TService>())
|
if (!CanRegisterDefault<TService>())
|
||||||
@ -39,6 +48,7 @@ namespace Tapeti.SimpleInjector
|
|||||||
container.Register<TService, TImplementation>();
|
container.Register<TService, TImplementation>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void RegisterDefault<TService>(Func<TService> factory) where TService : class
|
public void RegisterDefault<TService>(Func<TService> factory) where TService : class
|
||||||
{
|
{
|
||||||
if (!CanRegisterDefault<TService>())
|
if (!CanRegisterDefault<TService>())
|
||||||
@ -50,24 +60,29 @@ namespace Tapeti.SimpleInjector
|
|||||||
container.Register(factory);
|
container.Register(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void RegisterDefaultSingleton<TService, TImplementation>() where TService : class where TImplementation : class, TService
|
public void RegisterDefaultSingleton<TService, TImplementation>() where TService : class where TImplementation : class, TService
|
||||||
{
|
{
|
||||||
if (CanRegisterDefault<TService>())
|
if (CanRegisterDefault<TService>())
|
||||||
container.RegisterSingleton<TService, TImplementation>();
|
container.RegisterSingleton<TService, TImplementation>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void RegisterDefaultSingleton<TService>(TService instance) where TService : class
|
public void RegisterDefaultSingleton<TService>(TService instance) where TService : class
|
||||||
{
|
{
|
||||||
if (CanRegisterDefault<TService>())
|
if (CanRegisterDefault<TService>())
|
||||||
container.RegisterInstance(instance);
|
container.RegisterInstance(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void RegisterDefaultSingleton<TService>(Func<TService> factory) where TService : class
|
public void RegisterDefaultSingleton<TService>(Func<TService> factory) where TService : class
|
||||||
{
|
{
|
||||||
if (CanRegisterDefault<TService>())
|
if (CanRegisterDefault<TService>())
|
||||||
container.RegisterSingleton(factory);
|
container.RegisterSingleton(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void RegisterController(Type type)
|
public void RegisterController(Type type)
|
||||||
{
|
{
|
||||||
if (controllersLifestyle != null)
|
if (controllersLifestyle != null)
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<NoWarn>1701;1702;1591</NoWarn>
|
<NoWarn>1701;1702</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -4,7 +4,7 @@ using Tapeti.Config;
|
|||||||
namespace Tapeti.Transient
|
namespace Tapeti.Transient
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TapetiConfig extension to register Tapeti.Transient
|
/// ITapetiConfigBuilder extension to register Tapeti.Transient
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ConfigExtensions
|
public static class ConfigExtensions
|
||||||
{
|
{
|
@ -13,8 +13,8 @@ namespace Tapeti.Config
|
|||||||
/// Called after the message handler method, even if exceptions occured.
|
/// Called after the message handler method, even if exceptions occured.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <param name="handlingResult"></param>
|
/// <param name="consumeResult"></param>
|
||||||
/// <param name="next">Always call to allow the next in the chain to clean up</param>
|
/// <param name="next">Always call to allow the next in the chain to clean up</param>
|
||||||
Task Cleanup(IControllerMessageContext context, HandlingResult handlingResult, Func<Task> next);
|
Task Cleanup(IControllerMessageContext context, ConsumeResult consumeResult, Func<Task> next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,26 @@
|
|||||||
|
|
||||||
namespace Tapeti.Config
|
namespace Tapeti.Config
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to information about the message being consumed.
|
||||||
|
/// Allows the strategy to determine how the exception should be handled.
|
||||||
|
/// </summary>
|
||||||
public interface IExceptionStrategyContext
|
public interface IExceptionStrategyContext
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to the message context.
|
||||||
|
/// </summary>
|
||||||
IMessageContext MessageContext { get; }
|
IMessageContext MessageContext { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the exception being handled.
|
||||||
|
/// </summary>
|
||||||
Exception Exception { get; }
|
Exception Exception { get; }
|
||||||
|
|
||||||
HandlingResultBuilder HandlingResult { get; set; }
|
/// <summary>
|
||||||
|
/// Determines how the message has been handled. Defaults to Error.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="consumeResult"></param>
|
||||||
|
void SetConsumeResult(ConsumeResult consumeResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,16 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Tapeti.Config
|
namespace Tapeti.Config
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Denotes middleware that processes all published messages.
|
||||||
|
/// </summary>
|
||||||
public interface IPublishMiddleware
|
public interface IPublishMiddleware
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a message is published.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
/// <param name="next">Call to pass the message to the next handler in the chain</param>
|
||||||
Task Handle(IPublishContext context, Func<Task> next);
|
Task Handle(IPublishContext context, Func<Task> next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,11 @@ namespace Tapeti.Connection
|
|||||||
public class TapetiBasicConsumer : DefaultBasicConsumer
|
public class TapetiBasicConsumer : DefaultBasicConsumer
|
||||||
{
|
{
|
||||||
private readonly IConsumer consumer;
|
private readonly IConsumer consumer;
|
||||||
private readonly Func<ulong, ConsumeResponse, Task> onRespond;
|
private readonly Func<ulong, ConsumeResult, Task> onRespond;
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TapetiBasicConsumer(IConsumer consumer, Func<ulong, ConsumeResponse, Task> onRespond)
|
public TapetiBasicConsumer(IConsumer consumer, Func<ulong, ConsumeResult, Task> onRespond)
|
||||||
{
|
{
|
||||||
this.consumer = consumer;
|
this.consumer = consumer;
|
||||||
this.onRespond = onRespond;
|
this.onRespond = onRespond;
|
||||||
@ -35,7 +35,7 @@ namespace Tapeti.Connection
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
await onRespond(deliveryTag, ConsumeResponse.Nack);
|
await onRespond(deliveryTag, ConsumeResult.Error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using System.Net;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
using RabbitMQ.Client.Events;
|
using RabbitMQ.Client.Events;
|
||||||
@ -50,7 +49,7 @@ namespace Tapeti.Connection
|
|||||||
private IModel channelInstance;
|
private IModel channelInstance;
|
||||||
private ulong lastDeliveryTag;
|
private ulong lastDeliveryTag;
|
||||||
private DateTime connectedDateTime;
|
private DateTime connectedDateTime;
|
||||||
private HttpClient managementClient;
|
private readonly HttpClient managementClient;
|
||||||
|
|
||||||
// These fields must be locked, since the callbacks for BasicAck/BasicReturn can run in a different thread
|
// These fields must be locked, since the callbacks for BasicAck/BasicReturn can run in a different thread
|
||||||
private readonly object confirmLock = new object();
|
private readonly object confirmLock = new object();
|
||||||
@ -186,28 +185,29 @@ namespace Tapeti.Connection
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task Respond(ulong deliveryTag, ConsumeResponse response)
|
private async Task Respond(ulong deliveryTag, ConsumeResult result)
|
||||||
{
|
{
|
||||||
await taskQueue.Value.Add(() =>
|
await taskQueue.Value.Add(() =>
|
||||||
{
|
{
|
||||||
// No need for a retryable channel here, if the connection is lost we can't
|
// No need for a retryable channel here, if the connection is lost we can't
|
||||||
// use the deliveryTag anymore.
|
// use the deliveryTag anymore.
|
||||||
switch (response)
|
switch (result)
|
||||||
{
|
{
|
||||||
case ConsumeResponse.Ack:
|
case ConsumeResult.Success:
|
||||||
|
case ConsumeResult.ExternalRequeue:
|
||||||
GetChannel().BasicAck(deliveryTag, false);
|
GetChannel().BasicAck(deliveryTag, false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ConsumeResponse.Nack:
|
case ConsumeResult.Error:
|
||||||
GetChannel().BasicNack(deliveryTag, false, false);
|
GetChannel().BasicNack(deliveryTag, false, false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ConsumeResponse.Requeue:
|
case ConsumeResult.Requeue:
|
||||||
GetChannel().BasicNack(deliveryTag, false, true);
|
GetChannel().BasicNack(deliveryTag, false, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException(nameof(response), response, null);
|
throw new ArgumentOutOfRangeException(nameof(result), result, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -454,7 +454,7 @@ namespace Tapeti.Connection
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logger.Connect(connectionParams);
|
logger.Connect(connectionParams, isReconnect);
|
||||||
|
|
||||||
connection = connectionFactory.CreateConnection();
|
connection = connectionFactory.CreateConnection();
|
||||||
channelInstance = connection.CreateModel();
|
channelInstance = connection.CreateModel();
|
||||||
@ -510,7 +510,7 @@ namespace Tapeti.Connection
|
|||||||
else
|
else
|
||||||
ConnectionEventListener?.Connected();
|
ConnectionEventListener?.Connected();
|
||||||
|
|
||||||
logger.ConnectSuccess(connectionParams);
|
logger.ConnectSuccess(connectionParams, isReconnect);
|
||||||
isReconnect = true;
|
isReconnect = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
using Tapeti.Config;
|
using Tapeti.Config;
|
||||||
using Tapeti.Default;
|
using Tapeti.Default;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -37,106 +38,49 @@ namespace Tapeti.Connection
|
|||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<ConsumeResponse> Consume(string exchange, string routingKey, IMessageProperties properties, byte[] body)
|
public async Task<ConsumeResult> Consume(string exchange, string routingKey, IMessageProperties properties, byte[] body)
|
||||||
{
|
{
|
||||||
|
object message = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var message = messageSerializer.Deserialize(body, properties);
|
message = messageSerializer.Deserialize(body, properties);
|
||||||
if (message == null)
|
if (message == null)
|
||||||
throw new ArgumentException($"Message body could not be deserialized into a message object in queue {queueName}", nameof(body));
|
throw new ArgumentException("Message body could not be deserialized into a message object", nameof(body));
|
||||||
|
|
||||||
await DispatchMessage(message, new MessageContextData
|
return await DispatchMessage(message, new MessageContextData
|
||||||
{
|
{
|
||||||
Exchange = exchange,
|
Exchange = exchange,
|
||||||
RoutingKey = routingKey,
|
RoutingKey = routingKey,
|
||||||
Properties = properties
|
Properties = properties
|
||||||
});
|
});
|
||||||
|
|
||||||
return ConsumeResponse.Ack;
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception dispatchException)
|
||||||
{
|
{
|
||||||
// TODO exception strategy
|
// TODO check if this is still necessary:
|
||||||
// TODO logger
|
// var exception = ExceptionDispatchInfo.Capture(UnwrapException(eDispatch));
|
||||||
return ConsumeResponse.Nack;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
using (var emptyContext = new MessageContext
|
||||||
/*
|
|
||||||
|
|
||||||
handlingResult = new HandlingResult
|
|
||||||
{
|
|
||||||
ConsumeResponse = ConsumeResponse.Ack,
|
|
||||||
MessageAction = MessageAction.None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (Exception eDispatch)
|
|
||||||
{
|
{
|
||||||
var exception = ExceptionDispatchInfo.Capture(UnwrapException(eDispatch));
|
Config = config,
|
||||||
logger.HandlerException(eDispatch);
|
Queue = queueName,
|
||||||
try
|
Exchange = exchange,
|
||||||
{
|
RoutingKey = routingKey,
|
||||||
var exceptionStrategyContext = new ExceptionStrategyContext(context, exception.SourceException);
|
Message = message,
|
||||||
|
Properties = properties,
|
||||||
exceptionStrategy.HandleException(exceptionStrategyContext);
|
Binding = null
|
||||||
|
})
|
||||||
handlingResult = exceptionStrategyContext.HandlingResult.ToHandlingResult();
|
|
||||||
}
|
|
||||||
catch (Exception eStrategy)
|
|
||||||
{
|
|
||||||
logger.HandlerException(eStrategy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (handlingResult == null)
|
var exceptionContext = new ExceptionStrategyContext(emptyContext, dispatchException);
|
||||||
{
|
HandleException(exceptionContext);
|
||||||
handlingResult = new HandlingResult
|
return exceptionContext.ConsumeResult;
|
||||||
{
|
|
||||||
ConsumeResponse = ConsumeResponse.Nack,
|
|
||||||
MessageAction = MessageAction.None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
await RunCleanup(context, handlingResult);
|
|
||||||
}
|
|
||||||
catch (Exception eCleanup)
|
|
||||||
{
|
|
||||||
logger.HandlerException(eCleanup);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (handlingResult == null)
|
|
||||||
{
|
|
||||||
handlingResult = new HandlingResult
|
|
||||||
{
|
|
||||||
ConsumeResponse = ConsumeResponse.Nack,
|
|
||||||
MessageAction = MessageAction.None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
await client.Respond(deliveryTag, handlingResult.ConsumeResponse);
|
|
||||||
}
|
|
||||||
catch (Exception eRespond)
|
|
||||||
{
|
|
||||||
logger.HandlerException(eRespond);
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
context?.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception eDispose)
|
|
||||||
{
|
|
||||||
logger.HandlerException(eDispose);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task DispatchMessage(object message, MessageContextData messageContextData)
|
private async Task<ConsumeResult> DispatchMessage(object message, MessageContextData messageContextData)
|
||||||
{
|
{
|
||||||
|
var returnResult = ConsumeResult.Success;
|
||||||
var messageType = message.GetType();
|
var messageType = message.GetType();
|
||||||
var validMessageType = false;
|
var validMessageType = false;
|
||||||
|
|
||||||
@ -145,18 +89,23 @@ namespace Tapeti.Connection
|
|||||||
if (!binding.Accept(messageType))
|
if (!binding.Accept(messageType))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
await InvokeUsingBinding(message, messageContextData, binding);
|
var consumeResult = await InvokeUsingBinding(message, messageContextData, binding);
|
||||||
validMessageType = true;
|
validMessageType = true;
|
||||||
|
|
||||||
|
if (consumeResult != ConsumeResult.Success)
|
||||||
|
returnResult = consumeResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validMessageType)
|
if (!validMessageType)
|
||||||
throw new ArgumentException($"Unsupported message type in queue {queueName}: {message.GetType().FullName}");
|
throw new ArgumentException($"No binding found for message type: {message.GetType().FullName}");
|
||||||
|
|
||||||
|
return returnResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task InvokeUsingBinding(object message, MessageContextData messageContextData, IBinding binding)
|
private async Task<ConsumeResult> InvokeUsingBinding(object message, MessageContextData messageContextData, IBinding binding)
|
||||||
{
|
{
|
||||||
var context = new MessageContext
|
using (var context = new MessageContext
|
||||||
{
|
{
|
||||||
Config = config,
|
Config = config,
|
||||||
Queue = queueName,
|
Queue = queueName,
|
||||||
@ -165,21 +114,44 @@ namespace Tapeti.Connection
|
|||||||
Message = message,
|
Message = message,
|
||||||
Properties = messageContextData.Properties,
|
Properties = messageContextData.Properties,
|
||||||
Binding = binding
|
Binding = binding
|
||||||
};
|
})
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await MiddlewareHelper.GoAsync(config.Middleware.Message,
|
||||||
|
(handler, next) => handler.Handle(context, next),
|
||||||
|
async () => { await binding.Invoke(context); });
|
||||||
|
|
||||||
try
|
return ConsumeResult.Success;
|
||||||
{
|
}
|
||||||
await MiddlewareHelper.GoAsync(config.Middleware.Message,
|
catch (Exception invokeException)
|
||||||
(handler, next) => handler.Handle(context, next),
|
{
|
||||||
async () => { await binding.Invoke(context); });
|
var exceptionContext = new ExceptionStrategyContext(context, invokeException);
|
||||||
}
|
HandleException(exceptionContext);
|
||||||
finally
|
return exceptionContext.ConsumeResult;
|
||||||
{
|
}
|
||||||
context.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void HandleException(ExceptionStrategyContext exceptionContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
exceptionStrategy.HandleException(exceptionContext);
|
||||||
|
}
|
||||||
|
catch (Exception strategyException)
|
||||||
|
{
|
||||||
|
// Exception in the exception strategy. Oh dear.
|
||||||
|
exceptionContext.SetConsumeResult(ConsumeResult.Error);
|
||||||
|
logger.ConsumeException(strategyException, exceptionContext.MessageContext, ConsumeResult.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.ConsumeException(exceptionContext.Exception, exceptionContext.MessageContext, exceptionContext.ConsumeResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private struct MessageContextData
|
private struct MessageContextData
|
||||||
{
|
{
|
||||||
public string Exchange;
|
public string Exchange;
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
namespace Tapeti
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the response sent back after handling a message.
|
|
||||||
/// </summary>
|
|
||||||
public enum ConsumeResponse
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Acknowledge the message and remove it from the queue
|
|
||||||
/// </summary>
|
|
||||||
Ack,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Negatively acknowledge the message and remove it from the queue, send to dead-letter queue if configured on the bus
|
|
||||||
/// </summary>
|
|
||||||
Nack,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Negatively acknowledge the message and put it back in the queue to try again later
|
|
||||||
/// </summary>
|
|
||||||
Requeue
|
|
||||||
}
|
|
||||||
}
|
|
33
Tapeti/ConsumeResult.cs
Normal file
33
Tapeti/ConsumeResult.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
namespace Tapeti
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines how the message has been handled and the response given to the message bus.
|
||||||
|
/// </summary>
|
||||||
|
public enum ConsumeResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Acknowledge the message and remove it from the queue.
|
||||||
|
/// </summary>
|
||||||
|
Success,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Negatively acknowledge the message and remove it from the queue, send to dead-letter queue if configured on the bus.
|
||||||
|
/// </summary>
|
||||||
|
Error,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Negatively acknowledge the message and put it back in the queue to try again later.
|
||||||
|
/// </summary>
|
||||||
|
Requeue,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The message has been stored for republishing and will be delivered again by some other means.
|
||||||
|
/// It will be acknowledged and removed from the queue as if succesful.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This option is for compatibility with external scheduler services. The exception strategy must guarantee that the
|
||||||
|
/// message will eventually be republished.
|
||||||
|
/// </remarks>
|
||||||
|
ExternalRequeue
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,49 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
namespace Tapeti.Default
|
namespace Tapeti.Default
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Default ILogger implementation for console applications.
|
||||||
|
/// </summary>
|
||||||
public class ConsoleLogger : ILogger
|
public class ConsoleLogger : ILogger
|
||||||
{
|
{
|
||||||
public void Connect(TapetiConnectionParams connectionParams)
|
/// <inheritdoc />
|
||||||
|
public void Connect(TapetiConnectionParams connectionParams, bool isReconnect)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[Tapeti] Connecting to {connectionParams.HostName}:{connectionParams.Port}{connectionParams.VirtualHost}");
|
Console.WriteLine($"[Tapeti] {(isReconnect ? "Reconnecting" : "Connecting")} to {connectionParams.HostName}:{connectionParams.Port}{connectionParams.VirtualHost}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void ConnectFailed(TapetiConnectionParams connectionParams, Exception exception)
|
public void ConnectFailed(TapetiConnectionParams connectionParams, Exception exception)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[Tapeti] Connection failed: {exception}");
|
Console.WriteLine($"[Tapeti] Connection failed: {exception}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectSuccess(TapetiConnectionParams connectionParams)
|
/// <inheritdoc />
|
||||||
|
public void ConnectSuccess(TapetiConnectionParams connectionParams, bool isReconnect)
|
||||||
{
|
{
|
||||||
Console.WriteLine("[Tapeti] Connected");
|
Console.WriteLine($"[Tapeti] {(isReconnect ? "Reconnected" : "Connected")}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandlerException(Exception e)
|
/// <inheritdoc />
|
||||||
|
public void ConsumeException(Exception exception, IMessageContext messageContext, ConsumeResult consumeResult)
|
||||||
{
|
{
|
||||||
Console.WriteLine(e.ToString());
|
Console.WriteLine("[Tapeti] Exception while handling message");
|
||||||
|
Console.WriteLine($" Result : {consumeResult}");
|
||||||
|
Console.WriteLine($" Exchange : {messageContext.Exchange}");
|
||||||
|
Console.WriteLine($" Queue : {messageContext.Queue}");
|
||||||
|
Console.WriteLine($" RoutingKey : {messageContext.RoutingKey}");
|
||||||
|
|
||||||
|
if (messageContext is IControllerMessageContext controllerMessageContext)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" Controller : {controllerMessageContext.Binding.Controller.FullName}");
|
||||||
|
Console.WriteLine($" Method : {controllerMessageContext.Binding.Method.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ namespace Tapeti.Default
|
|||||||
/// <inheritdoc cref="IControllerMessageContext" />
|
/// <inheritdoc cref="IControllerMessageContext" />
|
||||||
public class ControllerMessageContext : MessageContext, IControllerMessageContext
|
public class ControllerMessageContext : MessageContext, IControllerMessageContext
|
||||||
{
|
{
|
||||||
private Dictionary<string, object> items = new Dictionary<string, object>();
|
private readonly Dictionary<string, object> items = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1,22 +1,31 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
namespace Tapeti.Default
|
namespace Tapeti.Default
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Default ILogger implementation which does not log anything.
|
||||||
|
/// </summary>
|
||||||
public class DevNullLogger : ILogger
|
public class DevNullLogger : ILogger
|
||||||
{
|
{
|
||||||
public void Connect(TapetiConnectionParams connectionParams)
|
/// <inheritdoc />
|
||||||
|
public void Connect(TapetiConnectionParams connectionParams, bool isReconnect)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void ConnectFailed(TapetiConnectionParams connectionParams, Exception exception)
|
public void ConnectFailed(TapetiConnectionParams connectionParams, Exception exception)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectSuccess(TapetiConnectionParams connectionParams)
|
/// <inheritdoc />
|
||||||
|
public void ConnectSuccess(TapetiConnectionParams connectionParams, bool isReconnect)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandlerException(Exception e)
|
/// <inheritdoc />
|
||||||
|
public void ConsumeException(Exception exception, IMessageContext messageContext, ConsumeResult consumeResult)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,23 +3,37 @@ using Tapeti.Config;
|
|||||||
|
|
||||||
namespace Tapeti.Default
|
namespace Tapeti.Default
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation of IExceptionStrategyContext.
|
||||||
|
/// </summary>
|
||||||
public class ExceptionStrategyContext : IExceptionStrategyContext
|
public class ExceptionStrategyContext : IExceptionStrategyContext
|
||||||
{
|
{
|
||||||
internal ExceptionStrategyContext(IMessageContext messageContext, Exception exception)
|
/// <summary>
|
||||||
|
/// The ConsumeResult as set by the exception strategy. Defaults to Error.
|
||||||
|
/// </summary>
|
||||||
|
public ConsumeResult ConsumeResult { get; set; } = ConsumeResult.Error;
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IMessageContext MessageContext { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Exception Exception { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ExceptionStrategyContext(IMessageContext messageContext, Exception exception)
|
||||||
{
|
{
|
||||||
MessageContext = messageContext;
|
MessageContext = messageContext;
|
||||||
Exception = exception;
|
Exception = exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IMessageContext MessageContext { get; }
|
|
||||||
|
|
||||||
public Exception Exception { get; }
|
/// <inheritdoc />
|
||||||
|
public void SetConsumeResult(ConsumeResult consumeResult)
|
||||||
private HandlingResultBuilder handlingResult;
|
|
||||||
public HandlingResultBuilder HandlingResult
|
|
||||||
{
|
{
|
||||||
get => handlingResult ?? (handlingResult = new HandlingResultBuilder());
|
ConsumeResult = consumeResult;
|
||||||
set => handlingResult = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,16 @@
|
|||||||
|
|
||||||
namespace Tapeti.Default
|
namespace Tapeti.Default
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation of an exception strategy which marks the messages as Error.
|
||||||
|
/// </summary>
|
||||||
public class NackExceptionStrategy : IExceptionStrategy
|
public class NackExceptionStrategy : IExceptionStrategy
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public void HandleException(IExceptionStrategyContext context)
|
public void HandleException(IExceptionStrategyContext context)
|
||||||
{
|
{
|
||||||
context.HandlingResult.ConsumeResponse = ConsumeResponse.Nack;
|
context.SetConsumeResult(ConsumeResult.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,20 @@ using System.Text.RegularExpressions;
|
|||||||
|
|
||||||
namespace Tapeti.Default
|
namespace Tapeti.Default
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// IExchangeStrategy implementation which uses the first identifier in the namespace in lower case,
|
||||||
|
/// skipping the first identifier if it is 'Messaging'.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// Messaging.Service.Optional.Further.Parts will result in the exchange name 'service'.
|
||||||
|
/// </example>
|
||||||
public class NamespaceMatchExchangeStrategy : IExchangeStrategy
|
public class NamespaceMatchExchangeStrategy : IExchangeStrategy
|
||||||
{
|
{
|
||||||
// If the namespace starts with "Messaging.Service[.Optional.Further.Parts]", the exchange will be "Service".
|
|
||||||
// If no Messaging prefix is present, the first part of the namespace will be used instead.
|
|
||||||
private static readonly Regex NamespaceRegex = new Regex("^(Messaging\\.)?(?<exchange>[^\\.]+)", RegexOptions.Compiled | RegexOptions.Singleline);
|
private static readonly Regex NamespaceRegex = new Regex("^(Messaging\\.)?(?<exchange>[^\\.]+)", RegexOptions.Compiled | RegexOptions.Singleline);
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string GetExchange(Type messageType)
|
public string GetExchange(Type messageType)
|
||||||
{
|
{
|
||||||
if (messageType.Namespace == null)
|
if (messageType.Namespace == null)
|
||||||
|
@ -12,6 +12,9 @@ namespace Tapeti.Default
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class RabbitMQMessageProperties : IMessageProperties
|
public class RabbitMQMessageProperties : IMessageProperties
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to the wrapped IBasicProperties
|
||||||
|
/// </summary>
|
||||||
public IBasicProperties BasicProperties { get; }
|
public IBasicProperties BasicProperties { get; }
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,11 +4,25 @@
|
|||||||
|
|
||||||
namespace Tapeti.Default
|
namespace Tapeti.Default
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Example exception strategy which requeues all messages that result in an error.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// You probably do not want to use this strategy as-is in production code, unless
|
||||||
|
/// you are sure that all your exceptions are transient. A better way would be to
|
||||||
|
/// check for exceptions you know are transient. An even better way would be to
|
||||||
|
/// never requeue but retry transient errors internally. See the Tapeti documentation
|
||||||
|
/// for an example of this pattern:
|
||||||
|
///
|
||||||
|
/// https://tapeti.readthedocs.io/en/latest/
|
||||||
|
/// </remarks>
|
||||||
public class RequeueExceptionStrategy : IExceptionStrategy
|
public class RequeueExceptionStrategy : IExceptionStrategy
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public void HandleException(IExceptionStrategyContext context)
|
public void HandleException(IExceptionStrategyContext context)
|
||||||
{
|
{
|
||||||
context.HandlingResult.ConsumeResponse = ConsumeResponse.Requeue;
|
context.SetConsumeResult(ConsumeResult.Requeue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,13 @@ using System.Text.RegularExpressions;
|
|||||||
|
|
||||||
namespace Tapeti.Default
|
namespace Tapeti.Default
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IRoutingKeyStrategy implementation which transforms the class name into a dot-separated routing key based
|
||||||
|
/// on the casing. Accounts for acronyms. If the class name ends with 'Message' it is not included in the routing key.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// ExampleClassNameMessage will result in example.class.name
|
||||||
|
/// </example>
|
||||||
public class TypeNameRoutingKeyStrategy : IRoutingKeyStrategy
|
public class TypeNameRoutingKeyStrategy : IRoutingKeyStrategy
|
||||||
{
|
{
|
||||||
private const string SeparatorPattern = @"
|
private const string SeparatorPattern = @"
|
||||||
@ -24,12 +31,17 @@ namespace Tapeti.Default
|
|||||||
private static readonly ConcurrentDictionary<Type, string> RoutingKeyCache = new ConcurrentDictionary<Type, string>();
|
private static readonly ConcurrentDictionary<Type, string> RoutingKeyCache = new ConcurrentDictionary<Type, string>();
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string GetRoutingKey(Type messageType)
|
public string GetRoutingKey(Type messageType)
|
||||||
{
|
{
|
||||||
return RoutingKeyCache.GetOrAdd(messageType, BuildRoutingKey);
|
return RoutingKeyCache.GetOrAdd(messageType, BuildRoutingKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actual implementation of GetRoutingKey, called only when the type has not been cached yet.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageType"></param>
|
||||||
protected virtual string BuildRoutingKey(Type messageType)
|
protected virtual string BuildRoutingKey(Type messageType)
|
||||||
{
|
{
|
||||||
// Split PascalCase into dot-separated parts. If the class name ends in "Message" leave that out.
|
// Split PascalCase into dot-separated parts. If the class name ends in "Message" leave that out.
|
||||||
@ -43,6 +55,7 @@ namespace Tapeti.Default
|
|||||||
return string.Join(".", words.Select(s => s.ToLower()));
|
return string.Join(".", words.Select(s => s.ToLower()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static List<string> SplitPascalCase(string value)
|
private static List<string> SplitPascalCase(string value)
|
||||||
{
|
{
|
||||||
var split = SeparatorRegex.Split(value);
|
var split = SeparatorRegex.Split(value);
|
||||||
|
@ -2,8 +2,13 @@
|
|||||||
|
|
||||||
namespace Tapeti.Exceptions
|
namespace Tapeti.Exceptions
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a message is nacked by the message bus.
|
||||||
|
/// </summary>
|
||||||
public class NackException : Exception
|
public class NackException : Exception
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public NackException(string message) : base(message) { }
|
public NackException(string message) : base(message) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,13 @@
|
|||||||
|
|
||||||
namespace Tapeti.Exceptions
|
namespace Tapeti.Exceptions
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a mandatory message has no route.
|
||||||
|
/// </summary>
|
||||||
public class NoRouteException : Exception
|
public class NoRouteException : Exception
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public NoRouteException(string message) : base(message) { }
|
public NoRouteException(string message) : base(message) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
|
||||||
|
|
||||||
namespace Tapeti
|
|
||||||
{
|
|
||||||
public class HandlingResult
|
|
||||||
{
|
|
||||||
public HandlingResult()
|
|
||||||
{
|
|
||||||
ConsumeResponse = ConsumeResponse.Nack;
|
|
||||||
MessageAction = MessageAction.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines which response will be given to the message bus from where the message originates.
|
|
||||||
/// </summary>
|
|
||||||
public ConsumeResponse ConsumeResponse { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers which action the Exception strategy has taken or will take to handle the error condition
|
|
||||||
/// on the message. This is important to know for cleanup handlers registered by middleware.
|
|
||||||
/// </summary>
|
|
||||||
public MessageAction MessageAction { get; internal set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HandlingResultBuilder
|
|
||||||
{
|
|
||||||
private static readonly HandlingResult Default = new HandlingResult();
|
|
||||||
|
|
||||||
private HandlingResult data = Default;
|
|
||||||
|
|
||||||
public ConsumeResponse ConsumeResponse {
|
|
||||||
get => data.ConsumeResponse;
|
|
||||||
set => GetWritableData().ConsumeResponse = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageAction MessageAction
|
|
||||||
{
|
|
||||||
get => data.MessageAction;
|
|
||||||
set => GetWritableData().MessageAction = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HandlingResult ToHandlingResult()
|
|
||||||
{
|
|
||||||
if (data == Default)
|
|
||||||
{
|
|
||||||
return new HandlingResult();
|
|
||||||
}
|
|
||||||
var result = GetWritableData();
|
|
||||||
data = Default;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HandlingResult GetWritableData()
|
|
||||||
{
|
|
||||||
if (data == Default)
|
|
||||||
{
|
|
||||||
data = new HandlingResult();
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,9 +2,17 @@
|
|||||||
|
|
||||||
namespace Tapeti.Helpers
|
namespace Tapeti.Helpers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for console applications.
|
||||||
|
/// </summary>
|
||||||
public static class ConsoleHelper
|
public static class ConsoleHelper
|
||||||
{
|
{
|
||||||
// Source: http://stackoverflow.com/questions/6408588/how-to-tell-if-there-is-a-console
|
/// <summary>
|
||||||
|
/// Determines if the application is running in a console.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Source: http://stackoverflow.com/questions/6408588/how-to-tell-if-there-is-a-console
|
||||||
|
/// </remarks>
|
||||||
public static bool IsAvailable()
|
public static bool IsAvailable()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -4,8 +4,18 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Tapeti.Helpers
|
namespace Tapeti.Helpers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for executing the middleware pattern.
|
||||||
|
/// </summary>
|
||||||
public static class MiddlewareHelper
|
public static class MiddlewareHelper
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the chain of middleware synchronously, starting with the last item in the list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="middleware">The list of middleware to run</param>
|
||||||
|
/// <param name="handle">Receives the middleware which should be called and a reference to the action which will call the next. Pass this on to the middleware.</param>
|
||||||
|
/// <param name="lastHandler">The action to execute when the innermost middleware calls next.</param>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
public static void Go<T>(IReadOnlyList<T> middleware, Action<T, Action> handle, Action lastHandler)
|
public static void Go<T>(IReadOnlyList<T> middleware, Action<T, Action> handle, Action lastHandler)
|
||||||
{
|
{
|
||||||
var handlerIndex = middleware?.Count - 1 ?? -1;
|
var handlerIndex = middleware?.Count - 1 ?? -1;
|
||||||
@ -28,6 +38,13 @@ namespace Tapeti.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes the chain of middleware asynchronously, starting with the last item in the list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="middleware">The list of middleware to run</param>
|
||||||
|
/// <param name="handle">Receives the middleware which should be called and a reference to the action which will call the next. Pass this on to the middleware.</param>
|
||||||
|
/// <param name="lastHandler">The action to execute when the innermost middleware calls next.</param>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
public static async Task GoAsync<T>(IReadOnlyList<T> middleware, Func<T, Func<Task>, Task> handle, Func<Task> lastHandler)
|
public static async Task GoAsync<T>(IReadOnlyList<T> middleware, Func<T, Func<Task>, Task> handle, Func<Task> lastHandler)
|
||||||
{
|
{
|
||||||
var handlerIndex = middleware?.Count - 1 ?? -1;
|
var handlerIndex = middleware?.Count - 1 ?? -1;
|
||||||
|
@ -3,8 +3,18 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Tapeti.Helpers
|
namespace Tapeti.Helpers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper methods for working with synchronous and asynchronous versions of methods.
|
||||||
|
/// </summary>
|
||||||
public static class TaskTypeHelper
|
public static class TaskTypeHelper
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the given type matches the predicate, taking Task types into account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="predicate"></param>
|
||||||
|
/// <param name="isTaskOf"></param>
|
||||||
|
/// <param name="actualType"></param>
|
||||||
public static bool IsTypeOrTaskOf(this Type type, Func<Type, bool> predicate, out bool isTaskOf, out Type actualType)
|
public static bool IsTypeOrTaskOf(this Type type, Func<Type, bool> predicate, out bool isTaskOf, out Type actualType)
|
||||||
{
|
{
|
||||||
if (type == typeof(Task))
|
if (type == typeof(Task))
|
||||||
@ -32,11 +42,24 @@ namespace Tapeti.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the given type matches the predicate, taking Task types into account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="predicate"></param>
|
||||||
|
/// <param name="isTaskOf"></param>
|
||||||
public static bool IsTypeOrTaskOf(this Type type, Func<Type, bool> predicate, out bool isTaskOf)
|
public static bool IsTypeOrTaskOf(this Type type, Func<Type, bool> predicate, out bool isTaskOf)
|
||||||
{
|
{
|
||||||
return IsTypeOrTaskOf(type, predicate, out isTaskOf, out _);
|
return IsTypeOrTaskOf(type, predicate, out isTaskOf, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the given type matches the compareTo type, taking Task types into account.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <param name="compareTo"></param>
|
||||||
|
/// <param name="isTaskOf"></param>
|
||||||
public static bool IsTypeOrTaskOf(this Type type, Type compareTo, out bool isTaskOf)
|
public static bool IsTypeOrTaskOf(this Type type, Type compareTo, out bool isTaskOf)
|
||||||
{
|
{
|
||||||
return IsTypeOrTaskOf(type, t => t == compareTo, out isTaskOf);
|
return IsTypeOrTaskOf(type, t => t == compareTo, out isTaskOf);
|
||||||
|
@ -16,6 +16,6 @@ namespace Tapeti
|
|||||||
/// <param name="properties">Metadata included in the message</param>
|
/// <param name="properties">Metadata included in the message</param>
|
||||||
/// <param name="body">The raw body of the message</param>
|
/// <param name="body">The raw body of the message</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<ConsumeResponse> Consume(string exchange, string routingKey, IMessageProperties properties, byte[] body);
|
Task<ConsumeResult> Consume(string exchange, string routingKey, IMessageProperties properties, byte[] body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,24 +7,79 @@ namespace Tapeti
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDependencyResolver
|
public interface IDependencyResolver
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Resolve an instance of T
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type to instantiate</typeparam>
|
||||||
|
/// <returns>A new or singleton instance, depending on the registration</returns>
|
||||||
T Resolve<T>() where T : class;
|
T Resolve<T>() where T : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolve an instance of T
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type to instantiate</param>
|
||||||
|
/// <returns>A new or singleton instance, depending on the registration</returns>
|
||||||
object Resolve(Type type);
|
object Resolve(Type type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows registering controller classes into the IoC container. Also registers default implementations,
|
/// Allows registering controller classes into the IoC container. Also registers default implementations,
|
||||||
/// so that the calling application may override these.
|
/// so that the calling application may override these.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// All implementations of IDependencyResolver should implement IDependencyContainer as well,
|
||||||
|
/// otherwise all registrations of Tapeti components will have to be done manually by the application.
|
||||||
|
/// </remarks>
|
||||||
public interface IDependencyContainer : IDependencyResolver
|
public interface IDependencyContainer : IDependencyResolver
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a default implementation in the IoC container. If an alternative implementation
|
||||||
|
/// was registered before, it is not replaced.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TService"></typeparam>
|
||||||
|
/// <typeparam name="TImplementation"></typeparam>
|
||||||
void RegisterDefault<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
void RegisterDefault<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a default implementation in the IoC container. If an alternative implementation
|
||||||
|
/// was registered before, it is not replaced.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factory"></param>
|
||||||
|
/// <typeparam name="TService"></typeparam>
|
||||||
void RegisterDefault<TService>(Func<TService> factory) where TService : class;
|
void RegisterDefault<TService>(Func<TService> factory) where TService : class;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a default singleton implementation in the IoC container. If an alternative implementation
|
||||||
|
/// was registered before, it is not replaced.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TService"></typeparam>
|
||||||
|
/// <typeparam name="TImplementation"></typeparam>
|
||||||
void RegisterDefaultSingleton<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
void RegisterDefaultSingleton<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a default singleton implementation in the IoC container. If an alternative implementation
|
||||||
|
/// was registered before, it is not replaced.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance"></param>
|
||||||
|
/// <typeparam name="TService"></typeparam>
|
||||||
void RegisterDefaultSingleton<TService>(TService instance) where TService : class;
|
void RegisterDefaultSingleton<TService>(TService instance) where TService : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a default singleton implementation in the IoC container. If an alternative implementation
|
||||||
|
/// was registered before, it is not replaced.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factory"></param>
|
||||||
|
/// <typeparam name="TService"></typeparam>
|
||||||
void RegisterDefaultSingleton<TService>(Func<TService> factory) where TService : class;
|
void RegisterDefaultSingleton<TService>(Func<TService> factory) where TService : class;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a concrete controller class in the IoC container.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
void RegisterController(Type type);
|
void RegisterController(Type type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
namespace Tapeti
|
namespace Tapeti
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called when an exception occurs while handling a message. Determines how it should be handled.
|
||||||
|
/// </summary>
|
||||||
public interface IExceptionStrategy
|
public interface IExceptionStrategy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when an exception occurs while handling a message.
|
/// Called when an exception occurs while handling a message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The exception strategy context containing the necessary data including the message context and the thrown exception.
|
/// <param name="context">The exception strategy context containing the necessary data including the message context and the thrown exception.
|
||||||
/// Also the response to the message can be set.
|
/// Also proivdes methods for the exception strategy to indicate how the message should be handled.</param>
|
||||||
/// If there is any other handling of the message than the expected default than HandlingResult.MessageFutureAction must be set accordingly. </param>
|
|
||||||
void HandleException(IExceptionStrategyContext context);
|
void HandleException(IExceptionStrategyContext context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,16 @@
|
|||||||
|
|
||||||
namespace Tapeti
|
namespace Tapeti
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Translates message classes into their target exchange.
|
||||||
|
/// </summary>
|
||||||
public interface IExchangeStrategy
|
public interface IExchangeStrategy
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the exchange belonging to the given message class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
string GetExchange(Type messageType);
|
string GetExchange(Type messageType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,46 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Tapeti.Config;
|
||||||
|
|
||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
|
|
||||||
namespace Tapeti
|
namespace Tapeti
|
||||||
{
|
{
|
||||||
// This interface is deliberately specific and typed to allow for structured logging (e.g. Serilog)
|
/// <summary>
|
||||||
// instead of only string-based logging without control over the output.
|
/// Handles the logging of various events in Tapeti
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This interface is deliberately specific and typed to allow for structured logging (e.g. Serilog)
|
||||||
|
/// instead of only string-based logging without control over the output.
|
||||||
|
/// </remarks>
|
||||||
public interface ILogger
|
public interface ILogger
|
||||||
{
|
{
|
||||||
void Connect(TapetiConnectionParams connectionParams);
|
/// <summary>
|
||||||
|
/// Called before a connection to RabbitMQ is attempted.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionParams"></param>
|
||||||
|
/// <param name="isReconnect">Indicates whether this is the initial connection or a reconnect</param>
|
||||||
|
void Connect(TapetiConnectionParams connectionParams, bool isReconnect);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the connection has failed or is lost.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionParams"></param>
|
||||||
|
/// <param name="exception"></param>
|
||||||
void ConnectFailed(TapetiConnectionParams connectionParams, Exception exception);
|
void ConnectFailed(TapetiConnectionParams connectionParams, Exception exception);
|
||||||
void ConnectSuccess(TapetiConnectionParams connectionParams);
|
|
||||||
void HandlerException(Exception e);
|
/// <summary>
|
||||||
|
/// Called when a connection to RabbitMQ has been succesfully established.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionParams"></param>
|
||||||
|
/// <param name="isReconnect">Indicates whether this is the initial connection or a reconnect</param>
|
||||||
|
void ConnectSuccess(TapetiConnectionParams connectionParams, bool isReconnect);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when an exception occurs in a consumer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exception"></param>
|
||||||
|
/// <param name="messageContext"></param>
|
||||||
|
/// <param name="consumeResult">Indicates the action taken by the exception handler</param>
|
||||||
|
void ConsumeException(Exception exception, IMessageContext messageContext, ConsumeResult consumeResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,16 @@
|
|||||||
|
|
||||||
namespace Tapeti
|
namespace Tapeti
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Translates message classes into routing keys.
|
||||||
|
/// </summary>
|
||||||
public interface IRoutingKeyStrategy
|
public interface IRoutingKeyStrategy
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the routing key for the given message class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageType"></param>
|
||||||
|
/// <returns></returns>
|
||||||
string GetRoutingKey(Type messageType);
|
string GetRoutingKey(Type messageType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
Tapeti/MessageAction.cs
Normal file
29
Tapeti/MessageAction.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
|
||||||
|
namespace Tapeti
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates how the message was handled.
|
||||||
|
/// </summary>
|
||||||
|
public enum MessageAction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The message was handled succesfully.
|
||||||
|
/// </summary>
|
||||||
|
Success,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// There was an error while processing the message.
|
||||||
|
/// </summary>
|
||||||
|
Error,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The message has been stored for republishing and will be delivered again
|
||||||
|
/// even if the current messages has been Acked or Nacked.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This option is for compatibility with external scheduler services that do not immediately requeue a message.
|
||||||
|
/// </remarks>
|
||||||
|
ExternalRetry
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
|
||||||
|
|
||||||
namespace Tapeti
|
|
||||||
{
|
|
||||||
public enum MessageAction
|
|
||||||
{
|
|
||||||
None = 1,
|
|
||||||
ErrorLog = 2,
|
|
||||||
Retry = 3,
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,17 +4,35 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Tapeti
|
namespace Tapeti
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Implementation of TapetiConnectionParams which reads the values from the AppSettings.
|
||||||
|
/// </summary>
|
||||||
|
/// <list type="table">
|
||||||
|
/// <listheader>
|
||||||
|
/// <description>AppSettings keys</description>
|
||||||
|
/// </listheader>
|
||||||
|
/// <item><description>rabbitmq:hostname</description></item>
|
||||||
|
/// <item><description>rabbitmq:port</description></item>
|
||||||
|
/// <item><description>rabbitmq:virtualhost</description></item>
|
||||||
|
/// <item><description>rabbitmq:username</description></item>
|
||||||
|
/// <item><description>rabbitmq:password</description></item>
|
||||||
|
/// <item><description>rabbitmq:prefetchcount</description></item>
|
||||||
|
/// </list>
|
||||||
public class TapetiAppSettingsConnectionParams : TapetiConnectionParams
|
public class TapetiAppSettingsConnectionParams : TapetiConnectionParams
|
||||||
{
|
{
|
||||||
public const string DefaultPrefix = "rabbitmq:";
|
private const string DefaultPrefix = "rabbitmq:";
|
||||||
public const string KeyHostname = "hostname";
|
private const string KeyHostname = "hostname";
|
||||||
public const string KeyPort = "port";
|
private const string KeyPort = "port";
|
||||||
public const string KeyVirtualHost = "virtualhost";
|
private const string KeyVirtualHost = "virtualhost";
|
||||||
public const string KeyUsername = "username";
|
private const string KeyUsername = "username";
|
||||||
public const string KeyPassword = "password";
|
private const string KeyPassword = "password";
|
||||||
public const string KeyPrefetchCount = "prefetchcount";
|
private const string KeyPrefetchCount = "prefetchcount";
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary></summary>
|
||||||
|
/// <param name="prefix">The prefix to apply to the keys. Defaults to "rabbitmq:"</param>
|
||||||
public TapetiAppSettingsConnectionParams(string prefix = DefaultPrefix)
|
public TapetiAppSettingsConnectionParams(string prefix = DefaultPrefix)
|
||||||
{
|
{
|
||||||
var keys = ConfigurationManager.AppSettings.AllKeys;
|
var keys = ConfigurationManager.AppSettings.AllKeys;
|
||||||
|
Loading…
Reference in New Issue
Block a user