ViennaNET:一組後端程式庫。 第2部分

Raiffeisenbank .NET 開發者社群繼續簡要回顧 ViennaNET 的內容。 關於我們如何以及為何走到這一步, 你可以閱讀第一部分.

在本文中,我們將介紹尚未考慮的用於處理分散式事務、佇列和資料庫的庫,這些庫可以在我們的 GitHub 儲存庫中找到(來源在這裡),和 Nuget 套件在這裡.

ViennaNET:一組後端程式庫。 第2部分

維也納NET.Sagas

當專案轉向DDD和微服務架構時,當業務邏輯分散在不同的服務中時,就會出現一個需要實現分散式事務機制的問題,因為許多場景往往會同時影響多個領域。 您可以更詳細地了解此類機制,例如, 在《微服務模式》一書中,Chris Richardson.

在我們的專案中,我們實現了一個簡單但有用的機制:傳奇,或者更確切地說是基於編排的傳奇。 其本質是這樣的:有一個業務場景,需要在不同的服務中順序執行操作,如果任何一步出現問題,都需要調用之前所有步驟的回滾過程,其中假如。 因此,在傳奇結束時,無論成功與否,我們都會在所有領域收到一致的數據。

我們的實作仍然以其基本形式進行,並且不依賴與其他服務互動的任何方法的使用。 使用起來並不困難:只需建立基本抽象類別 SagaBase<T> 的後代,其中 T 是您的上下文類,您可以在其中儲存 saga 工作所需的初始資料以及一些中間結果。 上下文實例將在執行期間傳遞到所有步驟。 Saga本身是一個無狀態類,因此可以將實例作為Singleton放入DI中以獲得必要的依賴關係。

廣告範例:

public class ExampleSaga : SagaBase<ExampleContext>
{
  public ExampleSaga()
  {
    Step("Step 1")
      .WithAction(c => ...)
      .WithCompensation(c => ...);
	
    AsyncStep("Step 2")
      .WithAction(async c => ...);
  }
}

呼叫範例:

var saga = new ExampleSaga();
var context = new ExampleContext();
await saga.Execute(context);

可以查看不同實現的完整範例 這裡 並與組裝 測試.

維也納NET.Orm.*

一組用於透過 Nhibernate 處理各種資料庫的庫。 我們使用 Liquibase 的 DB-First 方法,因此僅具有處理現成資料庫中的資料的功能。

ViennaNET.Orm.Seedwork и ViennaNET.Orm – 分別包含基本介面及其實作的主組件。 讓我們更詳細地看看它們的內容。

接口 IEntityFactoryService 及其實施 EntityFactoryService 是使用資料庫的主要起點,因為工作單元、用於處理特定實體的儲存庫以及命令執行器和直接 SQL 查詢都是在此處建立的。 有時,限制類別使用資料庫的功能很方便,例如,提供僅讀取資料的能力。 對於此類情況 IEntityFactoryService 有一個祖先-接口 IEntityRepositoryFactory,它僅聲明一個用於建立儲存庫的方法。

為了直接存取資料庫,使用了提供者機制。 我們團隊中使用的每個 DBMS 都有自己的實作: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

同時,多個提供者可以同時在一個應用程式中註冊,這使得例如在一個服務的框架內,無需任何修改基礎設施的成本,就可以從一個服務進行逐步遷移。一個 DBMS 到另一個 DBMS。 選擇所需連接以及特定實體類別(為其編寫映射到資料庫表)的提供者的機制是透過在 BoundedContext 類別(包含用於註冊域實體的方法)或其後繼類別中註冊實體來實現的ApplicationContext(包含註冊應用程式實體、直接請求和命令的方法),其中配置中的連線標識符被接受為參數:

"db": [
  {
    "nick": "mssql_connection",
    "dbServerType": "MSSQL",
    "ConnectionString": "...",
    "useCallContext": true
  },
  {
    "nick": "oracle_connection",
    "dbServerType": "Oracle",
    "ConnectionString": "..."
  }
],

應用程式上下文範例:

internal sealed class DbContext : ApplicationContext
{
  public DbContext()
  {
    AddEntity<SomeEntity>("mssql_connection");
    AddEntity<MigratedSomeEntity>("oracle_connection");
    AddEntity<AnotherEntity>("oracle_connection");
  }
}

如果未指定連線 ID,則將使用名為「default」的連線。

實體到資料庫表的直接對應是使用標準 NHibernate 工具實現的。 您可以透過 xml 檔案和類別來使用描述。 為了方便在單元測試中編寫存根儲存庫,有一個庫 ViennaNET.TestUtils.Orm.

可以找到使用 ViennaNET.Orm.* 的完整範例 這裡.

ViennaNET.訊息傳遞。*

一組用於處理佇列的庫。

Для работы с очередями был выбран такой же подход, что и с различными СУБД, а именно – максимально возможный унифицированный подход с точки зрения работы с библиотекой, независимо от используемого менеджера очередей. Библиотека ViennaNET.Messaging 正是這種統一的責任者,並且 ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue 分別包含 IBM MQ、RabbitMQ 和 Kafka 的適配器實作。

使用佇列時,有兩個過程:接收訊息和發送訊息。

考慮接收。 這裡有 2 個選項:連續收聽和接收單一訊息。 要不斷監聽佇列,首先必須描述繼承自的處理器類 IMessageProcessor,它將負責處理傳入的訊息。 接下來,它必須「連結」到特定隊列;這是透過在 IQueueReactorFactory 指示配置中的佇列標識符:

"messaging": {
    "ApplicationName": "MyApplication"
},
"rabbitmq": {
    "queues": [
      {
        "id": "myQueue",
        "queuename": "lalala",
        ...
      }
    ]
},

開始聆聽的範例:

_queueReactorFactory.Register<MyMessageProcessor>("myQueue");
var queueReactor = queueReactorFactory.CreateQueueReactor("myQueue");
queueReactor.StartProcessing();

然後,當服務啟動並呼叫該方法開始監聽時,指定佇列中的所有訊息都會轉到對應的處理器。

在工廠介面中接收單一訊息 IMessagingComponentFactory 有一個方法 CreateMessageReceiver這將建立一個接收者,等待指定佇列中的消息:

using (var receiver = _messagingComponentFactory.CreateMessageReceiver<TestMessage>("myQueue"))
{
    var message = receiver.Receive();
}

發送訊息 你需要使用相同的 IMessagingComponentFactory 並創建一個訊息發送者:

using (var sender = _messagingComponentFactory.CreateMessageSender<MyMessage>("myQueue"))
{
    sender.SendMessage(new MyMessage { Value = ...});
}

序列化和反序列化訊息有三種現成的選項:僅文字、XML 和 JSON,但如果需要,您可以輕鬆建立自己的介面實現 IMessageSerializer и IMessageDeserializer.

我們嘗試保留每個佇列管理器的獨特功能,例如 ViennaNET.Messaging.MQSeriesQueue 不僅可以發送文本,還可以發送字節訊息,並且 ViennaNET.Messaging.RabbitMQQueue 支援路由和即時佇列。 我們的 RabbitMQ 適配器包裝器也實作了某種 RPC:我們發送一條訊息並等待來自特殊臨時佇列的回應,該佇列僅為一個回應訊息而建立。

這裡 使用具有基本連接細微差別的隊列的範例.

ViennaNET.CallContext

我們使用隊列不僅用於不同系統之間的集成,還用於同一應用程式的微服務之間的通信,例如在傳奇中。 這導致需要與訊息一起傳輸諸如用戶登入、端到端日誌記錄的請求識別碼、來源IP位址和授權資料等輔助資料。 為了實現這些資料的轉發,我們開發了一個函式庫 ViennaNET.CallContext,它允許您儲存進入服務的請求的資料。 在這種情況下,透過佇列或透過 Http 發出請求的方式並不重要。 然後,在發送傳出請求或訊息之前,從上下文中獲取資料並將其放置在標頭中。 因此,下一個服務接收輔助資料並以相同的方式對其進行管理。

感謝您的關注,我們期待您的評論和請求!

來源: www.habr.com

添加評論