代码之家  ›  专栏  ›  技术社区  ›  RubenHerman

CQRS模式-接口

  •  4
  • RubenHerman  · 技术社区  · 8 年前

    我是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);
        }
    }
    
    2 回复  |  直到 8 年前
        1
  •  9
  •   ryanyuyu    8 年前

    如果我没弄错的话,你会混淆这里的缩略语。从你的问题来看,我觉得你并不是真的在问 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
  •  5
  •   VoiceOfUnreason    8 年前

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

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

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

    命令和查询都是不可变的;如果您仔细考虑一下,就会发现您真的不希望在运行中修改编码到命令或查询中的状态。因此,在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是否比服务器生成的GUIDs更不唯一?

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

    “听着,妈,没有结果。”呼叫者只需要确认命令(这样它就可以停止发送命令);然后它可以通过查询进行同步。