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.dbsreq.$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 => /.*\.js$/.test(Actor)).

    forEach(Actor => domain.register(require(path.join(actorPath, Actor))));

// Get Listener from listener folder

listeners.filter(listener => /.*\.js$/.test(listener)).

    forEach(listener => require(path.join(listenerPath, listener))(domain));

  • 第一步,在根目录下添加listener文件夹

  • 第二部,创建新的监听js文件,

Listener 内部写法,如下(个人经验)


module.exports = function(domain){

    // Utilise domain.on(...) to make onAction listening

}


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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 这几日雨丝连绵,让人的心情也添了几分愁思。眼看到8月下旬了,开学的日子临近了。家里有个亲戚的孩子要上大学了,过...
    哈哈的喵星人阅读 214评论 0 1
  • 市场营销策划,是指市场营销策划活动的主体——企业在市场营销活动中,为达到预定的市场营销目标,从新的营销视角、新的营...
    依东望阅读 616评论 0 1
  • 一、利用小程序提供的 API 跳转:1.// 保留当前页面,跳转到应用内的某个页面,使用wx.navigateBa...
    苏苡阅读 2,208评论 0 2
  • 你有没有吃过没有牛肉的牛肉面呢? 反正我没吃过啦~ 那为什么这碗牛肉面里看不到牛肉呢? 答案在下方 ↓ ↓ ↓ 哈...
    行动就是生产力阅读 309评论 0 0