• CQRS框架(nodejs的DDD开发落地框架)初识感想


    CQRS是啥?DDD又是啥?

    这两个概念其实没什么神秘的,当然此文章中的这两个概念以曾老师的课程为准(关于CQRS和DDD的标准概念,google上已经很多了,不再赘述。)

    DDD(Domain Driven Design),领域驱动设计开发。

    DDD和OOP有什么同吗?其实就我个人经验来说,没有任何不同(当然你可以反驳我),DDD就是OOP。这里以曾老师课上的概念为准,domain就是世界,包含了当前所有actor的一个域,这个域是一个上帝视角,可以监听每一个域中发生的事件,并且记录。

    CQRS,既命令和查询职责分离(Command Query Responsibility Segregation)。

    在普通mvc架构中,对于数据库的CRUD基本都是写在controller层,这样一来路由非常臃肿,而且维护起来简直是噩梦。

    CQRS将查询与职责分离。简单说来,就是写操作和读操作分离,读操作写在路由中,写操作通过面向对象写入类的业务方法中,这样路由中的查询部分薄了,而且对于写操作的可读性,重用性和维护性大大提高。

    相比较于普通mvc,cqrs分为核心层Core(及核心层扩展Core Extension)应用层(Application),UI层,看起来3层,其实是四层,但是由于核心层与核心层扩展的伸缩性很强,并且针对项目的大小来决定,所以就我觉得用3.5层来描述比较合适。

    取cqrs文档中的例子
    
    const {Actor} = require("cqrs");
    
    module.exports = class User extends Actor{
      constructor(data){
         const {name} = data;
         super({
           name,
           createTime: Date.now(),
           stars:[], // 被关注明星的 ids
           watchers:[] //  关注者的 ids
         });
      }
    
      // 关注某位明星
      async follow(starId){
        const service = this.service;
        const star = await service.get("User",starId);
        if(starId !== this.id && star){
          await star.addWatcher(this.id);
          this.$(starId)
        }
      }
    
      // 取消关注某位明星
      async unFollow(starId){
        const star = await this.service.get("User",starId);
        if(star){
          await star.deleteWatcher(this.id);
          this.$(starId);
        }
      }
    
      // 加入关注者 watcher
      addWatcher(watcherId){
        if(watcherId !== this.id)
        this.$(watcherId);
      }
    
      // 取消被关
      deleteWatcher(watcherId){
        this.$(watcherId);
      }
    
      get updater(){
        return {
          follow(json, event){
            const stars = json.stars;
            stars.push(event.data);
            return {
              stars
            }
          },
          unFollow(json, event){
            const stars = json.stars;
    
            var set = new Set(stars);
            set.delete(event.data);
    
            return {
              stars:[...set]
            }
          },
          addWatcher(json,event){
            const watchers = json.watchers;
            watchers.push(event.data);
            return {
              watchers
            }
          },
          deleteWatcher(json,event){
            const watchers = json.watchers;
            const set = new Set(watchers);
            set.delete(event.data);
            return {
              watchers:[...set]
            }
          }
        }
      }
    }
    

    以上例子是一个cqrs (传送门)Actor的实现,通过this.$产生一个事件,事件由updater接收,进行数据的真正修改。

    
    const {Domain} = require("cqrs");
    const User = require("./User");
    const domain = new Domain();
    
    // 注册 User Actor 类
    domain.register(User);
    
    // 即时异步执行函数
    (async function () {
    
      // 创建用户1
      let user1 = await domain.create("User",{
        name:"leo"
      });
    
      // 创建用户2
      let user2 = await domain.create("User",{
        name:"zengliang"
      })
    
      // user1 关注 user2
      await user1.follow(user2.id);
    
      console.log(user1.json.stars); // 打印一下 user1 监听所有 ids
      console.log(user2.json.watchers);  // 打印一下 user2 追随者的所有 ids
    
      user1.unFollow(user2.id);  // user1 取消关注 user2
    
      // 重新加载 user1 和 user2
      user1 = await domain.get("User",user1.id);
      user2 = await domain.get("User",user2.id);
    
      console.log(user1.json.stars); // 打印一下 user1 监听所有 ids
      console.log(user2.json.watchers);  // 打印一下 user2 追随者的所有 ids
    
    })();
    

    以上是在运行中对User实例对象的操作,关注与取关的操作。

    Any fool can write code that a computer can understand. Good programmers write code that humans can understand. -- 某位大牛

    以上的例子很好的诠释了可读性还有重用性。对于写操作来说,完全用业务方法来实现,那么路由中可以仅包含cqrs中Q的部分,这样做到了业务和查询分离,那么迷惑也开始解开了。

    • 写操作,用业务方法来完成,属于核心层
    • query,既查询操作,写在router中,是应用层变薄

    在使用普通mvc的时候,逻辑和查询通常都会放在路由当中,这样造成的高耦合性(coupling)让代码的重用性,可读性,可伸缩性很差。维护起来简直噩梦连连。我的第一个项目是用标准mvc完成,后期加新需求的时候基本山就是牵一发动全身,也是我的经验确实不够对于很多地方没有对代码进行可重用的封装。

    现在浅谈一下 Auxo(传送门)

    Auxo框架集成了Nuxt(Vue),Vuex,Express,cqrs四个重要框架。这样在开发时就不用再辛苦搭建开发环境了,直截了当。Auxo是约定式的框架,关于文件结构是根据Nuxt(传送门)的,所以有必要读一读Nuxt的文档,对Nuxt有一定了解之后就可以用了而且上手很快,因为基本上不需要配置什么东西。

    在Auxo框架中,数据遵循Event Sourcing原则,分两个collection。

    • 一个是事件数据库记录在domain中发生的所有事件,让事件回溯、长故事(saga)和事件锁(lock)成为可能;
    • 另外一个是查询数据库,记录普通数据,我自己的理解就是面向数据库开发的那种最基本的数据库。

    eventstore

    记录事件对象的数据库,可以通过该数据库的数据进行数据回溯。

    snap

    事件快照。domain中的事件的一个snapshot,我暂且理解为一个log

    server/index.js 中的 req.dbs req.$domain

    这两个属性已经在框架中直接挂载在了req对象上,归功于曾老师。在server/index.js中,已经定义好了,这个文件相当于express的app.js,只是文件名不一样。
    req.dbs就是上述的查询数据库,可以使用mongojs来query。
    req.$domain就是domain,即上帝视角,可以用以下语句

    
    req.$domain.get('User', uid); // 获取User对象
    req.$domain.create('User', {username: 'ephraimguo', password:'*******'}); // 创建user对象
    

    等domain对象的方法进行数据操作。

    在Vue组件中的axiosdomain

    这两个对象已经写在plugins/文件夹里面,可以直接在Vue组件中引用如下

    ```<template> <!-- Vue Template --> </template> <script> import axios from '@/plugins/aixos' import domain from '@/plugins/domain' // ... codes ... </script> <style> /* some style sheet */ </style> ```

    Listener 核心层扩展 (有个小坑)

    起初看到曾老师用listener但是不明白怎么监听,而且去看epxress-cqrs的源码的时候,看到listener的路径是作参数与传入了的。

    截取一段express-cqrs的源码
    
    // Register Actors Class from actors folder
    ActorList.filter(Actor =&gt; /.*.js$/.test(Actor)).
        forEach(Actor =&gt; domain.register(require(path.join(actorPath, Actor))));
        
    // Get Listener from listener folder
    listeners.filter(listener =&gt; /.*.js$/.test(listener)).
        forEach(listener =&gt; require(path.join(listenerPath, listener))(domain));
    
    • 第一步,在根目录下添加listener文件夹
    • 第二部,创建新的监听js文件,
    Listener 内部写法,如下(个人经验)
    
    
    module.exports = function(domain){ 
        // Utilise domain.on(...) to make onAction listening
    }
    
    

    这次先暂时聊这么多,cqrs还有很多好用的方法和思想可以慢慢琢磨,而且这种编程思想易实践,并且对全局的把控更精准,心有猛虎细嗅蔷薇,当然这篇文章也是针对上过曾老师课的童鞋们,不算是扫盲,过后会继续写一些关于cqrs框架应用的文章,也欢迎大家提问,并且一起讨论。如果有错误,也请大家指正,我会马上修改。

    来源:https://segmentfault.com/a/1190000016772949

  • 相关阅读:
    django 项目需要注意的一些点
    VUE之路
    Oracle 表格碎片的查看方法
    RHEL 6.x or 7.x 使用分区绑定ASM 磁盘的方法
    RMAN 修复主库 nologging 操作导致物理备库的坏块
    Oracle 数据库19c 回退降级到 11.2.0.4 方案
    如何评估oracle 数据库rman全备和增量备份大小
    在将Oracle GI和DB升级到19c或降级到以前的版本之前需要应用的补丁 (Doc ID 2668071.1)
    Oracle 数据库坏块处理
    opatch auto 安装11.2.0.4.20190115 PSU遇到 OUI-67133: Execution of PRE script failed,with returen value 1 报错
  • 原文地址:https://www.cnblogs.com/datiangou/p/10173208.html
Copyright © 2020-2023  润新知