ViennaNET:一组后端库。第2部分

Raiffeisenbank .NET开发社区继续对ViennaNET进行简短细分。可以在第一部分中了解到我们如何以及为什么要这样做



在本文中,我们将介绍尚未使用的用于处理分布式事务,队列和数据库的库,这些库可以在GitHub的存储库中找到(源在此处),而Nuget包在此处







维也纳网



当项目过渡到DDD和微服务体系结构时,当业务逻辑分布在不同的服务上时,就会出现与实现分布式事务机制有关的问题,因为许多场景经常会同时影响多个领域。您可以在Chris Richardson撰写的“ Microservices Patterns”一书中了解有关此类机制的更多信息



在我们的项目中,我们实现了一个简单但有用的机制:一个传奇,或者更确切地说是基于编排的传奇。其实质如下:在某些业务场景中,有必要按顺序在不同的服务中执行操作,而在任何步骤出现问题的情况下,都必须调用提供了该步骤的所有先前步骤的回滚过程。因此,在传奇的结尾,无论成功如何,我们都会在所有域中获得一致的数据。



我们的实现仍处于其基本形式,并且与使用任何与其他服务进行交互的方法无关。使用它并不难:可以从基本抽象类SagaBase <T>继承,其中T是您的上下文类,您可以在其中存储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);


可以在这里以及在带有测试的程序集中找到不同实现的完整示例



ViennaNET.Orm。*



一组用于通过Nhibernate处理各种数据库的库。我们将DB-First方法与Liquibase一起使用,因此仅存在用于处理成品数据库中的数据的功能。



ViennaNET.Orm.Seedwork ViennaNET.Orm-分别包含基本接口及其实现的主程序集。让我们详细介绍它们的内容。



接口IEntityFactoryService和其实现EntityFactoryService是使用数据库的主要起点,因为在这里创建了工作单元,使用特定实体的存储库以及命​​令执行程序和直接SQL查询。有时,限制类使用数据库的功能很方便,例如,启用只读数据。对于这种情况,它IEntityFactoryService具有一个祖先-一个IEntityRepositoryFactory仅声明用于创建存储库的方法的接口



为了直接访问数据库,使用了提供程序机制。对于我们数据库中使用的每个团队,都有自己的实现: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": "..."
  }
],


示例ApplicationContext:



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


如果未指定连接标识符,则将使用名为“默认”的连接。



使用标准NHibernate工具可将实体直接映射到数据库表。您可以通过xml文件和类使用描述。为了方便在单元测试中编写存根存储库,提供了一个库ViennaNET.TestUtils.Orm



使用ViennaNET.Orm的完整示例*可以在此处找到



ViennaNET消息传递*



一组用于处理队列的库。



为了使用队列,选择了与各种DBMS相同的方法,即在使用库方面最大可能的统一方法,而与使用的队列管理器无关。该库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



我们不仅将队列用于不同系统之间的集成,而且还将队列用于一个应用程序的微服务之间的通信,例如,在传奇的框架内。这导致需要与消息一起传送诸如用户名,端到端日志记录的请求ID,源ip地址和授权数据之类的辅助数据。为了实现此数据的转发,开发ViennaNET.CallContext一个库,该库允许存储来自进入服务的请求的数据。在这种情况下,如何通过队列或通过Http发出请求并不重要。然后,在发送出站请求或消息之前,将数据从上下文中取出并放入标头中。因此,下一个服务接收辅助数据并以相同的方式处理它们。



感谢您的关注,我们期待您的评论和请求!



All Articles