【问题标题】:CQRS pattern - interfacesCQRS 模式 - 接口
【发布时间】:2016-04-05 14:11:37
【问题描述】:

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

【问题讨论】:

    标签: interface cqrs


    【解决方案1】:

    如果我理解正确,您会在这里混淆首字母缩写词。从您对我的问题来看,您似乎并没有真正询问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; }
    }
    

    最后是命令处理程序的示例消费者:

    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)
    

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

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

    关于 CQRS 的注意事项

    此图中的 CQRS 与 CQS 等 OOP 方法在技术上有所不同。

    【讨论】:

      【解决方案2】:

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

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

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

      命令和查询都是不可变的;如果您稍微考虑一下,您会意识到您真的不希望在运行中修改编码到命令或查询中的状态。所以接口中的函数都应该是查询,在 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 {...}
      

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

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

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

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

      【讨论】:

      • 我确实在使用 IReturnCommand 时走错了路。我从来没有真正考虑过在客户端级别生成 GUID,但这是解决我的问题的一个非常好的方法。谢谢!
      猜你喜欢
      • 2021-03-07
      • 2019-02-27
      • 1970-01-01
      • 2016-05-19
      • 2013-12-03
      • 2012-02-07
      • 2010-09-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多