書接上回,我們從原先的那份 DBML 討論了我們的資料庫設計。
在正式開始今天的內容之前,我用了一些神奇的人工智慧與工人智慧,產出了這份 DBML。

我去老哥偷捲,都自己偷偷動工不揪。
666 稀有鳥類馬德都部鳩出現了,一直部鳩部鳩的叫。
而且這不就要給你派工作了嗎?
所以我要幹啥?
幫我看一下這份 Schema。
啊你不是都畫完了?
對啊。
那我看什麼?
哪裡看不懂就問哪裡,有覺得奇怪的也問。
啊?傳統技藝小黃鴨 Debug 法又要重出江湖了嗎?
可惡被發現了。
所謂「小黃鴨 Debug 法」,簡單來說就是找一隻小黃鴨,把自己的程式或想法從頭到尾講給它聽。
講著講著,有時候甚至不用等鴨子回答(雖然你應該是等不到它回答啦),你就會突然發現:
「欸幹,我這邊是不是寫錯了?」
至於我們今天的小黃鴨不只會聽,還會問,甚至還會吐槽。
比一般的小黃鴨好用很多,還不用給 API 充錢,就是需要吃點巴拿拿(Banana)犒勞一下。
所以我現在是鴨還是猴子?
你是猿神可以了嗎,趕緊開始。
行。
欸老哥,這裡的設計有點猛啊。

這裡的 permissions 是準備要用昨天提到的位運算對吧。
不錯啊,看一眼就懂了,一般人應該會先問為什麼把 permissions 設定成整數。
既然你知道我要幹什麼了,那就由你來跟大家解釋這邊的設計吧。
說好的小黃鴨呢?不應該是你解釋給我聽嗎?
能者過...…咳咳,能者多勞嘛,交給你了。
行,我們來看一下這段:
Table positions {
id uuid [pk]
name varchar(255) [not null, unique]
permissions bigint [not null, default: 0]
description text
}
以正常的思路來說,我們應該會開一個
Permission表,然後開一個中介表來記錄哪個position有哪些權限對吧。但蘇某沒有打算這麼做。
假設我們現在有四個權限要記錄:
- 查看廣播
- 發布廣播
- 回覆廣播
- 處理報修
我們可以把它們塞在同一個整數的不同位元:
查看廣播 0001
發布廣播 0010
回覆廣播 0100
處理報修 1000
這樣如果這個人所擁有的權限是:
查看廣播、發布廣播。我們只需要對
0001跟0010做一次OR運算得到0011也就是3。之後要檢查某個 Permission,也只需要透過
AND判斷對應的 bit 有沒有被打開。位運算在電腦裡本身是非常便宜的,而且這樣一來,一整組 Permission 就可以直接壓在一個
BIGINT裡,不需要為每一項 Permission 都建立一筆關聯資料。
我補充一下,位運算本身很便宜,不過老實說,我們學校這點使用量根本輪不到 CPU 操心。
真正方便的是,一整組 Permission 可以直接用一個值表示,之後 Role、Position 甚至 Override 都可以用同一套方式做 OR、AND。
缺點也很明顯,人沒辦法從
3這個魔法數字直接知道這個人有查看廣播、發布廣播這兩個權限。
不錯,說得挺好,那你覺得這個可讀性的問題怎麼解決?
開常數定義不就行了。
READ_MESSAGE = 1 << 0
SEND_MESSAGE = 1 << 1
REPLY_MESSAGE = 1 << 2
HANDLE_REPAIR = 1 << 3
對,所以真正寫 Code 的時候,我們不需要直接操作 1、2、4 這些神奇數字。
簡單來說,到這裡我們只需要記住一件事:
一個整數,就可以直接表示一整組 Permission。
好,中場休息,喝口水。
我也。欸,剛剛幫你講課,等等下課後珍奶一杯啊。
知道了,我放冰箱,一年後自己來拿。
吔屎丫你!放一年的珍奶是能喝?
等下,老兄我這裡有個問題。

嘿?說。
你這邊的 account 我知道為了共用帳號區分密碼所以設計成
not unique
但這樣要怎麼防止老師的帳號跟學生衝撞到?
利用 position_id 跟 account 組聯合唯一鍵啊。
那這樣你之後訊息你要怎麼設計推播範圍?如果有老師跟學生的
account撞名怎麼辦?
Shift。
……等等。
不對!我想到了,我們其實根本不需要拿 account 來決定廣播要送給誰。
啊?不是說要發給班級嗎,不拿帳號怎麼知道哪個是哪班?
就是因為要發給班級,所以才不應該看 Account。account 是拿來登入的。
我們可以在 group 上新增一個欄位叫 type,用這個欄位去描述這個 group 的屬性。
例如:class、grade、office、root……
在搜尋時,filter 只要設定 type = class,就能快速找出能設定成目標的 group 了。
再利用共同父節點,對可廣播的組織結構進行還原。
所以你前面硬是多拆一張
groups出來的目的就是在此?
想多了,我本來只是想要做授權用的,沒想到在這裡派上新的用場了。
欸欸那你那個
parent_id是不是可以搞成這樣?
高中部
├── 高一
│ ├── 101
│ ├── 102
│ └── 103
├── 高二
│ ├── 201
│ ├── 202
│ └── 203
└── 高三
正確。
在目前的設計中,真正能成為發送目標的只有 type = class 的 Group。
中間的 Group 主要負責描述組織結構,以及讓 UI 可以一次選取一整個範圍。
等一下,那這樣有點香欸。
因為資料庫裡本來就是:
高二
├── 201
├── 202
└── 203
前端顯示出來也是:
高二
├── 201
├── 202
└── 203
你根本不用再另外維護一份「高二有哪些班」?
就說叫建模了,那必須建得像啊 ∠( ᐛ 」∠)_
聽起來很像是剛剛才發現可以這樣用,裝成自己早就想好了。
我不是說了我剛剛才發現可以這樣用嗎 ==。
不過這確實是我很喜歡這個設計的地方。
對使用者而言,它可能完全沒有任何改變。
但對系統而言,可維護性卻提升了:UI 所呈現的組織結構,和資料庫所理解的組織結構,變成了同一件事情。
而且這樣剛才那個
account撞名的問題其實也消失了。
因為這樣系統根本不關心你的登入帳號叫什麼。
它在廣播推送中只關心:「你到底是哪個 Group?」
對。
看來今天這隻小黃鴨還真的有點用。
我其實還有想到一個問題,你應該不會想到。
嘿?
你說,如果有兩個能修改組織描述的人,同時修改同一段組織結構,
會發生什麼事呢?
?
好刁鑽的問題,Race Condition?
對...…
我有兩個思路。
?這麼快?
- 想辦法讓整次修改變成一個不可分割的操作
- 直接上鎖
有料,但前者很難設計。
?為什麼?
做到那裡再跟你講,這個問題可以在後端解決。
吊我胃口。
對,就吊你們胃口。
不要臉。
(響過一陣敲鑼打鼓聲)
欲知後「逝」如何,且聽下回分解。