zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    JSP基础语法:注释、Scriptlet、编译指令
    JDBC的LIKE书写规范
    AWT回顾篇
    1.五子棋预备知识
    对象的生命周期回顾篇
    学习activemq(2)写个简单的程序
    activemq in action(3)剖析JMS消息(转)
    activemq in action(1)
    学习activemq(3)
    hadhoop安装
  • 原文地址:https://www.cnblogs.com/datiangou/p/10173208.html
Copyright © 2011-2022 走看看