【问题标题】:Right implementation of CQRS design pattern?CQRS 设计模式的正确实现?
【发布时间】:2021-05-24 13:05:20
【问题描述】:

我正在开发一些使用 CQRS 设计模式的遗留应用程序(.Net 核心和框架应用程序)。

基本上,我们有一个使用 Dapper 的数据访问层存储库。然后我们通过创建两个不同的接口来应用 CQRS,一个用于命令,一个用于查询。这些界面看起来像:

//查询接口

public interface IOrderQuery
{
   Order GetOrderById(int id);
}

// 命令界面

public interface IOrderCommand
{
    bool PlaceOrder(Order placedOrder)
}

在我看来,这可能不是 CQRS 的正确实现,因为我们对输入和输出使用相同的模型,并且每个操作都没有特定的处理程序。但是我的 Principal Dev 说这是正确的方法,因为我们只需要将命令和查询分开,然后我们存档 CQRS 模式。我现在很困惑。

此外,我们对存储库使用 httpClient 的 Web api 应用相同的方法。

因此,我建议使用 MediatR 将 CQRS 与 DDD 一起应用,但看起来他们不想使用它,因为它过于复杂。

我只是一名初级开发人员,所以任何人都可以为我解释一下我们当前使用的方法是 CQRS 的正确实现吗?我应该将 CQRS 与 HttpClient 一起应用吗?使用 CQRS 有什么好处?

谢谢

【问题讨论】:

    标签: asp.net-core-webapi cqrs mediatr


    【解决方案1】:

    您有一个正确的观点,这不是实现 CQRS 的非常典型的方法。对我来说,它看起来更像是一个单一的方法存储库。虽然 CQRS 的主要目标是分离查询和命令,但从长远来看,您采用的这种方法可能会使事情复杂化。

    无论是查询还是命令,CQRS 的典型实现是您拥有包含执行此查询所需的所有信息的查询(或命令)对象 + 处理此查询的处理程序。检查下面的示例。

    public class OrdersQuery
    {
        public DateTime? FromDate { get; }
        public DateTime? ToDate { get; }
    
        public OrdersQuery(DateTime? fromDate, DateTime? toDate)
        {
            FromDate = fromDate;
            ToDate = toDate;
        }
    }
    
    public class OrdersQueryHandler
    {
        public List<Order> Handle(OrdersQuery query)
        {
            //Implementation Goes Here
        }
    }
    

    在此示例中,您的 OrdersQuery 包含可选的 FromDate 和 ToDate 过滤器(这仅用于演示),它应该包含查询处理程序执行查询所需的任何输入数据。或者根本没有数据,以防您想在一个表中返回所有数据,例如查找表(例如 GetOrdersTypes、国家/地区)

    要使用此查询,请执行以下操作。

    public static class Program
    {
        public static void Main(string[] args)
        {
            var handler = new OrdersQueryHandler();
            var orders = handler.Handle(new OrdersQuery(DateTime.Now.AddDays(-30), DateTime.Now));
        }
    }
    

    但您可能已经看到了问题,在实际应用中,您将需要使用依赖注入、工厂或某种可以轻松使用查询处理程序的方式。

    你不能每次需要使用一个新的 SomeHandler() ,这将创建对处理程序的硬依赖,并使代码更难维护。如果您采用您在问题中建议的方法,您最终会为每个查询/命令处理程序提供 1 个接口。想想在某个时候需要注入多少接口来执行一些复杂的业务规则。

    CQRS 和 Mediator 模式很好地结合在一起解决了这些问题,在我看来,MediatR 是最好的库,可以达到这个目的,而且它一点也不复杂。检查下面的示例。

    public class OrdersQuery : IRequest<List<Order>>
    {
        public DateTime? FromDate { get; }
        public DateTime? ToDate { get; }
    
        public OrdersQuery(DateTime? fromDate, DateTime? toDate)
        {
            FromDate = fromDate;
            ToDate = toDate;
        }
    }
    
    
    public class OrdersQueryHandler : IRequestHandler<OrdersQuery, List<Order>>
    {
        public Task<List<Order>> Handle(OrdersQuery request, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }
    }
    
    public static class Program
    {
        public static async Task Main(string[] args)
        {
            var services = new ServiceCollection()
                .AddMediatR(typeof(Program).Assembly)
                .BuildServiceProvider();
    
            var mediator = services.GetRequiredService<IMediator>();
    
            var orders = await mediator.Send(new OrdersQuery(DateTime.Now.AddDays(-30), DateTime.Now));
        }
    }
    

    在这里,您的查询将实现 IRequest,其中 T 是结果,您的查询处理程序将实现 IRequestHandler,其中 T 是查询类型,TResult 是结果(例如,列表)(注意:TResult 必须与IRequest 中的类型参数)。在此示例中,我使用的是 MS 依赖注入,但您可以使用您选择的 DI 库。

    MediatR 在这里扫描所有处理程序的程序集(如果需要,您可以手动注册它们)。现在要使用它,您只需要了解 IMediator 接口,无需再了解 IOrdersQuery、IOrdersCommand 等。您将只引用 IMediator,中介将负责为正确的查询解析正确的处理程序。

    现在是关于是否应该通过 HTTP 对资源使用 CQRS 的问题。如果 CQRS 是指这里与中介讨论的模式。是的,你绝对可以做到。

    但 CQRS 最常指的是数据库查询/命令。有时人们也实施 CQRS 来对不同的数据库进行读写。因此,为了避免混淆,只需将其称为中介模式,它肯定适用于 Http。

    【讨论】:

    • 感谢您的示例和解释。我已经成功说服他们将 CQRS 与 MediaR 一起使用
    • 很高兴听到@NinjaCoder ^^ 如果您对答案感到满意,请随时将其标记为问题的正确答案 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-02
    • 2013-03-14
    • 2019-11-24
    • 2012-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多