原文地址:https://docs.particular.net/tutorials/intro-to-nservicebus/2-sending-a-command/

侵删。

能够发送和接收message是任何NServiceBus 系统的主要特征。在两个进程之间传递持久化的message能使这个传递更加可靠,哪怕其中一个进程暂时不可用。在这个课程中我们将会展示如何发送并且处理一个信息。在接下来的15-20分钟里,你会学到如何定义message和message handler,如何在本地发送和接收message并且使用内置的日志功能。

什么是message

message是一组数据,他们通过单向communication在两个endpoint之间传递。在NServiceBus中,我们将message定义成一个简单的类。

在这节课中,我们将会关注commands。在第四节课:发布事件中,我们会展开讲到event。

要定义一个command,先生成一个类然后让它继承ICommand标记接口。

public class DoSomething :
ICommand
{
public string SomeProperty { get; set; }
}

这个标记接口没有实现任何方法,只是让NServiceBus 知晓这个类是一个command,因此它可以在开启一个endpoint的时候构建一些关于message类型的元数据。你在这个message中构建的任何属性都构成了message数据本身。

command类的名字一样也很重要。一个command是做一件事情的请求,因此它应当以一种祈使语气的方式来命名。PlaceOrder 和ChargeCreditCard 都是很好的command命名方式,因为他们看上去很像一个“请求”。PlaceOrder 将会下一个订单,而ChargeCreditCard 将会在信用卡中扣款。然而CustomerMessage就不是一个好名字。它只看失去不是那么像一个请求,并且不是非常一目了然。其他开发者应当一看名字就知道这个command的目的是什么。

command的名字也应当传递一些业务含义。UpdateCustomerPropertyXYZ虽然比CustomerMessage 更加一目了然,然而也不是一个好的command名字,因为它仅仅关注数据的操作而没有业务含义在里面。MarkCustomerAsGold,或者类似于这样的名字,就更加面向业务了——它也许是一个更加的选择。

当发送一个message的时候,endpoint的序列化工具会将DoSomething 类的实例序列化,然后把它添加到即将发出到队列的message中去。在另一头,接收方endpoint会将这个message反序列化成一个实例来在代码中使用。

message甚至可以包含一些子对象或者集合。(这个由序列化工具类型来决定)

public class DoSomethingComplex :
ICommand
{
public int SomeId { get; set; }
public ChildClass ChildStuff { get; set; }
public List<ChildClass> ListOfStuff { get; set; } = new List<ChildClass>();
} public class ChildClass
{
public string SomeProperty { get; set; }
}

message是两个endpoint之间的协议。message的任何改变都会对发送方和接收方产生影响。你的message中包含的的属性越多,就有越多产生变动的原因,因此确保你的message越精简越好。

同时你不能再你的message类中嵌入逻辑。每一个message都应该只包含自动属性不能有包含计算的属性或者方法。同样的,通过默认的无参构造函数来实例化集合属性也是一个很好的做法,就像上面那样,因此你永远不要担心会产生一个null的集合。

实际上,message应当只能包含数据。通过确保你的message足够小,并且赋予它清晰的目的,你就可以让你的代码更加容易理解和扩展。

组织messages

message是数据协议,他们在各个endpoint之间共享。因此你实际上不能把这些类放在各个endpoint的相同程序集中。他们应该在分布在不同的类库里。

message 的程序集应该是独立的,意味着他们应当仅仅包含NServiceBus message类型和任何被message自身需要的类型。例如,如果一个message使用了一个美剧类型作为他的一个属性,这个枚举类就也应该在message程序集中。

message程序集不应当依赖除了.NET Framework类库和NServiceBus 核心库之外的程序集,因为ICommand 接口位于NServiceBus 核心库中。

参照这些方法会让你的message协议在以后更加容易扩展。

处理message

我们构造了message handler来处理message,这个类实现了IHandleMessages<T>接口,T是一个message类型。一个message handler示例如下:

public class DoSomethingHandler :
IHandleMessages<DoSomething>
{
public Task Handle(DoSomething message, IMessageHandlerContext context)
{
// Do something with the message here
return Task.CompletedTask;
}
}

IHandleMessages<T> 实现了一个handle方法,NServiceBus 将会在一个T类型的message(在DoSomething中)到达的时候调用这个方法。handle方法接收message和一个包含处理message上下文API的IMessageHandlerContext实现。

除了显式返回一个task,你也可以在一个handler方法前面添加async关键字:

public class DoSomethingHandler :
IHandleMessages<DoSomething>
{
public async Task Handle(DoSomething message, IMessageHandlerContext context)
{
// Do something with the message here
}
}

如果你想要学习更多使用async 方法的构建handler方式,可以参阅Asynchronous Handlers

一个类可以实现多个IHandleMessages<T>来处理多种message类型。这样就可以将一些逻辑上相关联的handler组成一组,尽管处理每个message都会实例化出一个新的对象。

public class DoSomethingHandler :
IHandleMessages<DoSomething>,
IHandleMessages<DoSomethingElse>
{
public Task Handle(DoSomething message, IMessageHandlerContext context)
{
Console.WriteLine("Received DoSomething");
return Task.CompletedTask;
} public Task Handle(DoSomethingElse message, IMessageHandlerContext context)
{
Console.WriteLine("Received DoSomethingElse");
return Task.CompletedTask;
}
}

当NServiceBus 开启的时候,它将会找到所有的这些message handler类并且自动将他们合并在一起,因此当message到的时候他们都会被调用。这里不需要进行任何的初始化和配置。

handler在一个类还是多各类中实现都是一样的。当NServiceBus 启动的时候,它会找到所有的message handler然后将他们合并在一起,不需要任何配置。这样的组合方法是为了让你的代码更加清晰。

练习

现在让我们继续使用上节课构建的解决方案,将它进行一些更改让它能够发送message。你也可以直接拿一个已经完成的上节课的例子来开始。

当我们完成的时候,ClientUI endpoint会向自己发送一个 PlaceOrder ,然后处理这个message,就像下面这个图描述的这样:

创建一个message程序集

为了在endpoint之间分享message,它们必须各自独立在不同的程序集中间,现在让我们来创建这些程序集。

1.在这个解决方案中,生成一个新的项目然后选择类库项目类型。

2.把项目的名称设置成Message

3.把自动生成的class1.cs文件删掉

4.添加 NServiceBus  NuGet 包到这个项目中

5.在ClientUI 项目中,添加对Message项目的引用

创建一个message

我们将要在一个叫Commands的文件夹创建我们第一个command。

1.在Message项目中,创建一个新的叫做PlaceOrder的类

2.将PlaceOrder标记成public并且实现ICommand接口

3.添加一个string类型的公共OrderId属性

.NET Framework 在System.Windows.Input名称空间下面包含一个它定义的ICommand 接口。因此在你自动解析名称空间的时候,你要选择NServiceBus.ICommand。大多数你需要的类型都会在NServiceBus名称空间中。

完成之后,你的PlaceOrder 类应该是这样子的:

namespace Messages
{
public class PlaceOrder :
ICommand
{
public string OrderId { get; set; }
}
}

创建一个handler

现在我们已经定义了一个message了,我们可以创建一个相应的message handler。现在,然我们处理在ClientUI endpoint本地的message。

1.在ClientUI 项目中,创建一个叫做PlaceOrderHandler的类。

2.将这个handler类定义成public,然后实现IHandleMessages<PlaceOrder>接口。

3.添加一个日志实例,这能够让你使用和NServiceBus用的一样的日志系统。它在Console.WriteLine()上有一个很重要的优点:日志的信息都会在控制台上面展现。使用下面的代码将日志实例添加到你的handler类中:

static ILog logger = LogManager.GetLogger<PlaceOrderHandler>();

4.在handle方法中,使用logger来记录PlaceOrder message的接收,包括OrderId 的值:

5.因为我们所有在这个handler中做的事情都是同步的,返回Task.CompletedTask.

完成之后,你的PlaceOrderHandler 类应该是这样子的:

public class PlaceOrderHandler :
IHandleMessages<PlaceOrder>
{
static ILog log = LogManager.GetLogger<PlaceOrderHandler>(); public Task Handle(PlaceOrder message, IMessageHandlerContext context)
{
log.Info($"Received PlaceOrder, OrderId = {message.OrderId}");
return Task.CompletedTask;
}
}

因为LogManager.GetLogger(..); 的开销很大,所以请将logger实现作为成静态成员。

发送一个message

现在我们有了一个message和一个处理它的handler了,让我们来发送message。

在ClientUI 项目中,我们暂时先按回车键来终止endpoint。现在让我们来创建一个循环来让它更加具有互动性,我们可以使用键盘输入来决定是发送message还是退出。

将下面的方法添加到Program.cs 文件中:

static ILog log = LogManager.GetLogger<Program>();

static async Task RunLoop(IEndpointInstance endpointInstance)
{
while (true)
{
log.Info("Press 'P' to place an order, or 'Q' to quit.");
var key = Console.ReadKey();
Console.WriteLine(); switch (key.Key)
{
case ConsoleKey.P:
// Instantiate the command
var command = new PlaceOrder
{
OrderId = Guid.NewGuid().ToString()
}; // Send the command to the local endpoint
log.Info($"Sending PlaceOrder command, OrderId = {command.OrderId}");
await endpointInstance.SendLocal(command)
.ConfigureAwait(false); break; case ConsoleKey.Q:
return; default:
log.Info("Unknown input. Please try again.");
break;
}
}
}

当我们想要下一个订单的时候,我们先仔细地观察这个例子。为了生成一个 PlaceOrder 的command,我们简单地实例化一个PlaceOrder 类,然后给OrderId一个唯一值。记录完这些细节的之后,我们可以通过调用SendLocal 方法来发送它。

SendLocal(object message)是一个定义在IEndpointInstance 接口的方法,就像我们在这里使用的,它也定义在IMessageHandlerContext 接口中,这个接口在我们定义我们的message handler的时候我们也见到过。Local 意味着我们不把message发送到外面的endpoint去(位于一个不同的进程),因此我们倾向于在发送同时也接收message的endpoint中处理它。使用SendLocal(), 我们不需要其他任何的信息告诉message它要被发送到哪里。

在这节课程中,我们使用了SendLocal(而不是其他的更加常用的Send方法)。这样我们可以探索如何定义,发送和处理message,不需要第二个endpoint来处理它们。通过SendLocal方法,我们也不需要定义路由规则来控制这个message发送到哪里。我们将会在下一个课程中学习这些东西。

因为SendLocal() 返回一个Task,我们需要保证合理地await它。

现在让我们修改这个AsyncMain ,调用新的RunLoop 方法:

var endpointInstance = await Endpoint.Start(endpointConfiguration)
.ConfigureAwait(false); // Remove these two lines
Console.WriteLine("Press Enter to exit...");
Console.ReadLine(); // Replace with:
await RunLoop(endpointInstance); await endpointInstance.Stop()
.ConfigureAwait(false);

运行解决方案

现在我们可以运行这个解决方案。我们只要在控制台中输入P,一个command message就会被发送然后在同一个项目中的handler里面处理。

INFO  ClientUI.Program Press 'P' to place an order, or 'Q' to quit.
p
INFO ClientUI.Program Sending PlaceOrder command, OrderId = 1fb61e01-34a3-4562-82b1-85278565b59d
INFO ClientUI.Program Press 'P' to place an order, or 'Q' to quit.
INFO ClientUI.PlaceOrderHandler Received PlaceOrder, OrderId = 1fb61e01-34a3-4562-82b1-85278565b59d
p
INFO ClientUI.Program Sending PlaceOrder command, OrderId = d9e59362-ccf4-4323-8298-4bbc052fb877
INFO ClientUI.Program Press 'P' to place an order, or 'Q' to quit.
INFO ClientUI.PlaceOrderHandler Received PlaceOrder, OrderId = d9e59362-ccf4-4323-8298-4bbc052fb877

需要注意的是在发送message之后,ClientUI.Program的提示在ClientUI.PlaceOrderHandler 确认接收到message之后显示。这个是因为和直接调用Handle方法不一样,这个message是异步发送的,然后控制台立即返回到RunLoop(这个方法会立即重复提示出信息)。很快,当message被接收和处理之后,我们会看到Received PlaceOrder 的提示。

总结

在这节课中我们学习了关于message,message的程序集和message handler。我们创建了一个message和一个handler然后我们使用SendLocal() 方法来发送message到同一个endpoint中。

在下节课中,我们会创建第二个messaging endpoint,将我们的message处理转移到那里去。然后我们将会配置ClientUI ,将message发送到新的endpoint中。我们也会观察如何接受方endpoint不在线的时候我们发送message过去会发生什么。

NServiceBus入门:发送一个命令(Introduction to NServiceBus: Sending a command)的更多相关文章

  1. NServiceBus入门:发布事件(Introduction to NServiceBus: Publishing events)

    原文地址:https://docs.particular.net/tutorials/intro-to-nservicebus/4-publishing-events/ 侵删. 这个教程到目前为止,我 ...

  2. NServiceBus入门:多个endpoint(Introduction to NServiceBus: Multiple endpoints)

    原文地址:https://docs.particular.net/tutorials/intro-to-nservicebus/3-multiple-endpoints/ 侵删. 目前为止,我们只是在 ...

  3. NServiceBus 入门2

    NServiceBus官方文档翻译(二)NServiceBus 入门   在这篇教程中我们将学习如何创建一个非常简单的由客户端向服务端发送消息的订单系统.该系统包括三个项目:Client.Server ...

  4. NServiceBus官方文档翻译(二)NServiceBus 入门

    在这篇教程中我们将学习如何创建一个非常简单的由客户端向服务端发送消息的订单系统.该系统包括三个项目:Client.Server 和 Messages,我们将按照以下步骤来完成这个任务. 创建 Clie ...

  5. 【Linux】将一个命令的输出发送给另外一个命令

    一个命令的输出可以作为下一个命令的输入,下一个命令的输出又会传递给下一个命令 我们通常使用管道和子shell的方法来组合多个命令的输出 格式 $ cmd1 |cmd2 | cmd3 这里的3个组合命令 ...

  6. 利用Node.js的Net模块实现一个命令行多人聊天室

    1.net模块基本API 要使用Node.js的net模块实现一个命令行聊天室,就必须先了解NET模块的API使用.NET模块API分为两大类:Server和Socket类.工厂方法. Server类 ...

  7. ASP.NET Core 1.0 入门——了解一个空项目

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  8. mongodb新手入门,mongodb命令学习

    下面来总结一下mongodb新手入门的常用命令吧.要是您是mongodb新手,可以看下. 1,show dbs 查询mongodb里面的数据库列表 如果想查看当前连接在哪个数据库下面,可以直接输入db ...

  9. Linux入门_2-基础命令

    Linux入门-基础命令 目录 日期命令date 修改时区 日历命令cal 关机启动命令halt,reboot,poweroff whoami.who.who am i.w screen ...

随机推荐

  1. nodejs 优雅的连接 mysql

    1.mysql 及 promise-mysql nodejs 连接 mysql 有成熟的npm包 mysql ,如果需要promise,建议使用 promise-mysql: npm:https:// ...

  2. bzoj 1015 星球大战starwar

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1015 题解: 如果按照题目的意思,每次删点.删边太困难了……于是采用逆向思维,构造出最后的 ...

  3. STL不同容器的使用方法

    以下内容摘自:http://blog.csdn.net/u014465639/article/details/70241850 1.vector(需要导入头文件#include <vector& ...

  4. DNS解析原理与Bind部署DNS服务

    DNS是什么? DNS(Domain Name System,域名系统)是互联网上最核心的带层级的分布式系统,它负责把域名转换为IP地址.反查IP到域名的反向解析以及宣告邮件路由等信息,使得基于域名提 ...

  5. LeetCode解题报告—— Word Search & Subsets II & Decode Ways

    1. Word Search Given a 2D board and a word, find if the word exists in the grid. The word can be con ...

  6. Git----创建远程分支,并将文件上传到创建的远程分支上

    1.首先创建一个远程仓库 2.将远程仓库克隆到本地 (1)本地新建文件夹,命令行进入文件夹,执行clone操作 (2) git clone git@github.com:Lucky-Syw/lucky ...

  7. JMX monitor weblogic 总结

    https://blog.csdn.net/joy_91/article/details/42774839

  8. NoSQL 数据库应用

      类型 部分代表 特点 列存储 Hbase Cassandra Hypertable 顾名思义,是按列存储数据的.最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查 ...

  9. 战火魔兽CJQ圣印问题

    本来一直是玩的T的. 一次偶然机会打了次团本,用CJQ(毒蛇),在副本中问CJQ用什么圣印 有人说命令,有人说腐蚀... 对此做先研究 无BUFF木桩测试:5分钟(开sp翅膀,不踩奉献,技能什么好了按 ...

  10. CodeForces 779A Pupils Redistribution

    简单题. 因为需要连边的人的个数一样,又要保证和一样,所以必须每个数字的个数都是一样的. #include<map> #include<set> #include<cti ...