@@ -16,8 +16,8 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" /> | |||||
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" /> | <PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -16,9 +16,27 @@ | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.1" /> | |||||
<Compile Remove="x64\**" /> | |||||
<Compile Remove="x86\**" /> | |||||
<Content Remove="x64\**" /> | |||||
<Content Remove="x86\**" /> | |||||
<EmbeddedResource Remove="x64\**" /> | |||||
<EmbeddedResource Remove="x86\**" /> | |||||
<None Remove="x64\**" /> | |||||
<None Remove="x86\**" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Content Remove="C:\Users\yangxiaodong\.nuget\packages\rdkafka.internal.librdkafka\0.9.2-ci-28\build\..\runtimes\win7-x64\native\librdkafka.dll" /> | |||||
<Content Remove="C:\Users\yangxiaodong\.nuget\packages\rdkafka.internal.librdkafka\0.9.2-ci-28\build\..\runtimes\win7-x64\native\zlib.dll" /> | |||||
<Content Remove="C:\Users\yangxiaodong\.nuget\packages\rdkafka.internal.librdkafka\0.9.2-ci-28\build\..\runtimes\win7-x86\native\librdkafka.dll" /> | |||||
<Content Remove="C:\Users\yangxiaodong\.nuget\packages\rdkafka.internal.librdkafka\0.9.2-ci-28\build\..\runtimes\win7-x86\native\zlib.dll" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.2" /> | |||||
<PackageReference Include="RdKafka" Version="0.9.2-ci-189" /> | <PackageReference Include="RdKafka" Version="0.9.2-ci-189" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -13,6 +13,8 @@ namespace Cap.Consistency.Abstractions | |||||
} | } | ||||
public ConsumerExecutorDescriptor ConsumerDescriptor { get; set; } | public ConsumerExecutorDescriptor ConsumerDescriptor { get; set; } | ||||
} | } | ||||
} | } |
@@ -8,6 +8,7 @@ namespace Cap.Consistency.Abstractions | |||||
{ | { | ||||
public ConsumerInvokerContext(ConsumerContext consumerContext) { | public ConsumerInvokerContext(ConsumerContext consumerContext) { | ||||
ConsumerContext = consumerContext ?? throw new ArgumentNullException(nameof(consumerContext)); | ConsumerContext = consumerContext ?? throw new ArgumentNullException(nameof(consumerContext)); | ||||
} | } | ||||
public ConsumerContext ConsumerContext { get; set; } | public ConsumerContext ConsumerContext { get; set; } | ||||
@@ -1,10 +0,0 @@ | |||||
namespace Cap.Consistency | |||||
{ | |||||
public class BrokerOptions | |||||
{ | |||||
public string HostName { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,24 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using Cap.Consistency.Routing; | |||||
namespace Cap.Consistency.Builder | |||||
{ | |||||
public class ConsistencyMiddleware | |||||
{ | |||||
private readonly ITopicRoute _router; | |||||
public ConsistencyMiddleware(ITopicRoute router) { | |||||
_router = router; | |||||
} | |||||
public async Task Invoke() { | |||||
var context = new TopicRouteContext(); | |||||
context.Routes.Add(_router); | |||||
await _router.RouteAsync(context); | |||||
} | |||||
} | |||||
} |
@@ -1,31 +0,0 @@ | |||||
using System; | |||||
using Cap.Consistency; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
// ReSharper disable once CheckNamespace | |||||
namespace Microsoft.AspNetCore.Builder | |||||
{ | |||||
/// <summary> | |||||
/// Consistence extensions for <see cref="IApplicationBuilder"/> | |||||
/// </summary> | |||||
public static class BuilderExtensions | |||||
{ | |||||
/// <summary> | |||||
/// Enables Consistence for the current application | |||||
/// </summary> | |||||
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param> | |||||
/// <returns>The <see cref="IApplicationBuilder"/> instance this method extends.</returns> | |||||
public static IApplicationBuilder UseConsistency(this IApplicationBuilder app) { | |||||
if (app == null) { | |||||
throw new ArgumentNullException(nameof(app)); | |||||
} | |||||
var marker = app.ApplicationServices.GetService<ConsistencyMarkerService>(); | |||||
if (marker == null) { | |||||
throw new InvalidOperationException("Add Consistency must be called on the service collection."); | |||||
} | |||||
return app; | |||||
} | |||||
} | |||||
} |
@@ -13,11 +13,11 @@ | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Confluent.Kafka" Version="0.9.5" /> | <PackageReference Include="Confluent.Kafka" Version="0.9.5" /> | ||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.0" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" /> | |||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> | <PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -1,81 +0,0 @@ | |||||
using System; | |||||
using System.Reflection; | |||||
using System.Collections.Concurrent; | |||||
using System.Linq; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using System.Collections.Generic; | |||||
namespace Cap.Consistency | |||||
{ | |||||
/// <summary> | |||||
/// Helper functions for configuring consistency services. | |||||
/// </summary> | |||||
public class ConsistencyBuilder | |||||
{ | |||||
/// <summary> | |||||
/// Creates a new instance of <see cref="ConsistencyBuilder"/>. | |||||
/// </summary> | |||||
/// <param name="message">The <see cref="Type"/> to use for the message.</param> | |||||
/// <param name="service">The <see cref="IServiceCollection"/> to attach to.</param> | |||||
public ConsistencyBuilder(Type message, IServiceCollection service) { | |||||
MessageType = message; | |||||
Services = service; | |||||
} | |||||
/// <summary> | |||||
/// Gets the <see cref="IServiceCollection"/> services are attached to. | |||||
/// </summary> | |||||
/// <value> | |||||
/// The <see cref="IServiceCollection"/> services are attached to. | |||||
/// </value> | |||||
public IServiceCollection Services { get; private set; } | |||||
/// <summary> | |||||
/// Gets the <see cref="Type"/> used for messages. | |||||
/// </summary> | |||||
/// <value> | |||||
/// The <see cref="Type"/> used for messages. | |||||
/// </value> | |||||
public Type MessageType { get; private set; } | |||||
/// <summary> | |||||
/// Adds a <see cref="IConsistencyMessageStore{TMessage}"/> for the <seealso cref="MessageType"/>. | |||||
/// </summary> | |||||
/// <typeparam name="T">The role type held in the store.</typeparam> | |||||
/// <returns>The current <see cref="ConsistencyBuilder"/> instance.</returns> | |||||
public virtual ConsistencyBuilder AddMessageStore<T>() where T : class { | |||||
return AddScoped(typeof(IConsistencyMessageStore<>).MakeGenericType(MessageType), typeof(T)); | |||||
} | |||||
public virtual ConsistencyBuilder AddMessageMethodTable() { | |||||
var provider = Services.BuildServiceProvider(); | |||||
var finder = provider.GetRequiredService<QMessageFinder>(); | |||||
finder.GetQMessageMethods(Services); | |||||
return null; | |||||
// Services.AddSingleton(serviceType, concreteType); | |||||
// return Add(typeof(IConsistencyMessageStore<>).MakeGenericType(MessageType), typeof(T)); | |||||
} | |||||
/// <summary> | |||||
/// Adds a <see cref="ConsistencyMessageManager{TUser}"/> for the <seealso cref="MessageType"/>. | |||||
/// </summary> | |||||
/// <typeparam name="TMessageManager">The type of the message manager to add.</typeparam> | |||||
/// <returns>The current <see cref="ConsistencyBuilder"/> instance.</returns> | |||||
public virtual ConsistencyBuilder AddConsistencyMessageManager<TMessageManager>() where TMessageManager : class { | |||||
var messageManagerType = typeof(ConsistencyMessageManager<>).MakeGenericType(MessageType); | |||||
var customType = typeof(TMessageManager); | |||||
if (messageManagerType == customType || | |||||
!messageManagerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo())) { | |||||
throw new InvalidOperationException($"Type {customType.Name} must be derive from ConsistencyMessageManager<{MessageType.Name}>"); | |||||
} | |||||
Services.AddScoped(customType, services => services.GetRequiredService(messageManagerType)); | |||||
return AddScoped(messageManagerType, customType); | |||||
} | |||||
private ConsistencyBuilder AddScoped(Type serviceType, Type concreteType) { | |||||
Services.AddScoped(serviceType, concreteType); | |||||
return this; | |||||
} | |||||
} | |||||
} |
@@ -1,7 +0,0 @@ | |||||
namespace Cap.Consistency | |||||
{ | |||||
/// <summary> | |||||
/// Used to verify Consistency service was called on a ServiceCollection | |||||
/// </summary> | |||||
public class ConsistencyMarkerService { } | |||||
} |
@@ -1,147 +0,0 @@ | |||||
using System; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.AspNetCore.Http; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Logging; | |||||
namespace Cap.Consistency | |||||
{ | |||||
/// <summary> | |||||
/// Provides the APIs for managing message in a persistence store. | |||||
/// </summary> | |||||
/// <typeparam name="TMessage">The type encapsulating a message.</typeparam> | |||||
public class ConsistencyMessageManager<TMessage> : IDisposable where TMessage : class | |||||
{ | |||||
private bool _disposed; | |||||
private readonly HttpContext _context; | |||||
private CancellationToken CancellationToken => _context?.RequestAborted ?? CancellationToken.None; | |||||
/// <summary> | |||||
/// Constructs a new instance of <see cref="ConsistencyMessageManager{TMessage}"/>. | |||||
/// </summary> | |||||
/// <param name="store">The persistence store the manager will operate over.</param> | |||||
/// <param name="services">The <see cref="IServiceProvider"/> used to resolve services.</param> | |||||
/// <param name="logger">The logger used to log messages, warnings and errors.</param> | |||||
public ConsistencyMessageManager(IConsistencyMessageStore<TMessage> store, | |||||
IServiceProvider services, | |||||
ILogger<ConsistencyMessageManager<TMessage>> logger) { | |||||
if (store == null) { | |||||
throw new ArgumentNullException(nameof(store)); | |||||
} | |||||
Store = store; | |||||
Logger = logger; | |||||
if (services != null) { | |||||
_context = services.GetService<IHttpContextAccessor>()?.HttpContext; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Gets or sets the persistence store the manager operates over. | |||||
/// </summary> | |||||
/// <value>The persistence store the manager operates over.</value> | |||||
protected internal IConsistencyMessageStore<TMessage> Store { get; set; } | |||||
/// <summary> | |||||
/// Gets the <see cref="ILogger"/> used to log messages from the manager. | |||||
/// </summary> | |||||
/// <value> | |||||
/// The <see cref="ILogger"/> used to log messages from the manager. | |||||
/// </value> | |||||
protected internal virtual ILogger Logger { get; set; } | |||||
/// <summary> | |||||
/// Creates the specified <paramref name="message"/> in the backing store. | |||||
/// </summary> | |||||
/// <param name="message">The message to create.</param> | |||||
/// <returns> | |||||
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="OperateResult"/> | |||||
/// of the operation. | |||||
/// </returns> | |||||
public virtual Task<OperateResult> CreateAsync(TMessage message) { | |||||
ThrowIfDisposed(); | |||||
//todo: validation message fileds is correct | |||||
return Store.CreateAsync(message, CancellationToken); | |||||
} | |||||
/// <summary> | |||||
/// Updates the specified <paramref name="message"/> in the backing store. | |||||
/// </summary> | |||||
/// <param name="message">The message to update.</param> | |||||
/// <returns> | |||||
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="OperateResult"/> | |||||
/// of the operation. | |||||
/// </returns> | |||||
public virtual Task<OperateResult> UpdateAsync(TMessage message) { | |||||
ThrowIfDisposed(); | |||||
//todo: validation message fileds is correct | |||||
return Store.UpdateAsync(message, CancellationToken); | |||||
} | |||||
/// <summary> | |||||
/// Deletes the specified <paramref name="message"/> in the backing store. | |||||
/// </summary> | |||||
/// <param name="message">The message to delete.</param> | |||||
/// <returns> | |||||
/// The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="OperateResult"/> | |||||
/// of the operation. | |||||
/// </returns> | |||||
public virtual Task<OperateResult> DeleteAsync(TMessage message) { | |||||
ThrowIfDisposed(); | |||||
if (message == null) { | |||||
throw new ArgumentNullException(nameof(message)); | |||||
} | |||||
return Store.DeleteAsync(message, CancellationToken); | |||||
} | |||||
/// <summary> | |||||
/// Finds and returns a message, if any, who has the specified <paramref name="messageId"/>. | |||||
/// </summary> | |||||
/// <param name="messageId">The message ID to search for.</param> | |||||
/// <returns> | |||||
/// The <see cref="Task"/> that represents the asynchronous operation, containing the user matching the specified <paramref name="messageId"/> if it exists. | |||||
/// </returns> | |||||
public virtual Task<TMessage> FindByIdAsync(string messageId) { | |||||
ThrowIfDisposed(); | |||||
return Store.FindByIdAsync(messageId, CancellationToken); | |||||
} | |||||
/// <summary> | |||||
/// Gets the message identifier for the specified <paramref name="message"/>. | |||||
/// </summary> | |||||
/// <param name="message">The message whose identifier should be retrieved.</param> | |||||
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the identifier for the specified <paramref name="message"/>.</returns> | |||||
public virtual async Task<string> GetMessageIdAsync(TMessage message) { | |||||
ThrowIfDisposed(); | |||||
return await Store.GetMessageIdAsync(message, CancellationToken); | |||||
} | |||||
public void Dispose() { | |||||
Dispose(true); | |||||
GC.SuppressFinalize(this); | |||||
} | |||||
/// <summary> | |||||
/// Releases the unmanaged resources used by the message manager and optionally releases the managed resources. | |||||
/// </summary> | |||||
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> | |||||
protected virtual void Dispose(bool disposing) { | |||||
if (disposing && !_disposed) { | |||||
Store.Dispose(); | |||||
_disposed = true; | |||||
} | |||||
} | |||||
protected void ThrowIfDisposed() { | |||||
if (_disposed) { | |||||
throw new ObjectDisposedException(GetType().Name); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -1,26 +0,0 @@ | |||||
using Cap.Consistency; | |||||
namespace Microsoft.AspNetCore.Builder | |||||
{ | |||||
/// <summary> | |||||
/// Represents all the options you can use to configure the system. | |||||
/// </summary> | |||||
public class ConsistencyOptions | |||||
{ | |||||
/// <summary> | |||||
/// Gets or sets the <see cref="BrokerOptions"/> for the consistency system. | |||||
/// </summary> | |||||
public BrokerOptions Broker { get; set; } = new BrokerOptions(); | |||||
public long MaxPendingEventNumber { get; set; } | |||||
public int MaxPendingEventNumber32 { | |||||
get { | |||||
if (this.MaxPendingEventNumber < int.MaxValue) { | |||||
return (int)this.MaxPendingEventNumber; | |||||
} | |||||
return int.MaxValue; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -4,7 +4,7 @@ using System.Text; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Cap.Consistency.Abstractions; | using Cap.Consistency.Abstractions; | ||||
using Cap.Consistency.Infrastructure; | using Cap.Consistency.Infrastructure; | ||||
using Cap.Consistency.Route; | |||||
using Cap.Consistency.Routing; | |||||
using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||
namespace Cap.Consistency.Consumer | namespace Cap.Consistency.Consumer | ||||
@@ -12,6 +12,7 @@ namespace Cap.Consistency.Consumer | |||||
public class ConsumerHandler : IConsumerHandler | public class ConsumerHandler : IConsumerHandler | ||||
{ | { | ||||
private readonly IServiceProvider _serviceProvider; | |||||
private readonly IConsumerInvokerFactory _consumerInvokerFactory; | private readonly IConsumerInvokerFactory _consumerInvokerFactory; | ||||
private readonly IConsumerExcutorSelector _selector; | private readonly IConsumerExcutorSelector _selector; | ||||
private readonly ILoggerFactory _loggerFactory; | private readonly ILoggerFactory _loggerFactory; | ||||
@@ -19,30 +20,35 @@ namespace Cap.Consistency.Consumer | |||||
public ConsumerHandler( | public ConsumerHandler( | ||||
IServiceProvider serviceProvider, | |||||
IConsumerInvokerFactory consumerInvokerFactory, | IConsumerInvokerFactory consumerInvokerFactory, | ||||
IConsumerExcutorSelector selector, | IConsumerExcutorSelector selector, | ||||
ILoggerFactory loggerFactory) { | ILoggerFactory loggerFactory) { | ||||
_serviceProvider = serviceProvider; | |||||
_consumerInvokerFactory = consumerInvokerFactory; | _consumerInvokerFactory = consumerInvokerFactory; | ||||
_loggerFactory = loggerFactory; | _loggerFactory = loggerFactory; | ||||
_selector = selector; | _selector = selector; | ||||
_logger = loggerFactory.CreateLogger<ConsumerHandler>(); | _logger = loggerFactory.CreateLogger<ConsumerHandler>(); | ||||
} | } | ||||
public Task Start(TopicRouteContext context) { | |||||
public Task RouteAsync(TopicRouteContext context) { | |||||
if (context == null) { | if (context == null) { | ||||
throw new ArgumentNullException(nameof(context)); | throw new ArgumentNullException(nameof(context)); | ||||
} | } | ||||
context.ServiceProvider = _serviceProvider; | |||||
var matchs = _selector.SelectCandidates(context); | var matchs = _selector.SelectCandidates(context); | ||||
if (matchs == null || matchs.Count==0) { | |||||
if (matchs == null || matchs.Count == 0) { | |||||
_logger.LogInformation("can not be fond topic route"); | _logger.LogInformation("can not be fond topic route"); | ||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
var executeDescriptor = _selector.SelectBestCandidate(context, matchs); | var executeDescriptor = _selector.SelectBestCandidate(context, matchs); | ||||
context.Handler = c => { | context.Handler = c => { | ||||
var consumerContext = new ConsumerContext(executeDescriptor); | var consumerContext = new ConsumerContext(executeDescriptor); | ||||
@@ -52,17 +58,8 @@ namespace Cap.Consistency.Consumer | |||||
return invoker.InvokeAsync(); | return invoker.InvokeAsync(); | ||||
}; | }; | ||||
return Task.CompletedTask; | return Task.CompletedTask; | ||||
} | } | ||||
public void Start(IEnumerable<IConsumerService> consumers) { | |||||
throw new NotImplementedException(); | |||||
} | |||||
public void Stop() { | |||||
throw new NotImplementedException(); | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,15 +1,12 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Text; | using System.Text; | ||||
using Cap.Consistency.Routing; | |||||
namespace Cap.Consistency.Consumer | namespace Cap.Consistency.Consumer | ||||
{ | { | ||||
public interface IConsumerHandler | |||||
public interface IConsumerHandler : ITopicRoute | |||||
{ | { | ||||
void Start(IEnumerable<IConsumerService> consumers); | |||||
void Stop(); | |||||
} | } | ||||
} | } |
@@ -17,7 +17,6 @@ namespace Cap.Consistency.Consumer.Kafka | |||||
} | } | ||||
public void Start(TopicRouteContext routeContext ) { | public void Start(TopicRouteContext routeContext ) { | ||||
string brokerList = null;// args[0]; | string brokerList = null;// args[0]; | ||||
@@ -1,5 +1,6 @@ | |||||
using System; | using System; | ||||
using Cap.Consistency; | using Cap.Consistency; | ||||
using Cap.Consistency.Routing; | |||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
// ReSharper disable once CheckNamespace | // ReSharper disable once CheckNamespace | ||||
@@ -27,5 +28,25 @@ namespace Microsoft.AspNetCore.Builder | |||||
return app; | return app; | ||||
} | } | ||||
public static IApplicationBuilder UserRouter(this IApplicationBuilder builder, ITopicRoute router) { | |||||
if (builder == null) { | |||||
throw new ArgumentNullException(nameof(builder)); | |||||
} | |||||
if (router == null) { | |||||
throw new ArgumentNullException(nameof(router)); | |||||
} | |||||
var marker = builder.ApplicationServices.GetService<ConsistencyMarkerService>(); | |||||
if (marker == null) { | |||||
throw new InvalidOperationException("Add Consistency must be called on the service collection."); | |||||
} | |||||
var context = new TopicRouteContext(); | |||||
} | |||||
} | } | ||||
} | } |
@@ -0,0 +1,8 @@ | |||||
| |||||
// This file is used by Code Analysis to maintain SuppressMessage | |||||
// attributes that are applied to this project. | |||||
// Project-level suppressions either have no target or are given | |||||
// a specific target and scoped to a namespace, type, member, etc. | |||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0016:Use 'throw' expression", Justification = "<Pending>", Scope = "member", Target = "~M:Cap.Consistency.Internal.ConsumerInvoker.#ctor(Microsoft.Extensions.Logging.ILogger,Cap.Consistency.Abstractions.ConsumerContext,Cap.Consistency.Internal.ObjectMethodExecutor)")] | |||||
@@ -1,55 +0,0 @@ | |||||
using System; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace Cap.Consistency | |||||
{ | |||||
/// <summary> | |||||
/// Provides an abstraction for a store which manages consistent message. | |||||
/// </summary> | |||||
/// <typeparam name="TMessage"></typeparam> | |||||
public interface IConsistencyMessageStore<TMessage> : IDisposable where TMessage : class | |||||
{ | |||||
/// <summary> | |||||
/// Finds and returns a message, if any, who has the specified <paramref name="messageId"/>. | |||||
/// </summary> | |||||
/// <param name="messageId">The message ID to search for.</param> | |||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | |||||
/// <returns> | |||||
/// The <see cref="Task"/> that represents the asynchronous operation, containing the message matching the specified <paramref name="messageId"/> if it exists. | |||||
/// </returns> | |||||
Task<TMessage> FindByIdAsync(string messageId, CancellationToken cancellationToken); | |||||
/// <summary> | |||||
/// Creates a new message in a store as an asynchronous operation. | |||||
/// </summary> | |||||
/// <param name="message">The message to create in the store.</param> | |||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | |||||
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="OperateResult"/> of the asynchronous query.</returns> | |||||
Task<OperateResult> CreateAsync(TMessage message, CancellationToken cancellationToken); | |||||
/// <summary> | |||||
/// Updates a message in a store as an asynchronous operation. | |||||
/// </summary> | |||||
/// <param name="message">The message to update in the store.</param> | |||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | |||||
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="OperateResult"/> of the asynchronous query.</returns> | |||||
Task<OperateResult> UpdateAsync(TMessage message, CancellationToken cancellationToken); | |||||
/// <summary> | |||||
/// Deletes a message from the store as an asynchronous operation. | |||||
/// </summary> | |||||
/// <param name="message">The message to delete in the store.</param> | |||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | |||||
/// <returns>A <see cref="Task{TResult}"/> that represents the <see cref="OperateResult"/> of the asynchronous query.</returns> | |||||
Task<OperateResult> DeleteAsync(TMessage message, CancellationToken cancellationToken); | |||||
/// <summary> | |||||
/// Gets the ID for a message from the store as an asynchronous operation. | |||||
/// </summary> | |||||
/// <param name="message">The message whose ID should be returned.</param> | |||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param> | |||||
/// <returns>A <see cref="Task{TResult}"/> that contains the ID of the message.</returns> | |||||
Task<string> GetMessageIdAsync(TMessage message, CancellationToken cancellationToken); | |||||
} | |||||
} |
@@ -2,7 +2,7 @@ | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Text; | using System.Text; | ||||
using Cap.Consistency.Abstractions; | using Cap.Consistency.Abstractions; | ||||
using Cap.Consistency.Route; | |||||
using Cap.Consistency.Routing; | |||||
namespace Cap.Consistency.Infrastructure | namespace Cap.Consistency.Infrastructure | ||||
{ | { | ||||
@@ -0,0 +1,139 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Reflection; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using Cap.Consistency.Abstractions; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Logging; | |||||
namespace Cap.Consistency.Internal | |||||
{ | |||||
public class ConsumerInvoker : IConsumerInvoker | |||||
{ | |||||
protected readonly ILogger _logger; | |||||
protected readonly IServiceProvider _serviceProvider; | |||||
private readonly ObjectMethodExecutor _executor; | |||||
protected readonly ConsumerContext _consumerContext; | |||||
private Dictionary<string, object> _arguments; | |||||
public ConsumerInvoker(ILogger logger, | |||||
IServiceProvider serviceProvider, | |||||
ConsumerContext consumerContext, | |||||
ObjectMethodExecutor objectMethodExecutor) { | |||||
if (logger == null) { | |||||
throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
if (consumerContext == null) { | |||||
throw new ArgumentNullException(nameof(consumerContext)); | |||||
} | |||||
if (objectMethodExecutor == null) { | |||||
throw new ArgumentNullException(nameof(objectMethodExecutor)); | |||||
} | |||||
_logger = logger; | |||||
_serviceProvider = serviceProvider; | |||||
_consumerContext = consumerContext; | |||||
_executor = ObjectMethodExecutor.Create(_consumerContext.ConsumerDescriptor.MethodInfo, | |||||
_consumerContext.ConsumerDescriptor.ImplType.GetTypeInfo()); | |||||
} | |||||
public Task InvokeAsync() { | |||||
try { | |||||
using (_logger.BeginScope("consumer invoker begin")) { | |||||
_logger.LogDebug("Executing consumer Topic: {0}", _consumerContext.ConsumerDescriptor.Topic); | |||||
try { | |||||
var obj = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, _consumerContext.ConsumerDescriptor.ImplType); | |||||
_executor.Execute(obj, null); | |||||
return Task.CompletedTask; | |||||
} | |||||
finally { | |||||
_logger.LogDebug("Executed consumer method ."); | |||||
} | |||||
} | |||||
} | |||||
finally { | |||||
} | |||||
} | |||||
private object _controller; | |||||
private async Task InvokeConsumerMethodAsync() { | |||||
var controllerContext = _consumerContext; | |||||
var executor = _executor; | |||||
var controller = _controller; | |||||
var arguments = _arguments; | |||||
var orderedArguments = ConsumerMethodExecutor.PrepareArguments(arguments, executor); | |||||
var logger = _logger; | |||||
object result = null; | |||||
try { | |||||
var returnType = executor.MethodReturnType; | |||||
if (returnType == typeof(void)) { | |||||
executor.Execute(controller, orderedArguments); | |||||
result = new object(); | |||||
} | |||||
else if (returnType == typeof(Task)) { | |||||
await (Task)executor.Execute(controller, orderedArguments); | |||||
result = new object(); | |||||
} | |||||
//else if (executor.TaskGenericType == typeof(IActionResult)) { | |||||
// result = await (Task<IActionResult>)executor.Execute(controller, orderedArguments); | |||||
// if (result == null) { | |||||
// throw new InvalidOperationException( | |||||
// Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult))); | |||||
// } | |||||
//} | |||||
//else if (executor.IsTypeAssignableFromIActionResult) { | |||||
// if (_executor.IsMethodAsync) { | |||||
// result = (IActionResult)await _executor.ExecuteAsync(controller, orderedArguments); | |||||
// } | |||||
// else { | |||||
// result = (IActionResult)_executor.Execute(controller, orderedArguments); | |||||
// } | |||||
// if (result == null) { | |||||
// throw new InvalidOperationException( | |||||
// Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType)); | |||||
// } | |||||
//} | |||||
//else if (!executor.IsMethodAsync) { | |||||
// var resultAsObject = executor.Execute(controller, orderedArguments); | |||||
// result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject) { | |||||
// DeclaredType = returnType, | |||||
// }; | |||||
//} | |||||
//else if (executor.TaskGenericType != null) { | |||||
// var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments); | |||||
// result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject) { | |||||
// DeclaredType = executor.TaskGenericType, | |||||
// }; | |||||
//} | |||||
//else { | |||||
// // This will be the case for types which have derived from Task and Task<T> or non Task types. | |||||
// throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance( | |||||
// executor.MethodInfo.Name, | |||||
// executor.MethodInfo.DeclaringType)); | |||||
//} | |||||
//_result = result; | |||||
// logger.ActionMethodExecuted(controllerContext, result); | |||||
} | |||||
finally { | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace Cap.Consistency.Internal | |||||
{ | |||||
public class ConsumerMethodExecutor | |||||
{ | |||||
public static object[] PrepareArguments( | |||||
IDictionary<string, object> actionParameters, | |||||
ObjectMethodExecutor actionMethodExecutor) { | |||||
var declaredParameterInfos = actionMethodExecutor.ActionParameters; | |||||
var count = declaredParameterInfos.Length; | |||||
if (count == 0) { | |||||
return null; | |||||
} | |||||
var arguments = new object[count]; | |||||
for (var index = 0; index < count; index++) { | |||||
var parameterInfo = declaredParameterInfos[index]; | |||||
object value; | |||||
if (!actionParameters.TryGetValue(parameterInfo.Name, out value)) { | |||||
value = actionMethodExecutor.GetDefaultValueForParameter(index); | |||||
} | |||||
arguments[index] = value; | |||||
} | |||||
return arguments; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,285 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.ComponentModel; | |||||
using System.Linq; | |||||
using System.Linq.Expressions; | |||||
using System.Reflection; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Cap.Consistency.Internal | |||||
{ | |||||
public class ObjectMethodExecutor | |||||
{ | |||||
private readonly object[] _parameterDefaultValues; | |||||
private readonly ConsumerMethodExecutorAsync _executorAsync; | |||||
private readonly ConsumerMethodExecutor _executor; | |||||
private static readonly MethodInfo _convertOfTMethod = | |||||
typeof(ObjectMethodExecutor).GetRuntimeMethods().Single(methodInfo => methodInfo.Name == nameof(ObjectMethodExecutor.Convert)); | |||||
private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) { | |||||
if (methodInfo == null) { | |||||
throw new ArgumentNullException(nameof(methodInfo)); | |||||
} | |||||
MethodInfo = methodInfo; | |||||
TargetTypeInfo = targetTypeInfo; | |||||
ActionParameters = methodInfo.GetParameters(); | |||||
MethodReturnType = methodInfo.ReturnType; | |||||
IsMethodAsync = typeof(Task).IsAssignableFrom(MethodReturnType); | |||||
TaskGenericType = IsMethodAsync ? GetTaskInnerTypeOrNull(MethodReturnType) : null; | |||||
//IsTypeAssignableFromIActionResult = typeof(IActionResult).IsAssignableFrom(TaskGenericType ?? MethodReturnType); | |||||
if (IsMethodAsync && TaskGenericType != null) { | |||||
// For backwards compatibility we're creating a sync-executor for an async method. This was | |||||
// supported in the past even though MVC wouldn't have called it. | |||||
_executor = GetExecutor(methodInfo, targetTypeInfo); | |||||
_executorAsync = GetExecutorAsync(TaskGenericType, methodInfo, targetTypeInfo); | |||||
} | |||||
else { | |||||
_executor = GetExecutor(methodInfo, targetTypeInfo); | |||||
} | |||||
_parameterDefaultValues = GetParameterDefaultValues(ActionParameters); | |||||
} | |||||
private delegate Task<object> ConsumerMethodExecutorAsync(object target, object[] parameters); | |||||
private delegate object ConsumerMethodExecutor(object target, object[] parameters); | |||||
private delegate void VoidActionExecutor(object target, object[] parameters); | |||||
public MethodInfo MethodInfo { get; } | |||||
public ParameterInfo[] ActionParameters { get; } | |||||
public TypeInfo TargetTypeInfo { get; } | |||||
public Type TaskGenericType { get; } | |||||
// This field is made internal set because it is set in unit tests. | |||||
public Type MethodReturnType { get; internal set; } | |||||
public bool IsMethodAsync { get; } | |||||
//public bool IsTypeAssignableFromIActionResult { get; } | |||||
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) { | |||||
var executor = new ObjectMethodExecutor(methodInfo, targetTypeInfo); | |||||
return executor; | |||||
} | |||||
public Task<object> ExecuteAsync(object target, object[] parameters) { | |||||
return _executorAsync(target, parameters); | |||||
} | |||||
public object Execute(object target, object[] parameters) { | |||||
return _executor(target, parameters); | |||||
} | |||||
public object GetDefaultValueForParameter(int index) { | |||||
if (index < 0 || index > ActionParameters.Length - 1) { | |||||
throw new ArgumentOutOfRangeException(nameof(index)); | |||||
} | |||||
return _parameterDefaultValues[index]; | |||||
} | |||||
private static ConsumerMethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) { | |||||
// Parameters to executor | |||||
var targetParameter = Expression.Parameter(typeof(object), "target"); | |||||
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); | |||||
// Build parameter list | |||||
var parameters = new List<Expression>(); | |||||
var paramInfos = methodInfo.GetParameters(); | |||||
for (int i = 0; i < paramInfos.Length; i++) { | |||||
var paramInfo = paramInfos[i]; | |||||
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); | |||||
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); | |||||
// valueCast is "(Ti) parameters[i]" | |||||
parameters.Add(valueCast); | |||||
} | |||||
// Call method | |||||
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); | |||||
var methodCall = Expression.Call(instanceCast, methodInfo, parameters); | |||||
// methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" | |||||
// Create function | |||||
if (methodCall.Type == typeof(void)) { | |||||
var lambda = Expression.Lambda<VoidActionExecutor>(methodCall, targetParameter, parametersParameter); | |||||
var voidExecutor = lambda.Compile(); | |||||
return WrapVoidAction(voidExecutor); | |||||
} | |||||
else { | |||||
// must coerce methodCall to match ActionExecutor signature | |||||
var castMethodCall = Expression.Convert(methodCall, typeof(object)); | |||||
var lambda = Expression.Lambda<ConsumerMethodExecutor>(castMethodCall, targetParameter, parametersParameter); | |||||
return lambda.Compile(); | |||||
} | |||||
} | |||||
private static ConsumerMethodExecutor WrapVoidAction(VoidActionExecutor executor) { | |||||
return delegate (object target, object[] parameters) { | |||||
executor(target, parameters); | |||||
return null; | |||||
}; | |||||
} | |||||
private static ConsumerMethodExecutorAsync GetExecutorAsync(Type taskInnerType, MethodInfo methodInfo, TypeInfo targetTypeInfo) { | |||||
// Parameters to executor | |||||
var targetParameter = Expression.Parameter(typeof(object), "target"); | |||||
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); | |||||
// Build parameter list | |||||
var parameters = new List<Expression>(); | |||||
var paramInfos = methodInfo.GetParameters(); | |||||
for (int i = 0; i < paramInfos.Length; i++) { | |||||
var paramInfo = paramInfos[i]; | |||||
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); | |||||
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); | |||||
// valueCast is "(Ti) parameters[i]" | |||||
parameters.Add(valueCast); | |||||
} | |||||
// Call method | |||||
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); | |||||
var methodCall = Expression.Call(instanceCast, methodInfo, parameters); | |||||
var coerceMethodCall = GetCoerceMethodCallExpression(taskInnerType, methodCall, methodInfo); | |||||
var lambda = Expression.Lambda<ConsumerMethodExecutorAsync>(coerceMethodCall, targetParameter, parametersParameter); | |||||
return lambda.Compile(); | |||||
} | |||||
// We need to CoerceResult as the object value returned from methodInfo.Invoke has to be cast to a Task<T>. | |||||
// This is necessary to enable calling await on the returned task. | |||||
// i.e we need to write the following var result = await (Task<ActualType>)mInfo.Invoke. | |||||
// Returning Task<object> enables us to await on the result. | |||||
private static Expression GetCoerceMethodCallExpression( | |||||
Type taskValueType, | |||||
MethodCallExpression methodCall, | |||||
MethodInfo methodInfo) { | |||||
var castMethodCall = Expression.Convert(methodCall, typeof(object)); | |||||
// for: public Task<T> Action() | |||||
// constructs: return (Task<object>)Convert<T>((Task<T>)result) | |||||
var genericMethodInfo = _convertOfTMethod.MakeGenericMethod(taskValueType); | |||||
var genericMethodCall = Expression.Call(null, genericMethodInfo, castMethodCall); | |||||
var convertedResult = Expression.Convert(genericMethodCall, typeof(Task<object>)); | |||||
return convertedResult; | |||||
} | |||||
/// <summary> | |||||
/// Cast Task of T to Task of object | |||||
/// </summary> | |||||
private static async Task<object> CastToObject<T>(Task<T> task) { | |||||
return (object)await task; | |||||
} | |||||
private static Type GetTaskInnerTypeOrNull(Type type) { | |||||
var genericType = ExtractGenericInterface(type, typeof(Task<>)); | |||||
return genericType?.GenericTypeArguments[0]; | |||||
} | |||||
public static Type ExtractGenericInterface(Type queryType, Type interfaceType) { | |||||
if (queryType == null) { | |||||
throw new ArgumentNullException(nameof(queryType)); | |||||
} | |||||
if (interfaceType == null) { | |||||
throw new ArgumentNullException(nameof(interfaceType)); | |||||
} | |||||
if (IsGenericInstantiation(queryType, interfaceType)) { | |||||
// queryType matches (i.e. is a closed generic type created from) the open generic type. | |||||
return queryType; | |||||
} | |||||
// Otherwise check all interfaces the type implements for a match. | |||||
// - If multiple different generic instantiations exists, we want the most derived one. | |||||
// - If that doesn't break the tie, then we sort alphabetically so that it's deterministic. | |||||
// | |||||
// We do this by looking at interfaces on the type, and recursing to the base type | |||||
// if we don't find any matches. | |||||
return GetGenericInstantiation(queryType, interfaceType); | |||||
} | |||||
private static bool IsGenericInstantiation(Type candidate, Type interfaceType) { | |||||
return | |||||
candidate.GetTypeInfo().IsGenericType && | |||||
candidate.GetGenericTypeDefinition() == interfaceType; | |||||
} | |||||
private static Type GetGenericInstantiation(Type queryType, Type interfaceType) { | |||||
Type bestMatch = null; | |||||
var interfaces = queryType.GetInterfaces(); | |||||
foreach (var @interface in interfaces) { | |||||
if (IsGenericInstantiation(@interface, interfaceType)) { | |||||
if (bestMatch == null) { | |||||
bestMatch = @interface; | |||||
} | |||||
else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0) { | |||||
bestMatch = @interface; | |||||
} | |||||
else { | |||||
// There are two matches at this level of the class hierarchy, but @interface is after | |||||
// bestMatch in the sort order. | |||||
} | |||||
} | |||||
} | |||||
if (bestMatch != null) { | |||||
return bestMatch; | |||||
} | |||||
// BaseType will be null for object and interfaces, which means we've reached 'bottom'. | |||||
var baseType = queryType?.GetTypeInfo().BaseType; | |||||
if (baseType == null) { | |||||
return null; | |||||
} | |||||
else { | |||||
return GetGenericInstantiation(baseType, interfaceType); | |||||
} | |||||
} | |||||
private static Task<object> Convert<T>(object taskAsObject) { | |||||
var task = (Task<T>)taskAsObject; | |||||
return CastToObject<T>(task); | |||||
} | |||||
private static object[] GetParameterDefaultValues(ParameterInfo[] parameters) { | |||||
var values = new object[parameters.Length]; | |||||
for (var i = 0; i < parameters.Length; i++) { | |||||
var parameterInfo = parameters[i]; | |||||
object defaultValue; | |||||
if (parameterInfo.HasDefaultValue) { | |||||
defaultValue = parameterInfo.DefaultValue; | |||||
} | |||||
else { | |||||
var defaultValueAttribute = parameterInfo | |||||
.GetCustomAttribute<DefaultValueAttribute>(inherit: false); | |||||
if (defaultValueAttribute?.Value == null) { | |||||
defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType | |||||
? Activator.CreateInstance(parameterInfo.ParameterType) | |||||
: null; | |||||
} | |||||
else { | |||||
defaultValue = defaultValueAttribute.Value; | |||||
} | |||||
} | |||||
values[i] = defaultValue; | |||||
} | |||||
return values; | |||||
} | |||||
} | |||||
} |
@@ -4,10 +4,11 @@ using System.Text; | |||||
using System.Linq; | using System.Linq; | ||||
using Cap.Consistency.Consumer; | using Cap.Consistency.Consumer; | ||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
using System.Threading.Tasks; | |||||
namespace Cap.Consistency | namespace Cap.Consistency | ||||
{ | { | ||||
public class KafkaConsistency | |||||
public class KafkaConsistency:IRoute | |||||
{ | { | ||||
private IServiceProvider _serviceProvider; | private IServiceProvider _serviceProvider; | ||||
private IEnumerable<IConsumerHandler> _handlers; | private IEnumerable<IConsumerHandler> _handlers; | ||||
@@ -29,5 +30,9 @@ namespace Cap.Consistency | |||||
handler.Stop(); | handler.Stop(); | ||||
} | } | ||||
} | } | ||||
} | |||||
public async Task Start() { | |||||
} | |||||
} | } |
@@ -1,14 +0,0 @@ | |||||
using System; | |||||
namespace Cap.Consistency | |||||
{ | |||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)] | |||||
sealed class QMessageAttribute : Attribute | |||||
{ | |||||
public QMessageAttribute(string messageName) { | |||||
MessageName = messageName; | |||||
} | |||||
public string MessageName { get; private set; } | |||||
} | |||||
} |
@@ -1,48 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Reflection; | |||||
using System.Threading.Tasks; | |||||
using System.Collections.Concurrent; | |||||
using Cap.Consistency.Extensions; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
namespace Cap.Consistency | |||||
{ | |||||
public class QMessageFinder | |||||
{ | |||||
public ConcurrentDictionary<string, QMessageMethodInfo> GetQMessageMethods(IServiceCollection serviceColloection) { | |||||
if (serviceColloection == null) { | |||||
throw new ArgumentNullException(nameof(serviceColloection)); | |||||
} | |||||
var qMessageTypes = new ConcurrentDictionary<string, QMessageMethodInfo>(); | |||||
foreach (var serviceDescriptor in serviceColloection) { | |||||
foreach (var method in serviceDescriptor.ServiceType.GetTypeInfo().DeclaredMethods) { | |||||
var messageMethodInfo = new QMessageMethodInfo(); | |||||
if (method.IsPropertyBinding()) { | |||||
continue; | |||||
} | |||||
var qMessageAttr = method.GetCustomAttribute<QMessageAttribute>(); | |||||
if (qMessageAttr == null) { | |||||
continue; | |||||
} | |||||
messageMethodInfo.MessageName = qMessageAttr.MessageName; | |||||
messageMethodInfo.ImplType = method.DeclaringType; | |||||
messageMethodInfo.MethodInfo = method; | |||||
qMessageTypes.AddOrUpdate(qMessageAttr.MessageName, messageMethodInfo, (x, y) => y); | |||||
} | |||||
} | |||||
return qMessageTypes; | |||||
} | |||||
} | |||||
} |
@@ -1,17 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Reflection; | |||||
using System.Threading.Tasks; | |||||
namespace Cap.Consistency | |||||
{ | |||||
public class QMessageMethodInfo | |||||
{ | |||||
public MethodInfo MethodInfo { get; set; } | |||||
public Type ImplType { get; set; } | |||||
public string MessageName { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,12 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Cap.Consistency.Routing | |||||
{ | |||||
public interface ITopicRoute | |||||
{ | |||||
Task RouteAsync(TopicRouteContext context); | |||||
} | |||||
} |
@@ -6,12 +6,15 @@ using Cap.Consistency.Abstractions; | |||||
using Cap.Consistency.Consumer; | using Cap.Consistency.Consumer; | ||||
using Cap.Consistency.Infrastructure; | using Cap.Consistency.Infrastructure; | ||||
namespace Cap.Consistency.Route | |||||
namespace Cap.Consistency.Routing | |||||
{ | { | ||||
public delegate Task HandlerConsumer(ConsumerExecutorDescriptor context); | public delegate Task HandlerConsumer(ConsumerExecutorDescriptor context); | ||||
public class TopicRouteContext | public class TopicRouteContext | ||||
{ | { | ||||
public TopicRouteContext() { | |||||
} | |||||
public TopicRouteContext(DeliverMessage message) { | public TopicRouteContext(DeliverMessage message) { | ||||
Message = message; | Message = message; | ||||
@@ -23,9 +26,11 @@ namespace Cap.Consistency.Route | |||||
public HandlerConsumer Handler { get; set; } | public HandlerConsumer Handler { get; set; } | ||||
public IList<IConsumerHandler> Consumers { get; set; } | |||||
public IServiceProvider ServiceProvider { get; set; } | public IServiceProvider ServiceProvider { get; set; } | ||||
public IList<IConsumerHandler> Consumers { get; set; } | |||||
public IList<ITopicRoute> Routes { get; set; } | |||||
} | } | ||||
} | } |
@@ -1,47 +0,0 @@ | |||||
using System; | |||||
using Cap.Consistency; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||||
// ReSharper disable once CheckNamespace | |||||
namespace Microsoft.Extensions.DependencyInjection | |||||
{ | |||||
/// <summary> | |||||
/// Contains extension methods to <see cref="IServiceCollection"/> for configuring consistence services. | |||||
/// </summary> | |||||
public static class ServiceCollectionExtensions | |||||
{ | |||||
/// <summary> | |||||
/// Adds and configures the consistence services for the consitence. | |||||
/// </summary> | |||||
/// <param name="services">The services available in the application.</param> | |||||
/// <returns>An <see cref="ConsistencyBuilder"/> for application services.</returns> | |||||
public static ConsistencyBuilder AddConsistency<TMessage>(this IServiceCollection services) | |||||
where TMessage : class { | |||||
return services.AddConsistency<TMessage>(setupAction: null); | |||||
} | |||||
/// <summary> | |||||
/// Adds and configures the consistence services for the consitence. | |||||
/// </summary> | |||||
/// <param name="services">The services available in the application.</param> | |||||
/// <param name="setupAction">An action to configure the <see cref="ConsistencyOptions"/>.</param> | |||||
/// <returns>An <see cref="ConsistencyBuilder"/> for application services.</returns> | |||||
public static ConsistencyBuilder AddConsistency<TMessage>(this IServiceCollection services, Action<ConsistencyOptions> setupAction) | |||||
where TMessage : class { | |||||
services.TryAddSingleton<ConsistencyMarkerService>(); | |||||
services.TryAddScoped<ConsistencyMessageManager<TMessage>, ConsistencyMessageManager<TMessage>>(); | |||||
services.AddSingleton<QMessageFinder>(); | |||||
if (setupAction != null) { | |||||
services.Configure(setupAction); | |||||
} | |||||
return new ConsistencyBuilder(typeof(TMessage), services); | |||||
} | |||||
} | |||||
} |
@@ -23,20 +23,20 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" /> | |||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta5-build1225" /> | |||||
<PackageReference Include="xunit" Version="2.2.0-beta5-build3474" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" /> | |||||
<PackageReference Include="Moq" Version="4.6.36-*" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" /> | |||||
<PackageReference Include="System.Data.SqlClient" Version="4.3.0" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> | |||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" /> | |||||
<PackageReference Include="xunit" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" /> | |||||
<PackageReference Include="Moq" Version="4.7.10" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" /> | |||||
<PackageReference Include="System.Data.SqlClient" Version="4.3.1" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="1.2.0-preview1-22815" /> | <PackageReference Include="Microsoft.AspNetCore.Testing" Version="1.2.0-preview1-22815" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
@@ -19,13 +19,13 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" /> | |||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta5-build1225" /> | |||||
<PackageReference Include="xunit" Version="2.2.0-beta5-build3474" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" /> | |||||
<PackageReference Include="Moq" Version="4.6.36-*" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" /> | |||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> | |||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" /> | |||||
<PackageReference Include="xunit" Version="2.2.0" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.2" /> | |||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" /> | |||||
<PackageReference Include="Moq" Version="4.7.10" /> | |||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
</Project> | </Project> |