当前位置: 移动技术网 > IT编程>软件设计>设计模式 > 手撸一套纯粹的CQRS实现

手撸一套纯粹的CQRS实现

2019年06月09日  | 移动技术网IT编程  | 我要评论

关于cqrs,在实现上有很多差异,这是因为cqrs本身很简单,但是它犹如潘多拉魔盒的钥匙,有了它,读写分离、事件溯源、消息传递、最终一致性等都被引入了框架,从而导致cqrs背负了太多的混淆。本文旨在提供一套简单的cqrs实现,不依赖于es、messaging等概念,只关注cqrs本身。

cqrs的本质是什么呢?我的理解是,它分离了读写,为读写使用不同的数据模型,并根据职责来创建相应的读写对象;除此之外其它任何的概念都是对cqrs的扩展。

下面的伪代码将展示cqrs的本质:

使用cqrs之前:

customerservice

void makecustomerpreferred(customerid) 
customer getcustomer(customerid) 
customerset getcustomerswithname(name) 
customerset getpreferredcustomers() 
void changecustomerlocale(customerid, newlocale) 
void createcustomer(customer) 
void editcustomerdetails(customerdetails)

使用cqrs之后:

customerwriteservice

void makecustomerpreferred(customerid) 
void changecustomerlocale(customerid, newlocale) 
void createcustomer(customer) 
void editcustomerdetails(customerdetails)

customerreadservice

customer getcustomer(customerid) 
customerset getcustomerswithname(name) 
customerset getpreferredcustomers()

query

查询(query): 返回结果,但是不会改变对象的状态,对系统没有副作用。

查询的实现比较简单,我们首先定义一个只读的仓储:

public interface ireadonlybookrepository
{
    ilist<bookitemdto> getbooks();

    bookdto getbyid(string id);
}

然后在controller中使用它:

public iactionresult index()
{
    var books = readonlybookrepository.getbooks();

    return view(books);
}

command

命令(command): 不返回任何结果(void),但会改变对象的状态。

命令代表用户的意图,包含业务数据。

首先定义icommand接口,该接口不含任何方法和属性,仅作为标记来使用。

public interface icommand
{
    
}

与command对应的有一个commandhandler,handler中定义了具体的操作。

public interface icommandhandler<tcommand>
    where tcommand : icommand
{
    void execute(tcommand command);
}

为了能够封装handler的定位,我们还需要定一个icommandhandlerfactory:

public interface icommandhandlerfactory
{
    icommandhandler<t> gethandler<t>() where t : icommand;
}

icommandhandlerfactory的实现:

public class commandhandlerfactory : icommandhandlerfactory
{
    private readonly iserviceprovider serviceprovider;

    public commandhandlerfactory(iserviceprovider serviceprovider) 
    {
        this.serviceprovider = serviceprovider;
    }

    public icommandhandler<t> gethandler<t>() where t : icommand
    {
        var types = gethandlertypes<t>();
        if (!types.any())
        {
            return null;
        }
        
        //实例化handler
        var handler = this.serviceprovider.getservice(types.firstordefault()) as icommandhandler<t>;
        return handler;
    }

    //这段代码来自diary.cqrs项目,用于查找command对应的commandhandler
    private ienumerable<type> gethandlertypes<t>() where t : icommand
    {
        var handlers = typeof(icommandhandler<>).assembly.getexportedtypes()
            .where(x => x.getinterfaces()
                .any(a => a.isgenerictype && a.getgenerictypedefinition() == typeof(icommandhandler<>)))
                .where(h => h.getinterfaces()
                    .any(ii => ii.getgenericarguments()
                        .any(aa => aa == typeof(t)))).tolist();


        return handlers;
    }

然后我们定义一个icommandbus,icommandbus通过send方法来发送命令和执行命令。定义如下:

public interface icommandbus
{
    void send<t>(t command) where t : icommand;
}

icommandbus的实现:

public class commandbus : icommandbus
{
    private readonly icommandhandlerfactory handlerfactory;

    public commandbus(icommandhandlerfactory handlerfactory)
    {
        this.handlerfactory = handlerfactory;
    }

    public void send<t>(t command) where t : icommand
    {
        var handler = handlerfactory.gethandler<t>();
        if (handler == null)
        {
            throw new exception("未找到对应的处理程序");
        }

        handler.execute(command);
    }
}

我们来定一个新增命令createbookcommand:

public class createbookcommand : icommand
{
    public createbookcommand(createbookdto dto)
    {
        this.dto = dto;
    }

    public createbookdto dto { get; set; }
}

我不知道这里直接使用dto对象来初始化是否合理,我先这样来实现

对应createbookcommand的handler如下:

public class createbookcommandhandler : icommandhandler<createbookcommand>
{
    private readonly iwritablebookrepository bookwritablerepository;

    public createbookcommandhandler(iwritablebookrepository bookwritablerepository)
    {
        this.bookwritablerepository = bookwritablerepository;
    }

    public void execute(createbookcommand command)
    {
        bookwritablerepository.createbook(command.dto);
    }
}

当我们在controller中使用时,代码是这样的:

[httppost]
public iactionresult create(createbookdto dto)
{
    dto.id = guid.newguid().tostring("n");
    var command = new createbookcommand(dto);
    commandbus.send(command);

    return redirect("~/book");
}

ui层不需要了解command的执行过程,只需要将命令通过commandbus发送出去即可,对于前端的操作也很简洁。

该实例的完整代码在github上,感兴趣的朋友请移步>>

如果代码中有错误或不合适的地方,请在评论中指出,谢谢支持。

参考文档

如对本文有疑问, 点击进行留言回复!!

相关文章:

  • 设计模式-10-适配器模式-[组合模式]

    设计模式-10-适配器模式-[组合模式]

    1.[组合模式] 是 [对象的适配器模式] 的衍生模式 组合模式:它在我们树型结构的问题中,模糊了简单元素和... [阅读全文]
  • 软件设计七大原则

    软件设计七大原则 1、 开闭原则 定义:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。中心思想:用抽象构建框架,用实现扩展细节。即面向抽象编... [阅读全文]
  • PHP设计模式—外观模式

    定义: 外观模式(Facade):又叫门面模式,为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 ... [阅读全文]
  • PHP设计模式—组合模式

    定义: 组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。当你发... [阅读全文]
  • PHP设计模式—装饰器模式

    定义: 装饰器模式(Decorator):动态的给一个对象添加一些额外的职责,就增加功能来说,装饰器比生成子类更加灵活。 结构: Component:定... [阅读全文]
  • 【设计模式】六大设计原则

    六大设计原则 单一职责 定义 每个类都应该有一个单一的功能 一个类或者模块应该有且只有一个改变的原因 规范 定义类的方法 避免类之间耦合度太高 里氏替换... [阅读全文]
  • 【设计模式】单例模式

    定义 确保某个类只有一个实例 实现方式 饿汉式加载(线程安全) 等价于 懒汉式加载 非线程安全 线程安全 1. Double Check 2. 借助La... [阅读全文]
  • 23种 设计模式 简介

    设计模式总结 创建型模式 创建型模式隐藏了这些类的实例是如何被创建和放在一起,整个系统关于这些对象所知道的是抽象类所定义的接口。这样,创建型模式在创建了... [阅读全文]
  • 大话编程之-设计模式篇(简介)

    设计模式简介 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程... [阅读全文]
  • 单例模式学习

    单例模式学习 1 饿汉式单例模式 还没用就创建了对象,可能会浪费空间 2 懒汉式单例模式 无线程锁 java package main.java.com... [阅读全文]
验证码:
移动技术网