我是CQRS模式的新手,但我想了解为什么要使用两个接口:
public interface IQuery{}
public interface ICommand{}
而不仅仅是一个界面(例如IExecutable,或其他......)
比你还有一个处理程序(例如IExecutionHandler,或其他......)
如果您愿意,您仍然可以将其拆分为ICommandExecutionHandler和IQueryExecutionHandler
更新:尝试
下一个代码只是我如何看待它的一个例子。我可能完全错误了......所以请分享您的疑虑/我的错误。我只是想了解这一点。
public interface IExecutable { }
public interface ICommand : IExecutable { }
public interface IReturnCommand<TOutput>: ICommand
{
TOutput Result { get; set; }
}
public interface IQuery<TOutput>: IExecutable
{
TOutput Result { get; set; }
}
public interface IExecutionHandler<in T>: IDisposable where T : IExecutable
{
void Execute(T executable);
}
public class CreateAttachments : IReturnCommand<List<Guid>>
{
public List<Attachment> Attachments { get; set; }
public List<Guid> Result { get; set; }
}
public abstract class BaseExecutionHandler : IDisposable
{
protected readonly IUnitOfWork UnitOfWork;
private bool _disposed;
protected BaseExecutionHandler(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
UnitOfWork.Dispose();
}
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
public class AttachmentCommandHandler : BaseExecutionHandler,
IExecutionHandler<CreateAttachments>
{
public AttachmentCommandHandler(IUnitOfWork unitOfWork) : base(unitOfWork)
{
}
public void Execute(CreateAttachments command)
{
command.Result = command.Attachments.Select(x => UnitOfWork.Create(x)).ToList();
}
}
public interface IProcessor : IDisposable
{
void Process<TExecutable>(TExecutable command) where TExecutable : IExecutable;
}
public class Processor : IProcessor
{
private readonly Dictionary<IExecutable, IExecutionHandler<IExecutable>> _handlers;
private readonly IUnitOfWork _unitOfWork;
private bool _disposed;
public Processor(IUnitOfWork unitOfWork)
{
_handlers = new Dictionary<IExecutable, IExecutionHandler<IExecutable>>();
_unitOfWork = unitOfWork;
}
private IExecutionHandler<IExecutable> GetHandler<TExecutable>(TExecutable executable) where TExecutable: IExecutable
{
if (_handlers.ContainsKey(executable))
{
return _handlers[executable];
}
var handlerType = typeof(IExecutionHandler<>).MakeGenericType(executable.GetType());
var handler = Activator.CreateInstance(handlerType, _unitOfWork) as IExecutionHandler<IExecutable>;
_handlers.Add(executable, handler);
return handler;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
foreach (var handler in _handlers.Values)
{
handler.Dispose();
}
}
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Process<TExecutable>(TExecutable executable) where TExecutable : IExecutable
{
var handler = GetHandler(executable);
handler.Execute(executable);
}
}
public class AttachmentController : ApiController
{
private readonly IProcessor _processor;
public AttachmentController(IProcessor processor)
{
_processor = processor;
}
public List<Guid> Post(List<Attachment> attachments)
{
var command = new CreateAttachments { Attachments = attachments };
_processor.Process(command);
return command.Result;
}
[EnableQuery]
public IQueryable<Attachment> Get()
{
var query = new GetAllAttachments { };
_processor.Process(query);
return query.Result;
}
protected override void Dispose(bool disposing)
{
_processor.Dispose();
base.Dispose(disposing);
}
}
答案 0 :(得分:10)
如果我弄错了,你会在这里混淆首字母缩略词。从您的问题看来,您似乎并没有真正询问Command and Query Responsibility Segregation模式,但您可能会问Command-Query Separation原则。
在这种情况下,basics简称:
更改系统状态但不返回值
返回结果,不要改变系统的可观察状态(没有副作用)。
我将尝试演示具有通用接口(及其实现)与非通用接口之间的区别。此演示中显示的类似思维方式适用于通用查询处理程序。
通用命令处理程序接口:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
其示例实施:
public class ExampleCommandHandler : ICommandHandler<ExampleCommand>
{
public void Handle(ExampleCommand command)
{
// Do whatever logic needed inside this command handler
}
}
传递给命令处理程序的示例Command
:
public class ExampleCommand
{
public int Id { get; set; }
public string Name { get; set; }
}
最后是Command处理程序的示例消费者:
public class ExampleService
{
private readonly ICommandHandler<ExampleCommand> commandHandler;
public ExampleService(ICommandHandler<ExampleCommand> handler)
{
commandHandler = handler;
}
public void DoStuff(int id, string name)
{
var command = new ExampleCommand
{
Id = id,
Name = name
};
commandHandler.Handle(command);
}
}
ICommandHandler
使用泛型命令处理程序可以让用户依赖于这种抽象而不是完全实现的命令处理程序。
如果您依赖于不会实现泛型接口的此ExampleCommandHandler
的确切实现,则示例服务的构造函数将具有以下依赖项:
public ExampleService(ExampleCommandHandler handler)
在这种情况下,您无法修饰此处理程序,因为它没有实现接口。
另外值得注意的是,使用此设置,您只需要对Command处理程序而不是服务的DoStuff()
方法进行单元测试,因为行为位于Command处理程序中。
此图中的CQRS与CQS等OOP方法相比存在技术差异。
答案 1 :(得分:4)
我想了解为什么你应该使用两个接口,而不只是一个接口
如果查询和命令具有不同的行为合同,则应使用两个接口。
因此,充实这个问题的方法是开始考虑在每个界面中声明的签名,以及常见的签名是否真的意味着同样的事情。
命令和查询都是不可变的;如果你仔细考虑一下,你会发现你真的不希望在飞行中修改编码到命令或查询中的状态。因此,接口中的函数应该都是CQS意义上的查询 - 返回对象状态副本而不以任何方式更改它的函数。
鉴于此,命令和查询有什么共同之处?也许是一堆元数据,以便调用正确类型的处理程序,以便您可以将响应与请求相关联,依此类推。所有这些的抽象是一个消息(参见企业集成模式,Gregor Hohpe)。
所以你当然可以证明
public interface IMessage {...}
所以你可能有
public interface ICommand : IMessage {...}
public interface IQuery : IMessage {...}
取决于是否存在对所有消息都不通用的所有命令共有的查询。您的实现可能甚至需要
public interface CQCommonThing : IMessage {...}
public interface ICommand : CQCommonThing {...}
public interface IQuery : CQCommonThing {...}
但是我很难想出任何与查询和命令共同的查询示例,这些查询和命令也不属于Message。
另一方面,如果您正在考虑标记接口,那么您实际上并没有指定合同,如下所示:
public interface IQuery{}
public interface ICommand{}
然后我不知道您是否想要将这些组合起来,除非您可能想要使用IMessage
。
检查您的实施情况,看起来您在某处丢失了情节。
public class AttachmentCommandHandler : BaseExecutionHandler,
IExecutionHandler<CreateAttachments>
{
public void Execute(CreateAttachments command)
{
command.Result = command.Attachments.Select(x => UnitOfWork.Create(x)).ToList();
}
}
这是一个命令“在我的记录系统中创建一堆实体”,还是一个查询“返回给我一个创建的实体列表”?试图同时做两件事都违反了CQS,这暗示你在错误的轨道上。
换句话说,这个构造在这里
public interface IReturnCommand<TOutput>: ICommand
{
TOutput Result { get; set; }
}
很奇怪 - 为什么在使用CQRS模式时你会需要这样的东西?
使用CreateAttachments作为示例,您当前的实现调用客户端发送到命令处理程序,并接收返回的匹配guid列表。这实施起来很有挑战性 - 但你不必选择这样做。在客户端上生成ID并使它们成为命令的一部分有什么问题?您认为客户端生成的GUID在某种程度上不如服务器生成的GUID唯一吗?
public class CreateAttachments : ICommand
{
// or a List<Pair<Guid, Attachment> if you prefer
// or maybe the ID is part of the attachment
public Map<Guid, Attachment> Attachments { get; set; }
}
“看,马,没有结果。”调用者只需要确认命令(以便它可以停止发送它);然后它可以通过查询同步。