Cumulo 的 ClojureScript 模塊已經成型

爲何要有 Cumulo?

刷一下存在感, 感受 Cumulo cljs 接近一個可 Demo 的狀態了
2014 開始使用 React 以後, 就對 Restful 的套路感到有疑問
React 要的是聲明式地描述結構, 而後用算法自動填充其中的邏輯
Restful API 的設計相似 DOM API 的設計, 跟 React 的思路徹底不一樣
爲了在網絡層達高開發效率, 高層級的抽象是少不了的前端

我微博上留了點記錄, 還有個很早的視頻, 其實套路大概是同樣的
http://www.tudou.com/programs/view/EoKUKOXe1eo/
兩年中間個人作了很多的試驗, 也從 js 遷移到了 cljs, 變化很大
後來其實爲了簡單, 我繼續作了簡化, 方便用不多的代碼就能上手
從一開始我就知道性能不行, 並且我主要的目標就是爲了編碼方便
方案驗證可行以後, 我纔會去考慮性能的問題git

若是你寫過大一點的網頁聊天室, 要同步各類狀態, 問題就會遇到
後端開發也許舊的無所謂, 但在前端會以爲比較難受
當服務器有一條數據更新, 而前端有多個 model 依賴這條數據顯示github

  • 經常使用的辦法是監聽 server push, 本地 model 每一個位置到須要更新一遍算法

  • 先後端存儲的數據格式有差異時, 更新的代碼也會更繁瑣數據庫

  • 增長新代碼時比較囉嗦, 並且可能漏掉已有代碼中的一些關聯操做後端

  • 本地和遠端的操做須要作區分, 在某些邏輯上要特殊處理服務器

從方案來講舊的 Restful 有着內在的不足, 對複雜場景的抽象偏低
Cumulo 並非爲了解決所有問題, 就確實能夠在部分場景簡化開發微信

和 GraphQL 或者 Falcor 方案的區別

先聲明我並無寫過這兩樣代碼, 前者由於官方技術, 比較熟悉
後者我從 Netflix 開發人員的演講視頻瞭解的網絡

個人見解是 GraphQL 不夠通用, 從 View 去聲明 Store 對數據流有影響
這是個依賴關係的問題, 我認爲是 View 依賴 Store, 而不是反過來
Cumulo 裏 Store 的結構依賴一些狀態值, 而不是 View 當中的數據聲明
這也是 Web 路由的作法, 用片斷的字符串來決定展開的結構是怎樣
Cumulo 中用 state 中的一些參數來決定 Store 怎樣展開
另外相比 GraphQL 對數據庫封裝, Cumulo 的 Diff 就太粗暴了, 傷害性能架構

對 Falcor 的細節瞭解不多, 官方文檔很複雜, 至少對我來講...
總體思路闡述得很漂亮, 就是分析一個網頁須要的數據, 只抓取缺乏的數據
相比 GraphQL, JSON Graph 的概念更簡單一些
簡聊的代碼裏山寨了一部分, 但我實在不瞭解 Falcor, 只學到皮毛
對我來講我會以爲太複雜我就先不碰了, 我沒有處理複雜性問題的天分
用 Promise 強行封裝請求我以爲也只是權宜之計, 問題複雜性依然在

Cumulo 往簡單了說就是把前端 DOM 更新方案原模原樣搬到了後端
原本 DOM 更新慢, 如今是網絡慢, 我消耗服務器性能來簡化網絡
多個 Restful 請求就合併在一個 Diff 裏了, 處理返回結果的代碼也省了

數據流設計

Cumulo 的核心思想大體用下面的代碼就能表達完了:

db_0 = {states: {}, users: {}, messages: {}}
# get `action` from network
db_n+1 = updater(db_n, action)

scene = renderScene(db)
store = renderStore(scene, user_id)

changes = diff(store_n, store_n+1)
# send `changes` over network
clientStore_n+1 = patch(clientStore_n, changes)

DB 中的 states 涉及到一些用戶行爲, 會有一些特殊性
其他的只是前端 React Store 的更新代碼而已, 純函數的代碼
其中的 scene 也許會有困惑, 但這主要是將來優化性能, 能夠先略過
大體上就是對應的前端 React 架構, Store, updater, render, diff/patch

之前介紹 Data Diff 比較多一點, 如今看來用 Diff 來更新很普通
Diff 方案的話把性能作好最重要, 不必深刻介紹細節了

JavaScript 方案

其實算是單向數據流的一個延伸, 好多工具鏈複用就行了
主要是數據的 diff/patch 和 DOM 的 diff/patch
界面直接用 React, 數據須要藉助 immutable 模塊

http://facebook.github.io/immutable-js/docs/
https://github.com/intelie/immutable-js-diff/
https://github.com/intelie/immutable-js-patch/

具體代碼我某抽象出滿意的方案, 並且轉向 Clojure 後沒有再深刻
但能夠參考下面 Clojure 版數據流代碼進行自行想象...
純函數代碼只要瞭解參數和返回數據類型便可掌握,
WebSocket 部分比較囉嗦, 須要藉助具體實現才能知道細節

ClojureScript 方案

cljs 中其實也有可複用的代碼, 但我仍是試着弄了
cljs 原生就是不可變數據, 用的是 deep equal, diff 過程性能不佳
我寫了個 shallow equal 作 Diff, 適用性和性能有問題, 只是基本可用
而 Respo 的大體也是同樣, 性能和適用場景有不小的侷限

https://github.com/Cumulo/shallow-diff
https://github.com/mvc-works/respo

也照着 js 方案同樣嘗試對共用邏輯作了一些優化, 發現更清晰一些
Clojure 的 Atom 類型是個自帶 watcher 的引用, 很實用

https://github.com/Cumulo/cumulo-client
https://github.com/Cumulo/cumulo-server

前端模塊提供兩個有反作用的函數 setup-socket! send!
而後在前端我只要簡單地初始化, 後邊監聽 store-ref 便可

(defonce store-ref (atom {}))
(defn dispatch [op op-data] (send! op op-data))

(defn configs {:url "ws://localhost:4010"})
(setup-socket! store-ref configs)

後端提供 setup-server! reload-renderer! 兩個帶反作用的函數
其餘 updater render-scene render-view 是純函數須要本身定義
reload-renderer! 是爲熱替換而寫的,

(defonce db-ref (atom schema/database))

(defn -main []
  (setup-server! db-ref updater render-scene render-view {:port 4010}))

(defn on-jsload []
  (reload-renderer! @db-ref updater render-scene render-view))

updater 其實就是接受一些內部生成的參數, 純函數而已, 用於更新 DB

(defn updater [db op op-data state-id op-id op-time]
  (case op
    :state/connect (state/connect db op-data state-id op-id op-time)
    db))

具體代碼能夠在項目文件當中找到 Demo, 這裏不展開

狀態

目前屬於畫大餅的狀態. 我開發的勁頭也不足, 確實是編寫邊玩
週末也就整理了一下 Respo 代碼, 而後更新了一下相關依賴
topic-tag 是四月份寫的, 我更新到了 Respo 和 Shallow Diff 上
基本能表明目前 Cumulo 方案實際開發中的狀態

https://github.com/TopixIM/topic-tag
https://github.com/TopixIM/topic-tag-server

底層其實用了 Node.js 的 ws 模塊, 雖然換成 Java 也不是不能夠..
可是吧, 先後端同時作熱替換這種噱頭用 Java 仍是作不到
我也就靠熱替換噱頭一下了... js 方面用 Webpack 直接就能作
cljs 裏略麻煩, Figwheel 先後端都能作, 也比較成熟
個人代碼裏用的是 boot-reload, 更簡單, 但不能作服務端
總之 cljs 就是人少, 就算效率高, 實際上比不上 js 的體量

走一步看一步吧. 對 cljs 和 Cumulo 有興趣能夠微博微信找我聊.

相關文章
相關標籤/搜索