概念
Shuttle ESB的基本組成部分有:
消息(Message)
隊列(Queues)
服務總線(Service bus)
每一個服務總線實例被關聯而且爲此運行着僅一個輸入隊列。這是收件箱。全部收件箱收到的消息都被關聯的服務總線實例所處理。
消息
Shuttle 是基於消息的。消息是實現了特性消息接口的數據傳輸對象:
public class ActivateMemberCommand
{
string MemberId { get; set; }
}
public class MemberActivatedEvent
{
string MemberId { get; set; }
}
隊列
消息是被
Shuttle ESB上回調的消息處理器(
message handler)所處理的。當一個服務總線被啓動,它就開始監聽一個消息輸入隊列的消息。因此消息之中都會在相關的隊列中被處理。收件箱配置是在應用程序配置文件中被指明的。採起的方法是,至少投遞一次(
an
at-least-once
delivery
)。這和只投遞一次(
an
exactly-once
delivery
)的區別是,邊界狀況可能致使一個消息被執行一次以上(這可能不多發生)。不管如何,因爲其它機制(
mechanism
)的邊界狀況可能致使一個消息在不可能到達節點的時候遺失(一個複製的消息對於節點來講比根本沒有消息要容易處理得多)。
全部的隊列都是沒有破壞性的,這很重要,並且應該老是在實現的思路中被認可。因此,當一個消息被從隊列中恢復,它應該既能夠被確認也能夠釋放該消息返回隊列。
服務總線
一個服務總線實例在每一個鏈接服務總線應用程序中都是必須的。用來配置服務總線,須要聯合使用代碼,應用程序配置文件和自定義組件。
public class ServiceBusHost : IHost, IDisposable
{
private static IServiceBus bus;
public void Start()
{
bus = ServiceBus.Create().Start();
}
public void Dispose()
{
bus.Dispose();
}
}
一個服務總線實例在應用程序配啓動的時候建立和開啓,釋放的時候退出。一個服務總線能夠承載在任何類型的應用程序中,可是,大多數的電影場景是承載他們做爲服務。雖然你能夠寫你本身的服務去承載你的服務總線,可是,那並非必須的,由於你或許想要用通常服務宿主(
http://shuttle.github.io/shuttle-esb/generic-host/index.html
)
消息類型
命令消息
一個命令消息用來告訴另外一個系統或者組件要作什麼。這說明主叫系統(calling system)知道被叫系統(called system)的行爲。所以,這是一種高度的行爲耦合。
一個命令消息老是屬於一個終結點(endpoint)。發送一個命令永遠不會致使消息進到超過一個消息隊列。
雖然在某些狀況下,咱們應該最小化消息所須要的消息的類型,這些咱們將在接下來的部分進行討論。
開始一個過程
存在這樣的一些狀況,咱們須要以某系事情爲開始。讓咱們來用這個例子從客戶端接收一個訂單。在咱們的應用程序中,咱們應該發送一個
CreateOrderCommand
到咱們的訂單服務,這將開啓相關的流程。
這是客戶端的代碼:
bus.Send(new CreateOrderCommand("ClientName", "ProductXYZ"));
若是沒有地方發送這個消息的話,調用將會失敗。
如今咱們能夠公佈一個事件,如:
OrderReceivedEvent
而且,咱們的訂單服務能夠訂閱這個事件並開始一切。
bus.Publish(new OrderOrderReceivedEvent("ClientName", "ProductXYZ"));
即便沒有訂閱者,這個請求也不會失敗。
因此,其中的區別單純是消息的目的。當咱們可使用事件(event)時,這應該是首選,可是,當某一動做在一個系統中是必須的話,那麼一個命令(command)或許會更加合適。固然,即便使用一個命令來處理,依然可能會有其它系統對獲知一個訂單被接收了感興趣,因此,訂單系統應該在接下來發佈一個事件。
更加底層的方法(
RPC(Remote Procedure Call Protocol)——
遠程過程調用
協議
)
在某些狀況下,一個事件沒法傳達一個特定方法的意圖。好比,咱們須要發送一個e-mail給一個管理者,只要一個訂單被建立(或者訂單的總金額超出限額)。e-mail服務負責發送e-mail,經過咱們的smtp服務器。可是e-mail服務不能訂閱
OrderReceivedEvent
,由於e-mail系統須要另一個系統的規則。
有鑑於此,e-mail系統負責發送郵件。任何一個須要發送郵件的系統將會須要決定,何時去作這件事。所以,訂單系統將會發送一個命令到e-mail系統:
bus.Send(new SendMailCommand
{
To = "manager@ordercompany.co.za",
From = "orderservice@ordercompany.co.za",
Subject = "Important Order Received",
Body = "Order Details"
});
事件消息
一個事件消息被用來通知任意訂閱的組件有什麼業務上重要的事情發生了。每一個訂閱了事件的組件將會得到一份事件消息的副本。一個事件若是沒有訂閱者存在,那麼發佈這個事件也不會有任何效果。
文檔消息(document message)
一個文檔消息單純得被用於向某個終結點發送數據。相較於事件消息,它並不存在任何意圖,而且接收者能夠隨意處置它。這並不表明着數據會被沒有緣由得發送到終結點。一個終結點可能須要一個文檔從幾個服務,或者數據是自動送到一些終結點的,由於它多是必須的。
耦合
行爲耦合(behavioural coupling)
行爲耦合是用來指這樣的一種耦合,一個系統知道另外一個系統的行爲。當一個命令被髮送到一個系統,你指望它會被進行一種制定的處理。這表現出了一種高度的行爲耦合。當一個事件消息被公佈,並無期待從訂閱者得到什麼預期的反饋。這是一種較低的行爲耦合。
能夠預見,若是預期某個事件將會有指定的結果,這將會再次增長行爲耦合。
時間耦合(temporal coupling)
時間耦合泛指服務在被須要時的可用性。
服務A要求服務B可用,以使服務A能夠執行某個方法,這裏就存在一個高度的時間耦合。相反的,若是服務A能夠繼續,即便ServiceB是不可用的,那麼他們就只有低的時間耦合。
一個同步的web服務請求是一個高度的時間耦合的狀況。
如今你可能疑惑服務A怎麼可以繼續進行,在它須要服務B一些操做的狀況下。答案是用隊列異步通訊。有些人或許認爲一個web服務請求就能夠實現異步,可是,這裏有些區別。服務A可能在收到一個迴應的要求以前就中止了,而這可能致使一個web服務請求失敗。
伴隨着消息,事物老是在同一時間向一個方向移動。服務A向服務B是一個操做,而且老是會結束。服務B向服務A是另外一個活動,而且老是會結束。
模式
請求/響應(request/response)
請求一個終結點,表現爲一個肯定方法,這裏,你發送一個命令消息:
bus.Send(new RequestMessage());
儘管這是一個很是簡單的模式,他將致使很是緊密的行爲耦合。這不必定是壞事,在不少狀況下,這是被明確要求的。
典型的處理命令消息的消息處理器和一些業務邏輯打交道,並處理消息。可是,有時仍是須要響應的。
響應(response)能夠是一個命令消息,也能夠是一個事件消息,你能夠單純的請求服務總線的回覆(reply)方法:
bus.Send(new ResponseMessage(), c => c.Reply());
響應或許,固然能夠,用事件教習解耦,可是這取決於實現者決定用哪一種方法。這將再也不是請求/響應,但也不是發佈/訂閱。請求/響應模式的優點是,它提供給請求提供了直接得到響應的能力,而發佈的消息將會致使全部的訂閱方收到一份消息的副本。
發佈/訂閱
這種模式將會致使在發佈者和訂閱者之間沒有行爲耦合。事實上,可能對於一個特定的事件消息,可能沒有任何一個訂閱者,可是或許也沒有一個這樣的場景,須要處於某種業務需求發佈一個事件,這意味着至少會有一個訂閱者。要發佈一個事件消息,你能夠像下面這樣:
bus.Publish(new EventMessage());
每一個訂閱者收到它本身的那份消息拷貝來進行處理。
這從本質上區別了須要被髮送給一個工做者(worker)處理的那些消息的分配。
消息分配
能夠預見,一箇中間點若是收到過多的工做就可能會它的處理過程可能會開始落後。這種狀況下,它可能改變消息分配的工做者節點。
一個終結點將會自動分配消息收到可用消息的工做者(worker)。一個終結點能夠被配置爲知分配消息,不處理,經過吧inbox標籤的distribute屬性設置爲true來實現。
因爲消息分發哦被集成到收件箱中處理,相同的節點只須要根據須要在不一樣的機器上做爲工做者被安裝屢次便可。你但願用來處理分配消息的終結點將會須要一個控制收件箱的配置,由於全部的Shuttle的消息須要被處理而不去在一個隊列中等待,就像是收件箱背後隱藏着成千的消息。每一個工做者在配置中這樣被定義,終結點的控制收件箱的分配策略是必須的:
<configuration>
<configSections>
<section name="serviceBus" type="Shuttle.ESB.Core.ServiceBusSection, Shuttle.ESB.Core"/>
</configSections>
<serviceBus>
<control
workQueueUri="msmq://./control-inbox-work"
errorQueueUri="msmq://./shuttle-error"/>
<inbox
distribute="true"
workQueueUri="msmq://./inbox-work"
errorQueueUri="msmq://./shuttle-error"/>
</serviceBus>
</configuration>
任何接收消息的終結點能夠被配置消息分發策略。
你接下來能夠安裝任意你須要的數量的工做者在任意你數量你但願的機器上,配置他們和一個分配者通訊。物理的分配者連通相關的全部工做者,構成一個消息的邏輯的終結點。工做者配置以下:
<configuration>
<configSections>
<section name="serviceBus" type="Shuttle.ESB.Core.ServiceBusSection, Shuttle.ESB.Core"/>
</configSections>
<serviceBus>
<worker
distributorControlWorkQueueUri="msmq:///control-inbox=work" />
<inbox
workQueueUri="msmq://./workerN-inbox-work"
errorQueueUri="msmq://./shuttle-error"
threadCount="15">
</inbox>
</serviceBus>
</configuration>
一旦應用程序配置文件包含了worker標籤,每一個線程變爲空閒都會向分配者發送一個消息,表名一個線程可用了。分配者將會發送消息向每一個可用的線程。
消息分配異常(Message Distribution Exceptions)
一些隊列技術不須要消息分配。用來替代工做者,另外一個終結點能夠消費同一個輸入隊列。這一方案須要中間人。因爲中間人其中的管理隊列,消息被消費者的每一個正常運行的線程消費着。消費者被建立的地點是無所謂的,因此隊列能夠被各類各樣的過程所消費。
中間人風格和msmq或者sql-based隊列不同,中間人的消息處理這是被承載着消費者線程的宿主所管理的。這裏,過程A不知道消息正在被過程B所消費,致使一個過程偷了另外一個過程的消息。
消息路由(message routing)
一般來講,每發送一個條消息,消息是一個命令。它並無必要非是一個命令,而且你能夠發送一個事件消息到一個特定的終結點,可是更多的每每不是,你將會發送一個命令。消息被髮送,經過調用服務總線實例上加載的一個相關的方法:
TransportMessage Send(object message);
TransportMessage Send(object message, Action<TransportMessageConfigurator> configure);
僅沒有RecipientInboxWorkQueueUri 設置的消息,將會被服務總線所路由。
若是你須要訪問任何可用消息的元數據, TranssportMessage外殼將會被返回。
Shuttle ESB使用一個IMessageRoutProvider接口的實現來決定在那裏發送消息。
public interface IMessageRouteProvider
{
IEnumerable<string> GetRouteUris(object message);
}
消息路由提供者的使用是在構造服務總線時被指定的:
bus = ServiceBus
.Create(c => c.MessageRouteProvider(new DefaultForwardingRouteProvider())
.Start();
DefaultMessageRouteProvider使用應用程序配置文件來決定向哪裏發送消息:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="serviceBus" type="Shuttle.ESB.Core.ServiceBusSection, Shuttle.ESB.Core"/>
</configSections>
<serviceBus>
<messageRoutes>
<messageRoute uri="msmq://serverA/inbox">
<add specification="StartsWith" value="Shuttle.Messages1" />
<add specification="StartsWith" value="Shuttle.Messages2" />
</messageRoute>
<messageRoute uri="sql://serverB/inbox">
<add specification="TypeList" value="DoSomethingCommand, Assembly" />
</messageRoute>
<messageRoute uri="msmq://serverC/inbox">
<add specification="Regex" value=".+[Cc]ommand.+" />
</messageRoute>
<messageRoute uri="sql://serverD/inbox">
<add specification="Assembly" value="TheAssemblyName" />
</messageRoute>
</messageRoutes>
</serviceBus>
</configuration>
每一個IMessageRouteProvider的實現能夠決定路由,然而,它須要從消息中得到目的地。一個典型的場景,也是DefaultMessageRouteProvider的工做方式,是用類型全稱來決定目的地。
請注意:每一個消息類型可能僅僅被送到一個終結點(使用send)。