24、Wiring up EasyNetQ with TopShelf and Windsor 让EasyNetQ、TopShelf 和Windsor 一起使用
围绕消息总线架构一个系统需要编写许多小而集中的组件,这些组件趴在总线上等待它们所关心的消息。这些应用程序最好是写成window服务,而实现window服务的最好方式是使用 TopShelf开源框架,用TopShelf写window服务超简单。这个开源库脱胎于优秀的MassTransit 项目。你可以简单地创建一个控制台应用程序,然后从NuGet安装TopShelf,然后调用API描述你的服务。
一个IoC(DI)容器应该是应用程序的核心(除了最简单的.NET程序)。如果你还没用过IoC容器,那你真的该好好看看这个文章《read this for why you should》。有相当多的IoC容器可供选择,我们用的是Castle Windsor.
本章我们展示一下如何让EasyNetQ、TopShelf 和Windsor 一起使用。
IoC的口号是“任何使用了IoC容器的应用,都应该只把IoC库引用到2个地方”:
一是创建IoC容器的地方,容器存在于应用程序的整个生命周期里。二是从容器中解析根服务。所有其他依赖项都是由容器本身默默提供的。这就是为啥IoC容器这么拽这么必要的原因。
依照这个法则,在Main()方法中我们创建IoC容器,解析我们应用程序的根服务。
public class Program { public static void Main(string[] args) {
//创建IoC容器 var container = new WindsorContainer().Install(FromAssembly.This()); HostFactory.Run(x => { x.Service<IVaultService>(s => { s.ConstructUsing(name => container.Resolve<IVaultService>()); s.WhenStarted(tc => tc.Start()); s.WhenStopped(tc => { tc.Stop(); container.Release(tc); container.Dispose(); }); }); x.RunAsLocalSystem(); x.SetDescription("Vault service."); x.SetDisplayName("Vault.Service"); x.SetServiceName("Vault.Service"); }); } }
Windsor的一个基本原则是,你必须释放你解析的任何组件。Windsor跟踪任何实现了IDisposable的组件,并确保不管组件在依赖关系图的什么地方被解析,Dispose方法都会被调用。但是你需要调用Release来正确地实现它。
“tc”变量是IVaultService的实例,它在“ConstructUsing”调用中得到解析,因此我们可以在Release调用中使用它。
EasyNetQ呢?EasyNetQ的口号是:你应该创建单个IBus的实例存在于整个应用程序的生命周期中。现在我们可以在Main() 创建IBus实例,然后是TopShelf 的配置,然后是创建IoC容器。但是由于我们希望IoC容器能够管理应用程序中的所有组件的生命周期。
首先,让我们创建一个简单的工厂方法,该方法获取EasyNetQ的连接字符串,并创建一个IBus的新实例
public class BusBuilder { public static IBus CreateMessageBus() { var connectionString = ConfigurationManager.ConnectionStrings["easynetq"]; if (connectionString == null || connectionString.ConnectionString == string.Empty) { throw new VaultServiceException("easynetq connection string is missing or empty"); } return RabbitHutch.CreateBus(connectionString.ConnectionString); } }
现在,我们可以编写 IWindsorInstaller 注册我们的服务:
public class Installer : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register( Component.For<IVaultService>().ImplementedBy<VaultService>().LifestyleTransient(), Component.For<IBus>().UsingFactoryMethod(BusBuilder.CreateMessageBus).LifestyleSingleton() ); } }
请注意,我们告诉Windsor 使用我们的工厂方法UsingFactoryMethod,而不是“Instance”来创建IBus实例。Instance方法告诉Windsor ,我们自己将为服务的生命周期承担责任,但是我们想要的是让Windsor 在应用程序关闭时自动调用Dispose。
UsingFactoryMethod 告诉Windsor 应该去管理IBus的生命周期。我们将其声明为LifestyleSingleton ,因为我们希望在应用程序的整个生命周期中只使用一个IBus的实例。
现在,我们可以在我们的IVaultService实现中引用IBus:
public interface IVaultService //我们的一个服务 { void Start(); void Stop(); } public class VaultService : IVaultService { private readonly IBus bus; public VaultService(IBus bus) { this.bus = bus; } public void Start() { bus.SubscribeAsync<MyMessage>("vault_handler", msg => { }); } public void Stop() { // shutdown code } }
在这里,我们只是在VaultService服务的开始方法中订阅MyMessage。我可能还会有一个IMyMessageHandler服务,由IVaultService引用,来完成消息处理。
所以你看到了,一个简单的方法,可以把这三个优秀的开源项目一起使用。
几个例子:
EasyNetQ例子包括:Request/Response、Autosubcriber,使用Windsor IoC
https://bitbucket.org/philipogorman/createrequestservice/src