Command Bus 就是命令总线。
命令总线 是一种将 命令 与 执行 分离的设计思想。
以创建一个系统用户为示例,普通的代码大致如下 :
public class UserController : ControllerBase
{
// 用户服务,可能通过某些 IOC / DI 注入得到
private readonly IUserService userService;
// 为了简化,只以登录名和密码作为参数。
[HttPost("Create")]
public Guid Create(string loginName, string password)
{
Guid newId = this.userService.Create(new User()
{
LoginName = loginName,
Password = password
});
return newId;
}
}
上面的代码已经将 Controller 与 Service的实现 分离了, 编写者只要通过约定好的 IUserService 就可以进行各种操作了。
虽然耦合度已经大幅度降低, 但是依赖存在着 UserController 对 IUserService 的依赖。
让 UserController.Create 只关心对用户的创建,而不关心由哪个 Service 创建,可以让耦合度变得更低。
Command Bus,这是一个让调用方仅关心命令本身,而不需要关心由谁来处理的结构。 使用 Command Bus 后 Controller 的代码大体如下
// UserController.cs
public class UserController : ControllerBase
{
// 命令总线,可能通过某些 IOC / DI 注入得到
private readonly ICommandBus commandBus;
// 为了简化,只以登录名和密码作为参数。
[HttPost("Create")]
public Guid Create(string loginName, string password)
{
// 构建一个命令
CreateUserCommand command = new CreateUserCommand(loginName, password);
// 将命令交给命令总线,不用关心由谁实现
Guid newId = this.commandBus.Dispatch<CreateUserCommand, Guid>(command);
return newId;
}
}
我们可以在一个合适的位置编写一个命令执行类来处理这个指令
// Handlers/CreateUserCommandHandler.cs
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand>
{
private readonly IUserService userService;
public object Handle(CreateUserCommand command)
{
return userService.Create(new User()
{
LoginName = command.LoginName,
Password = command.Password
});
}
}
就像这样,我们把 执令 和 执行分离 了。 更低的耦合度,会带来更好的维护性。
更多 命令总线 的信息,可以点击 CQRS 查阅更多文章。
访问 nuget 页面,使用相应的命令行下载最新版。
Command 必须实现 ICommand 接口, 该接口目标是一个空接口,没有任何需要实现的内容。
CommandHandler 必须实现 ICommandHandler<TCommand> 接口, 对实现其 Handle 方法, Handle 方法可以返回 object ,
在通常情况下,我们不需要对 CommandHandler 的返回类型做抽象, 在特殊的使用场景下,我们可以对 CommandHandler 的返回类型做抽象,一个常见的例子就是 MVC 下的 ActionResult。
MVC 就是一种 CommandBus 的应用,Http 请求就是 Command, Controller 就是 Handler
CommandHandlerFactory 是一个注册了所有 CommandHandler 组件,它被设计成了允许用户自己开发的模式,只需要实现 ICommandHandlerFactory 就可以成为一个 CommandHandlerFactory。
目前系统里包含了以下两个 CommandHandlerFactory
- DefaultCommandHandlerFactory, 手动注册 Handler
- ConfigurationCommandHandlerFactory, 通过 App.Config / Web.Config 注册 Handler,如何配置见最后的 《配置文件结构》
ICommandHandlerFactory factory = new xxxxxCommandHandlerFactory();
// DefaultCommandBus 被设计了成了通过构造函数注入工厂的方式
// 你可以使用任何一种 IOC / DI 框架,对其进行自动组装。
ICommandBus commandBus = new DefaultCommandBus(factory);
HelloCommand command = new HelloCommand();
// 只要你所编写的 Handler 能够从 ICommandHandlerFactory 中产出,这里一定会执行到
commandBus.Dispatch(command);
2.0.0 版本起,CommandBus 不再返回值,
而是将 TCommand 视作即包含了执行命令的参数,也包含了执行命令的结果。
比如一个获取用户名称的命令,使用只读属性表示向 CommandHandler 提供参数,使用只写属性表示向 CommandHandler 提供执行结果。
由于命令往往只有属性组成,所以强烈建议你的命令都是 interface
public interface IGetUserNameCommand : ICommand
{
string UserName { set; }
int UserId { get; }
}
你可以为你任何一种业务模式赋予命令的功能,让它能够通过事件总线获取相应的属性
// OrderModel.cs
public class OrderModel : IGetUserNameCommand
{
public OrderEntity Order { get; private set; }
public int UserId => Order.CreateUserId;
public String UserName { get; set; }
}
// code
OrderModel order = this.OrderService.GetById(1);
commandBus.Dispatch(order);
string createUserName = order.UserName;