刷一下存在感, 感受 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 並非爲了解決所有問題, 就確實能夠在部分場景簡化開發微信
先聲明我並無寫過這兩樣代碼, 前者由於官方技術, 比較熟悉
後者我從 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 方案的話把性能作好最重要, 不必深刻介紹細節了
其實算是單向數據流的一個延伸, 好多工具鏈複用就行了
主要是數據的 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 部分比較囉嗦, 須要藉助具體實現才能知道細節
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 有興趣能夠微博微信找我聊.