DevUI是一支兼具設計視角和工程視角的團隊,服務於華為雲DevCloud平臺和華為內部數箇中後臺系統,服務於設計師和前端工程師。
官方網站:devui.design
Ng元件庫:ng-devui(歡迎Star)
引言
作為前端開發者,有些時候我們在後端服務還未ready的時候就接到了緊急開發需求,⾯對資料接⼝的缺失和資料持久化的⽀持,開發舉步維艱。當然,加班也許也是⼀種解決問題的⽅法,但如果我們能夠⾃⼰動動手指頭去解決這兩個問題,那麼前端開發者們不僅增進了對業務的瞭解還掌握了對資料接⼝定義的主動權,後期的聯調時間成本也可以⼤⼤縮⼩。
本文適合前端開發者閱讀,閱讀時長10分鐘。
開始
⾸先,我選⽤Vue全家桶來做這個Mock App的講解,因為程式碼少、效率高。前端做資料持久化需要一個存資料的地方,有的讀者可能對localstorage和sessionstorage比較熟悉,但它們的缺點如下:
- 缺少對結構化資料儲存的優化,存取都需要呼叫JSON.stringify和JSON.parse。
- 缺少對資料系統化的管理
- 缺少對資料查詢的支援
由此可得localstorage和sessionstorage都不適合拿來解決我們的問題,⽽WebSQL已經壽終正寢,所以只有IndexedDB可選了。
INDEXEDDB是⼀個嵌⼊在瀏覽器中的事務資料庫。該資料庫的管理圍繞JSON物件集合的概念,這類似NOSQL資料庫MONGODB與COUCHDB。其中每個物件使⽤插⼊時⽣成的鍵標識。⽽索引系統優化對儲存物件的訪問。[Wikipedia](https://zh.wikipedia.org/wiki/Indexed_Database_API)
決定了使⽤IndexedD做資料持久化⽅案,我推薦使⽤Dexie.js對IndexedDB進⾏操作。實際上,如果沒有Dexie.js這層封裝,我也不會想在前端做資料mock。
所有的⼯具已經ready,我們將使⽤經典的message board來作為業務模型,做⼀個只關注資料層⾯的mock。
業務剖析
先上⼀張圖, Message board的業務邏輯很簡單,⽤戶先建立Board(帖⼦),然後此⽤戶或其它⽤戶在Board(帖⼦)⾥建立Message(回覆)。按照正常BBS的邏輯,⽤戶未登入的時候也可以看到帖⼦,只是不能回覆,這個特性也會在Mock App中體現。
由於IndexedDB和MongoDB很相似,所以我們可以直接將User直接儲存在Message和Board的author中,⽽Message則使⽤parentId+parentType來建⽴和Board的關係。
Coding
定義資料庫
使⽤Vue cli新建⼀個⼲淨的項⽬,安裝dexie,在src⽂件夾下新建⼀個db.ts⽂件,放⼊資料庫的Schema
import Dexie from "dexie";
interface DBObject {
[key: string]: any;
}
const db: DBObject = new Dexie("myDb");
db.version(1).stores({
users: `++id, name`,
boards: `++id, topic, description, author`,
messages: `++id, content, author, createdAt, parentId, parentType`
})
這⾥傳⼊stores⽅法的object就是資料庫的schema了。 keys代表了資料庫的表, value中是以逗號分隔的columns。⼀個⽐較特殊的是++id,它的意思是⾃增整形,並且它是作為表的主鍵存在的。其他的columns和業務剖析中的圖⼀致,就不展開了。
定義Mock API
我們只實現最⼩可⽤的Mock App,只需實現以下⼏個API:
- createUser: 建立⽤戶,⽤以登入應⽤
- getUsers: 獲取⽤戶列表,⽅便我們切換⽤戶
- createDiscussion: 建立Board,類似於建立⼀個帖⼦的概念
- getDiscussions: 獲取Board列表,類似於獲取帖⼦列表的概念
- getDisucssionMessage: 獲取Board下的Message列表,類似於獲取⼀個帖⼦下⾯所有回覆的概念
- createDiscussionMessage: 建立Board下的Message,類似於在帖⼦下建立回覆的回覆的概念
在本⽂最後給出的項⽬程式碼中有它們的具體實現。這⾥只對getUsers、 createUser和getDiscussionMessage⽅法做講解:
api.prototype.getUsers = function() {
return db.transaction("r", db.users, function() {
return db.users.toArray();
});
};
這個⽅法返回了⼀個Promise, db.transaction中第⼀個引數是”r”,熟悉linux許可權系統的同學肯定知道了,這是read許可權的意思,因為這個getUsers⽅法涉及到讀資料庫操作,所以這個transaction需要read access。 第⼆個傳⼊的引數是db.users,它宣告瞭transaction將建⽴和Users table的連線,⽽function中return的db.users.toArray()則返回了Users table中所有的資料。
api.prototype.createUser = function(name: string) {
return db.transaction("rw", db.users, function() {
return db.users.add({ name: name });
});
};
這個⽅法同樣返回了⼀個Promise, db.transaction中第⼀個引數是”rw”,也就是read & write的意思,因為這個createUser⽅法涉及到讀資料庫操作,所以它需要write access。 db.users.add({name:name})則新建了⼀⾏⽤戶資料,⽤戶的id被⾃動補全。
api.prototype.getDisucssionMessage = function(discussionId:number) {
return db.transaction("r", db.messages, function() {
return db.messages.where({
parentId: discussionId,
parentType: "discussion"
}).toArray();
});
};
這個⽅法也返回了⼀個Promise, function中的db.message.where⽅法有點SQL的味道,它的作⽤你也猜到了,就是通過提供的過濾器(object)在Messages table中查詢資料,然後返回所有符合過濾器篩選的結果。
定義Vuex Store
我們會⽤Vuex actions把API管理起來,在Angular中也可以⽤Service達到同樣的效果,管理起來的⽬的是當後端API開發完成的時候,我們可以很⽅便地遷移到新的API上,⽽不需要⼤量地變更已經寫好的業務程式碼。
我們在Vuex的actions中建⽴和上⽂Mock API中相對應的⽅法,同時為了⽅便獲取到⽤戶登入的狀態,我們可以在getters中加⼊loginStatus,因為我們的多個⻚⾯需要判斷⽤戶是否登入,只有登入的⽤戶才可以發⾔,未登入的⽤戶只能檢視討論。
完成業務
Mock API和Vuex Store都寫好之後我們可以開始著手實現我們的業務邏輯。由於本身的業務實在太簡單,我直接放原始碼了,這⾥不做展開。
假設我們完成了Mock App的編寫,如何利用已完成的程式碼,儘量少地改動業務邏輯來適配後端的API呢?在Vuex(或服務)中抽象出來的API就功不可沒了,理想情況下我們只需要改變引入的API源(Angular服務注入的時候可以用UseClass)就能做到切換API的工作,因為我們的業務邏輯和API用什麼技術方案實現的完全沒關係!
事實上,基於這樣的流程編寫的App也能降低Code Smell,促進應用與API的解耦,為更健壯、更具擴充性、更具可測性的Code Base打好基礎。
總結
IndexedDB賦予了開發者們即使沒有後端的時候仍可以繼續前端開發的能⼒,使⽤Dexie的API去管理IndexDB⽆疑減⼩了IndexedDB的使⽤⻔檻。同時,使⽤Vuex、 Angular這類具有服務注⼊能⼒的庫/框架可以有效降低API源切換的時間成本和⻛險。
在前端給出需要的資料接⼝格式後,後端只需按要求提供相應資料即可。如果有擴充套件需求,只需在前端給出的接⼝格式上持續演進,⽽不⽤為了對⻬接⼝格式⽽扯⽪。
在業務邏輯與API實現解耦之後,前端開發者可以有更多的時間去思考⽤戶體驗、編寫單元測試,從⽽提⾼整個應⽤的魯棒性、可⽤性和易⽤性。
最後需要強調的是,此⽂並不是說後端沒⽤,因為後端提供的⼀些能⼒仍是前端遠遠不及的。本⽂的觀點是,在前端做Mock App的這段時間內,後端可以有更充⾜的時間、空間去思考後端架構和實現、以更穩定的狀態承載更⼤的總業務容量,從⽽為產品、公司和客戶帶來價值。
以下是專案原始碼:
https://github.com/AnonymousArthur/expedited-mock
加入我們
我們是DevUI團隊,歡迎來這裡和我們一起打造優雅高效的人機設計/研發體系。招聘郵箱:muyang2@huawei.com。
文/DevUI Arthur