首页 > 代码库 > Shuttle ESB(二)——架构模型介绍

Shuttle ESB(二)——架构模型介绍

该部分包含如下五部分内容,限于篇幅,本文先介绍前三个:概念、消息类型、耦合。

一、概念

二、消息类型

三、耦合

四、模式

五、消息路由


概念

本位中的所有代码,不是一个完整的例子,也不是一个vs解决方案。它向我们介绍了,在Shuttle ESB里面一些比较重要的概念。在Shuttle ESB入门实例 里面,有一个简单的实现,将这些概念融合在了一起,大家可以结合实例,理解本文的概念。

Shuttle ESB的基本组成:消息、队列和服务总线


每一个服务总线实例都是相关联的,因此只有一个输入队列,注意只能有一个输入队列。这个输入队列就是一个收件箱。收件箱中接收的所有消息,都是由服务总线实例进行使用。


    消息

Shuttle是基于消息的。这些消息是实现了指定接口的数据传输对象。例如:

public class ActivateMemberCommand
    {
        string MemberId { get; set; }
    }
public class MemberActivatedEvent
    {
        string MemberId { get; set; }
    }


队列

Shuttle ESB中的消息,都是由Handler处理的。当一个服务总线实例启动的时候,它就开始监听收件箱里的队列消息。所以,在相关队列里面处理的消息,一定要有结束。收件箱的配置,是在应用程序配置文件中设置的。


这种消息发送的方法,采用至少发送一次的机制。这不同于传统概念上的,准确传递一次的例子。在某些临界情况,它可能多次处理消息。然而,其他机制的临界情况,可能会导致信息的丢失(一个重复的消息比没有消息更容易点)

很重要的一点就是:所有的队列都是无损的,并且有确认反馈机制。

所以,一旦消息从队列中重新取回,就有可能是确认消息释放消息返回到队列。


服务总线

在每个应用程序访问服务总线时,服务总线实例是必须的。服务总线的基本组成:关联的代码、应用程序配置文件和应用自定义组件。例如:

public class ServiceBusHost : IHost, IDisposable
    {
        private static IServiceBus bus;
public void Start()
        {
            bus = ServiceBus.Create().Start();
        }
public void Dispose()
        {
            bus.Dispose();
        }
    }


创建并运行一个服务总线实例创建,同时,启动和配置退出应用程序。服务总线可以在任何类型的应用程序中使用,最经典的情况就是作为服务进行通信。虽然你可以自己写服务,来作为服务总线,但是它是不必要的,因为你可以能用到通用主机:generic service host:http://shuttle.github.io/shuttle-esb/generic-host/index.html


消息类型


命令消息

由于命令是一个要求执行特定功能的明确请求,因此命令会被发送到单独的终结点。也就是说:为了发送消息,你需要知道某个终结点的相关业务实现。因此,命令导致了高度的行为耦合。如果在发送消息时,那个接收消息的终结点是可配置的,那么便可随时更改终结点。


一个命令消息总是属于单一的一端。发送一个命令也永远不会导致一个消息出现在多个队列里。
虽然,我们应该尽量减少使用这种消息类型,但是它在特殊情况,也有使用的必要性,例如下面的例子。

    启动一个应用

在我们的应用程序中,我们需要发送一个CreateOrderCommand 队列服务。这将启动相关过程。

所以从客户端代码开始:

bus.Send(new CreateOrderCommand("ClientName", "ProductXYZ"));


如果没有消息接收端,这次调用就会失败。

现在我们发布一个事件,像OrderReceivedEvent 和我们的队列服务,能够订阅一个事件,也能够取消订阅事件。

bus.Publish(new OrderOrderReceivedEvent("ClientName", "ProductXYZ"));


如果没有订阅者,调用也不会失败。

因此,不同之处就是:使用消息的情况不同。当我们使用事件时,我们会选择订阅模式;但是当一个系统中,已经确定一些特定行为的时候,使用命令的方式,应该更合适一些。当然,当使用一个命令的方法时,仍然有一些其他系统知道对方已经接收到了命令,那么订单服务会接着发布一个事件。


底层方法(RPC)

在某些情况下,一个事件可能无法传递某个动作的意图。例如:当一个命令创建的时候(或者是命令的数量超过上限的时候),我们可能会给领导发一封邮件。电子邮件服务负责通过SMTP服务器发送电子邮件。这电子邮件是无法通过OrderReceivedEvent事件订阅的,因为电子邮件系统需要适应另一个系统中的规则。

在这个例子中,邮件系统负责发送邮件。 任何系统发送邮件,都需要决定什么时候发送。因此,这个订单服务将向邮件服务发送一个指令:

bus.Send(new SendMailCommand
                 {
                     To = "manager@ordercompany.co.za",
                     From = "orderservice@ordercompany.co.za",
                     Subject = "Important Order Received",
                     Body = "Order Details"
                 });


事件消息

一个事件消息就是用来通知所有的订阅组件。每个组件订阅事件,将得到一份事件消息。对于一个事件来说,如果没有订阅者,那么用户也不会有什么影响。


文档消息

一个文档消息用来简单的向一段发送数据。与使用事件消息一样,文档消息一定的方向性,接受者可以接收也可以不接收。但是,这不意味着数据会无缘无故的发送到一个终端。终端可能需要从一些服务中获取一个文档消息。或者根据需要,数据会自动发送给某些终端。


耦合


行为耦合


行为耦合是指一个系统与另一个系统行为的耦合程度。当你的系统接收到一个命令消息,你需要它在某个特定需求中使用。这就代表着一种高度的行为耦合。然而,当一个事件消息发布好了,没有订阅者等待获取消息。这就是一个较低的行为耦合。
也就是说:可能有一个期望的事件会导致一个特定的结果。在这种情况下,行为的耦合增加了。


时空耦合


时空耦合指的是一个服务的可用性。例如:ServiceA服务依赖ServiceB服务。为了保证ServiceA能够正常运行,需要同时保证ServiceB是可用的,然后ServiceA才能够正常运行。这就是一个高度的时空耦合。相反的,如果ServiceA没有ServiceB,仍然能够正常运行,那么这就是一个低时空耦合。
一个异步Web-Service调用就是一个高时空耦合例子。


现在你可能会心存疑虑:在一个用例中,需要ServiceA和ServiceB两个服务,才能够完成。那么,他们是怎么单独进行的?
这其实很简单,答案就是:使用队列,进行异步通信。

你可能会说了:一个web-Service的调用可能使用异步通信,但是这里还是有问题的。当一个客户端的请求未接到相应时,ServiceA可能会执行失败。这就会导致web-Service调用失败。


我们使用消息进行通信,消息经常会向着一个方向传递。从ServiceA到ServiceB是一个动作,执行之后,这个动作就结束了;从ServiceB到ServiceA又是一个动作,执行之后,这个动作就又结束了。

Shuttle ESB(二)——架构模型介绍