這兩個概念其實沒什麼神祕的,固然此文章中的這兩個概念以曾老師的課程爲準(關於CQRS和DDD的標準概念,google上已經不少了,再也不贅述。)ios
DDD(Domain Driven Design),領域驅動設計開發。
DDD和OOP有什麼同嗎?其實就我我的經驗來講,沒有任何不一樣(固然你能夠反駁我),DDD就是OOP。這裏以曾老師課上的概念爲準,domain就是世界,包含了當前全部actor的一個域,這個域是一個上帝視角,能夠監聽每個域中發生的事件,而且記錄。git
CQRS,既命令和查詢職責分離(Command Query Responsibility Segregation)。
在普通mvc架構中,對於數據庫的CRUD基本都是寫在controller層,這樣一來路由很是臃腫,並且維護起來簡直是噩夢。github
CQRS將查詢與職責分離。簡單說來,就是寫操做和讀操做分離,讀操做寫在路由中,寫操做經過面向對象寫入類的業務方法中,這樣路由中的查詢部分薄了,並且對於寫操做的可讀性,重用性和維護性大大提升。數據庫
相比較於普通mvc,cqrs分爲核心層Core(及核心層擴展Core Extension)應用層(Application),UI層,看起來3層,實際上是四層,可是因爲核心層與核心層擴展的伸縮性很強,而且針對項目的大小來決定,因此就我以爲用3.5層來描述比較合適。express
取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實例對象的操做,關注與取關的操做。json
Any fool can write code that a computer can understand. Good programmers write code that humans can understand. -- 某位大牛
以上的例子很好的詮釋了可讀性還有重用性。對於寫操做來講,徹底用業務方法來實現,那麼路由中能夠僅包含cqrs中Q的部分,這樣作到了業務和查詢分離,那麼迷惑也開始解開了。axios
在使用普通mvc的時候,邏輯和查詢一般都會放在路由當中,這樣形成的高耦合性(coupling)讓代碼的重用性,可讀性,可伸縮性不好。維護起來簡直噩夢連連。個人第一個項目是用標準mvc完成,後期加新需求的時候基本山就是牽一髮動全身,也是個人經驗確實不夠對於不少地方沒有對代碼進行可重用的封裝。segmentfault
Auxo框架集成了Nuxt(Vue),Vuex,Express,cqrs四個重要框架。這樣在開發時就不用再辛苦搭建開發環境了,直截了當。Auxo是約定式的框架,關於文件結構是根據Nuxt(傳送門)的,因此有必要讀一讀Nuxt的文檔,對Nuxt有必定了解以後就能夠用了並且上手很快,由於基本上不須要配置什麼東西。架構
在Auxo框架中,數據遵循Event Sourcing原則,分兩個collection。
記錄事件對象的數據庫,能夠經過該數據庫的數據進行數據回溯。
事件快照。domain中的事件的一個snapshot,我暫且理解爲一個log
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對象的方法進行數據操做。
axios
和 domain
這兩個對象已經寫在plugins/
文件夾裏面,能夠直接在Vue組件中引用以下
<!-- Vue Template --> </template> <script> import axios from '@/plugins/aixos' import domain from '@/plugins/domain' // ... codes ... </script> <style> /* some style sheet */ </style>
起初看到曾老師用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 內部寫法,以下(我的經驗)
module.exports = function(domain){ // Utilise domain.on(...) to make onAction listening }
此次先暫時聊這麼多,cqrs還有不少好用的方法和思想能夠慢慢琢磨,並且這種編程思想易實踐,而且對全局的把控更精準,心有猛虎細嗅薔薇,固然這篇文章也是針對上過曾老師課的童鞋們,不算是掃盲,事後會繼續寫一些關於cqrs框架應用的文章,也歡迎你們提問,而且一塊兒討論。若是有錯誤,也請你們指正,我會立刻修改。
來源:https://segmentfault.com/a/1190000016772949