CQRS模式 - 接口

Rub*_*man 3 interface cqrs

我是CQRS模式的新手,但我想了解为什么要使用两个接口:

public interface IQuery{}
public interface ICommand{}
Run Code Online (Sandbox Code Playgroud)

而不是只有一个接口(例如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);
    }
}
Run Code Online (Sandbox Code Playgroud)

kay*_*ess 10

如果我得到正确的话你会在这里混淆首字母缩略词.从你的问题看来,你似乎并没有真正询问命令和查询责任隔离模式,但你可能会问到命令查询分离原则.

在这种情况下,简单的基础是:

命令

更改系统的状态但不返回值

查询

返回结果并且不要更改系统的可观察状态(没有副作用).

我将尝试演示具有通用接口(及其实现)与非通用接口之间的区别.此演示中显示的类似思维方式适用于通用查询处理程序.

解决问题的技术方面

通用命令处理程序接口如下:

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}
Run Code Online (Sandbox Code Playgroud)

其示例实施:

public class ExampleCommandHandler : ICommandHandler<ExampleCommand> 
{
    public void Handle(ExampleCommand command)
    {
        // Do whatever logic needed inside this command handler
    }
}
Run Code Online (Sandbox Code Playgroud)

Command传递给命令处理程序的示例:

public class ExampleCommand
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

最后是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用通用的好处 ICommandHandler

使用泛型命令处理程序可以让用户依赖于这种抽象而不是完全实现的命令处理程序.

如果您依赖于ExampleCommandHandler不实现泛型接口的确切实现,那么示例服务的构造函数将具有如下依赖关系:

public ExampleService(ExampleCommandHandler handler)
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您无法修饰此处理程序,因为它没有实现接口.

另外值得注意的是,使用此设置,您只需要对Command处理程序而不是服务的DoStuff()方法进行单元测试,因为行为位于Command处理程序中.

关于CQRS的说明

该图中的CQRS是与CQS等OOP方法相比的技术差异.


Voi*_*son 5

我想了解为什么应该使用两个接口,而不是仅一个接口

如果查询和命令具有不同的行为契约,则应使用两个接口。

因此,充实这个问题的方法是开始考虑在每个接口中声明什么签名,以及常见的签名是否真的意味着相同的事情。

命令和查询都是不可变的;如果您稍微思考一下,您就会意识到您确实不希望编码到命令或查询中的状态在运行中被修改。因此,在 CQS 意义上,接口中的函数都应该是查询 - 返回对象状态副本而不以任何方式更改它的函数。

鉴于此,命令和查询有什么共同点?也许是一堆元数据,以便调用正确类型的处理程序,以便您可以将响应与请求关联起来,等等。所有这些的抽象是消息(请参阅企业集成模式,Gregor Hohpe)。

所以你当然可以证明

public interface IMessage {...}
Run Code Online (Sandbox Code Playgroud)

所以你可能有

public interface ICommand : IMessage {...}
public interface IQuery : IMessage {...}
Run Code Online (Sandbox Code Playgroud)

取决于是否存在对所有命令通用而不是对所有消息通用的查询。您的实现可能甚至需要

public interface CQCommonThing : IMessage {...}
public interface ICommand : CQCommonThing {...}
public interface IQuery : CQCommonThing {...}
Run Code Online (Sandbox Code Playgroud)

但我很难想出任何属于查询和命令但不属于消息的查询示例。

另一方面,如果您正在考虑标记接口,您实际上并没有指定合同,如下所示:

public interface IQuery{}
public interface ICommand{}
Run Code Online (Sandbox Code Playgroud)

那么我不知道您有什么理由想要将它们组合起来,除非您可能想使用它们IMessage

回顾你的实施,看起来你在某个地方失去了情节。

public class AttachmentCommandHandler : BaseExecutionHandler,
    IExecutionHandler<CreateAttachments>
{
    public void Execute(CreateAttachments command)
    {
        command.Result =  command.Attachments.Select(x => UnitOfWork.Create(x)).ToList();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个命令“在我的记录系统中创建一堆实体”,还是一个查询“向我返回已创建实体的列表”?尝试同时执行这两项操作违反了 CQS,这暗示您走错了路。

换句话说,这里的构造

public interface IReturnCommand<TOutput>: ICommand
{
    TOutput Result { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

很奇怪——为什么在使用 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; }
}
Run Code Online (Sandbox Code Playgroud)

“看,妈妈,没有结果。” 调用者只需要确认该命令(以便它可以停止发送该命令);然后就可以通过查询来同步了。