CQRS模式 - 接口 [英] CQRS pattern - interfaces

查看:128
本文介绍了CQRS模式 - 接口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm new at the CQRS pattern, but I'd like to understand why you should use two interfaces:

public interface IQuery{}
public interface ICommand{}

而不只是一个接口(用于例如IExecutable,或其他......)

比你还有一个处理程序(例如IExecutionHandler,或者其他......)


如果你想要,您仍然可以将其拆分为ICommandExecutionHandler和IQueryExecutionHandler

Instead of just one interface (for example IExecutable, or whatever...)
Than you also have one handler (for example IExecutionHandler, or whatever...)
And if you want, you could still split it up into an ICommandExecutionHandler and a IQueryExecutionHandler

更新:尝试

下一个代码只是我如何看待它的一个例子。我可能完全错了......所以请分享您的疑虑/我的错误。我只是想了解这一点。

Next code is just an example of how I see it. It's possible that I'm completely wrong with this... so please share your concerns/my faults. I'm just trying to understand this.

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);
    }
}


推荐答案


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

I'd like to understand why you should use two interfaces, instead of just one interface

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

You should use two interfaces if Queries and Commands have different behavior contracts.

因此,充实这个问题的方法是开始考虑在每个接口中声明哪些签名,以及是否或者不是常见的那些真正意味着同样的事情。

So the way to flesh out this question is to start to consider what signatures would be declared in each interface, and whether or not the common ones really mean the same thing.

命令和查询都是不可变的;如果你仔细考虑一下,你会发现你真的不希望在飞行中修改编码到命令或查询中的状态。因此,接口中的函数应该都是CQS意义上的查询 - 返回对象状态副本而不以任何方式更改它的函数。

Commands and Queries are both immutable; if you give it a bit of thought, you'll realize you really don't want the state encoded into the command or query to be modified in flight. So the functions in the interface should all be queries, in the CQS sense - functions that return a copy of the state of the object without changing it in any way.

给定那命令和查询有什么共同之处?也许是一堆元数据,以便调用正确类型的处理程序,以便您可以将响应与请求相关联,依此类推。所有这些的抽象是一个消息(参见企业集成模式,Gregor Hohpe)。

Given that, what do commands and queries have in common? Maybe a bunch of meta data, so that the right kinds of handlers get invoked, so that you can correlate the responses with the requests, and so on. The abstraction of all of this is a Message (see Enterprise Integration Patterns, Gregor Hohpe).

所以你当然可以证明

public interface IMessage {...}

所以你可能有

public interface ICommand : IMessage {...}
public interface IQuery : IMessage {...}

取决于是否存在常见的查询到所有消息不常见的命令。您的实现可能甚至需要

Depending upon whether or not there are queries which are common to all Commands that are not common to all Messages. It's possible that your implementation might even want

public interface CQCommonThing : IMessage {...}
public interface ICommand : CQCommonThing {...}
public interface IQuery : CQCommonThing {...}

但是我很难想出任何与查询和命令共同的查询示例,这些查询和命令也不属于Message。

But I'm stumped to come up with any examples of queries that would belong in common to Queries and Commands that don't also belong to Message.

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

On the other hand, if you are considering marker interfaces, where you aren't actually specifying a contract, like so:

public interface IQuery{}
public interface ICommand{}

然后我不知道你想要将这些结合起来的任何原因除外,你可能想要使用 IMessage

then I don't know of any reason that you would want to combine these, except in the sense that you might want to use IMessage instead.

检查你的实现,看起来你在某个地方丢失了情节。

Reviewing your implementation, it looks like you lost the plot somewhere.

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

这是一个命令创建一堆实体我的记录系统或查询返回给我一个创建的实体列表?试图同时做两件事都违反了CQS,这暗示你在错误的轨道上。

Is this a command "create a bunch of entities in my system of record", or a query "return to me a list of created entities"? Trying to do both at the same time violates CQS, which is a hint that you are on the wrong track.

换句话说,这个构造在这里

In other words, this construct here

public interface IReturnCommand<TOutput>: ICommand
{
    TOutput Result { get; set; }
}

很奇怪 - 为什么你在使用时需要这样的东西? CQRS模式?

is bizarre -- why would you ever need such a thing when using the CQRS pattern?

使用CreateAttachments作为示例,您当前的实现调用客户端发送到命令处理程序,并接收返回的匹配guid列表。实施起来很有挑战性 - 但你不必选择这样做。在客户端上生成ID并使它们成为命令的一部分有什么问题?您认为客户端生成的GUID在某种程度上不如服务器生成的GUID唯一吗?

Using CreateAttachments as an example, your current implementation calls for the client sending to the command handler, and receiving a list of matching guids in return. That's challenging to implement -- but you don't have to choose to do it that way. What's wrong with generating the IDs on the client, and making them part of the command? Do you think client generated GUIDs are somehow less unique than a server generated 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; }
}

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

"Look, Ma, no Result." The caller just needs an acknowledgement of the command (so that it can stop sending it); and then it can synchronize via query.

这篇关于CQRS模式 - 接口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆