这是整个示例项目的git仓库链接:https://gitlab.com/sunnyatticsoftware/DiegoDrivenDesign/DiDrDe.MessageBus
我希望有一个项目来封装MassTransit的功能,这样我的发布者和消费者项目就不需要知道MassTransit的存在。依赖关系应该是这样的:
- DiDrDe.MessageBus ==> MassTransit
- DiDrDe.MessageBus ==> DiDrDe.Contracts
- DiDrDe.Model ==> DiDrDe.Contracts
- DiDrDe.Publisher ==> DiDrDe.MessageBus
- DiDrDe.Publisher ==> DiDrDe.Contracts
- DiDrDe.Publisher ==> DiDrDe.Model
- DiDrDe.ConsumerOne ==> DiDrDe.Contracts
- DiDrDe.ConsumerOne ==> DiDrDe.MessageBus
- DiDrDe.ConsumerOne ==> DiDrDe.Model
- DiDrDe.ConsumerTwo ==> DiDrDe.Contracts
- DiDrDe.ConsumerTwo ==> DiDrDe.MessageBus
- DiDrDe.ConsumerTwo ==> DiDrDe.Model
注意,DiDrDe.MessageBus对DiDrDe.Model一无所知,因为它是一个通用项目,适用于任何消息类型。
为了实现这一点,我正在实施适配器模式,以便我的自定义接口IEventDtoBus(用于发布事件)和IEventDtoHandler(用于消费事件)是所有发布者和消费者所知道的。MassTransit包装项目(称为DiDrDe.MessageBus)使用适配器实现了这些,其中包括一个由IEventDtoBus和EventDtoHandlerAdapter组成的EventDtoBusAdapter,以及一个由IEventDtoHandler组成的唯一泛型IConsumer。我遇到的问题是MassTransit要求消费者在注册时必须知道其类型,而我的消费者是一个泛型消费者,其类型在编译时不应该被MassTransit包装器所知道。
我需要找到一种方法,在运行时将EventDtoHandlerAdapter注册为每个传递的TEventDto类型的消费者(例如作为类型集合)。请查看我的存储库以获取所有详细信息。
MassTransit支持一个重载方法,它接受一个类型(很好!正是我想要的),但它还需要一个第二个参数
Func<type, object> consumerFactory
,我不知道如何实现它。
更新1:
问题是我无法像这样注册这个泛型消费者:consumer.Consumer<EventDtoHandlerAdapter<ThingHappened>>();
因为我遇到了编译错误
严重程度 代码 描述 项目 文件 行 抑制状态 错误 CS0310 'EventDtoHandlerAdapter' 必须是一个非抽象类型,具有公共的无参数构造函数,才能在泛型类型或方法'ConsumerExtensions.Consumer(IReceiveEndpointConfigurator, Action<IConsumerConfigurator>)'中使用它作为参数'TConsumer' DiDrDe.MessageBus C:\src\DiDrDe.MessageBus\DiDrDe.MessageBus\IoCC\Autofac\RegistrationExtensions.cs
更新2:我尝试了几种方法,并在我的存储库上更新了项目。这些是我在MassTransit包装器项目中的尝试。请注意,如果我为每个我想处理的消息(事件)添加一个依赖项,我就能使所有东西都正常工作。但我不想要那样..我不希望这个项目知道任何关于它可以处理的消息。如果我只能注册消费者,只知道消息类型就好了..
cfg.ReceiveEndpoint(host, messageBusOptions.QueueName, consumer =>
{
//THIS WORKS
var eventDtoHandler = context.Resolve<IEventDtoHandler<ThingHappened>>();
consumer.Consumer(() => new EventDtoHandlerAdapter<ThingHappened>(eventDtoHandler));
// DOES NOT WORK
//var typeEventDtoHandler = typeof(IEventDtoHandler<>).MakeGenericType(typeof(ThingHappened));
//var eventDtoHandler = context.Resolve(typeEventDtoHandler);
//consumer.Consumer(eventDtoHandler);
// DOES NOT WORK
//consumer.Consumer<EventDtoHandlerAdapter<ThingHappened>>(context);
// DOES NOT WORK
//var consumerGenericType = typeof(IConsumer<>);
//var consumerThingHappenedType = consumerGenericType.MakeGenericType(typeof(ThingHappened));
//consumer.Consumer(consumerThingHappenedType, null);
});
更新3:根据Igor的建议,我尝试做以下事情:
var adapterType = typeof(EventDtoHandlerAdapter<>).MakeGenericType(typeof(ThingHappened));
consumer.Consumer(adapterType, (Type x) => context.Resolve(x));
但是我遇到了一个运行时错误,错误信息如下:
请求的服务'DiDrDe.MessageBus.EventDtoHandlerAdapter`1[[DiDrDe.Model.ThingHappened, DiDrDe.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'未注册。为了避免这个异常,您可以注册一个组件来提供该服务,使用IsRegistered()方法检查服务是否已注册,或者使用ResolveOptional()方法来解决可选依赖。
我甚至尝试将EventDtoHandlerAdapter<>单独注册为IConsumer,以防这是问题所在,但是没有成功。
builder
.RegisterGeneric(typeof(EventDtoHandlerAdapter<>))
.As(typeof(IConsumer<>))
.SingleInstance();
还有:
builder
.RegisterType<EventDtoHandlerAdapter<ThingHappened>>()
.AsSelf();
而且它告诉我
System.ObjectDisposedException:'此解析操作已经结束。在使用lambda注册组件时,lambda表达式中的IComponentContext 'c'参数不能被存储。相反,要么再次从'c'解析IComponentContext,要么解析基于Func<>的工厂来创建后续组件
只是为了澄清,我只需要注册我的EventDtoHandlerAdapter<TEventDto>
消费者。它是泛型的,所以对于我支持的每个TEventDto,都会存在一个注册。问题是我不需要提前知道类型,所以我需要操作类型。
更新4:根据Igor的建议,尝试了一种新的方法。这次使用了“代理”。我已经在我的存储库中更新了最新尝试的所有细节。 我有我的消费者和标记接口:
public interface IEventDtoHandler
{
}
public interface IEventDtoHandler<in TEventDto>
: IEventDtoHandler
where TEventDto : IEventDto
{
Task HandleAsync(TEventDto eventDto);
}
我有自己的一个消费者实现,对MassTransit一无所知。
public class ThingHappenedHandler
: IEventDtoHandler<ThingHappened>
{
public Task HandleAsync(ThingHappened eventDto)
{
Console.WriteLine($"Received {eventDto.Name} " +
$"{eventDto.Description} at consumer one that uses an IEventDtoHandler");
return Task.CompletedTask;
}
}
现在我的“包装消费者”是我称之为适配器的东西,因为它了解MassTransit(它实现了MassTransit的IConsumer接口)。
public class EventDtoHandlerAdapter<TConsumer, TEventDto>
: IConsumer<TEventDto>
where TConsumer : IEventDtoHandler<TEventDto>
where TEventDto : class, IEventDto
{
private readonly TConsumer _consumer;
public EventDtoHandlerAdapter(TConsumer consumer)
{
_consumer = consumer;
}
public async Task Consume(ConsumeContext<TEventDto> context)
{
await _consumer.HandleAsync(context.Message);
}
}
现在最后一步是将我的“包装消费者”注册到MassTransit。但由于它是泛型的,我不知道该如何做。这是问题所在。
我可以按照建议,将所有我的消费者类型扫描并在Autofac中注册。
var interfaceType = typeof(IEventDtoHandler);
var consumerTypes =
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.GetTypes())
.Where(x => interfaceType.IsAssignableFrom(x)
&& !x.IsInterface
&& !x.IsAbstract)
.ToList();
现在我有所有的消费者类型(所有实现了的实现,包括我的)。现在怎么办?如何注册它?
类似以下的方法不起作用:
foreach (var consumerType in consumerTypes)
{
consumer.Consumer(consumerType, (Type x) => context.Resolve(x));
}
但我猜这是正常的,因为我想要注册的是我的EventDtoHandlerAdapter,它是真正的IConsumer。
所以,我想我没有理解你的建议。对不起!
我需要的是像这样的东西:
//THIS WORKS
var eventDtoHandler = context.Resolve<IEventDtoHandler<ThingHappened>>();
consumer.Consumer(() => new EventDtoHandlerAdapter<IEventDtoHandler<ThingHappened>, ThingHappened>(eventDtoHandler));
但是不能使用ThingHappened模型,因为模型不应该被知道。这就是我卡住的地方。
更新5:根据Chris Patterson的建议进行了新的尝试(他的解决方案已合并到我的存储库中),但问题仍然存在。
澄清一下,DiDrDe.MessageBus必须对任何发布者、消费者和模型都是不可知的。它只应该依赖于MassTransit和DiDrDe.Contracts,而Chris的解决方案中有一行代码:
cfg.ReceiveEndpoint(host, messageBusOptions.QueueName, consumer =>
{
consumer.Consumer<EventDtoHandlerAdapter<ThingHappened>>(context);
});
这个直接依赖于
ThingHappened
模型。这是不允许的,实际上与我已经有的解决方案没有太大区别,那个解决方案是:cfg.ReceiveEndpoint(host, messageBusOptions.QueueName, consumer =>
{
//THIS works, but it uses ThingHappened explicitly and I don't want that dependency
var eventDtoHandler = context.Resolve<IEventDtoHandler<ThingHappened>>();
consumer.Consumer(() => new EventDtoHandlerAdapter<ThingHappened>(eventDtoHandler));
});
抱歉如果这不够清楚,但是DiDrDe.MessageBus最终将成为一个nuGet包,被许多不同的消费者和发布者项目共享,并且不应该依赖于任何特定的消息/模型。
更新6: 问题已解决。非常感谢Igor和Chris的时间和帮助。 我已将解决方案推送到我的存储库的主分支。
附注:不幸的是,当我在同一个消费者中有两个处理程序处理相同的事件时,这个解决方案有其局限性,因为似乎只有一个处理程序被执行(两次)。我希望两个处理程序都被执行,或者只有一个处理程序被执行一次(而不是两次)。但这已经是另一个话题了 :)
builder.RegisterGeneric
吗? - cl0ud