Identifier Design Consideration


整理一些設計 Identifier 要考慮的事情,特別是要揭露給使用者的時候,像是 RESTful API 的設計。原文來自 facebook 寫的草稿。


Consideration

用途

Identifier (經常縮寫成 Id or ID),本質就是 識別,用來找到特定的資源 / 物件,在系統中具備 唯一性 (Unique)

生活中常見的例子:

  1. 身分證字號:用於識別個人身份,是各國政府機構核發的獨特識別碼。
  2. 產品編號:用於識別產品,通常由製造商分配,有助於追蹤產品的製造和銷售情況。
  3. 網域名稱 (DNS): 用於識別網路上的各種資源,例如網站、郵件伺服器等。
  4. IP Address:用於識別網路上的設備,是通訊協定中的一個重要元素。
  5. 手機 IMEI 碼:用於識別手機設備,有助於防止偷竊和盜版。
  6. 電商的訂單:訂單編號、取貨號碼

適用範圍 (Scope)

前面一段舉例很多例子,但實際上 Id 都有適用範圍。

  1. 身分證字號:適用範圍是國家之內,如果是護照號碼,範圍就是全球。
  2. 產品編號:適用範圍就是生產公司的範圍。
  3. IP Address:以 IPv4 而言,本身就有區分 保留 (Private | Broadcast) / 公有 (Public) … 等,Public 就是全球範圍網域 (WAN)、私有就是私有網域 (LAN) …
  4. 登入帳號:App 登入的帳號本身則是看使用 SSO 還是 Local,就可以區分範圍。

EC2 Instance Id 當例子,EC2 InstanceId 的設計是為了 可程式化 (Programmable),具備全局的唯一性 (Global Unique)。也就是每個 InstanceId,不管是在哪一個 AWS Region or Zone、或者跨 AWS Account,都是唯一的 Id,而且不可重複使用。詳細參閱 Instance ID uniqueness

程式語言裡的變數可以當作一種 id,範圍有 global variable or local variable。當名稱衝突的時候,依照程式語言的設計,可能會有不同層度的錯誤或者警告。

1
2
3
4
5
6
7
8
9
>>> name = "rick"
>>> def getName():
... name = "jack"
... return name
...
>>> print(name)
rick
>>> getName()
'jack'

縮小範圍

電商為例,訂單編號本身是個全域變數,是電商系統的在看的,不管有怎麼好的設計、編碼規則,單一個編號通常會很長。

設計時要讓使用者可以快速定位,基本的做法就是改變範圍,所以現在很常用的縮小範圍就是:

  1. 區域範圍:一家超商能放的商品數量多則上百件、少則數十件
  2. 反向檢索:透過使用者的手機末幾碼,反向索引編號。因為訂單編號是電商系統產生的,本質上跟使用者示弱關聯,取貨的是使用者,用一個弱關聯的號碼讓使用者取貨,是不好的使用體驗。所以反向的用使用者的電話號碼或者身分證末幾碼,可以達到同樣效果,同時可以快速過濾。

可數不可數

這概念跟英文的 可數 / 不可數、數學上的 窮舉法 (Proof by exhaustion)、程式語言的 迴圈 (for)white / iterate (迭代器) … 等概念是一樣的。

Id 的數量會很多,通常會歸類成不可數,像是:

  1. 每隻手機 IMEI 碼:代表這只手機的唯一性與合法性。但手機的數量很多,基本上發展到現在已經數量多到不知道怎麼數了 …
    • 可以透過 *#06# 查到
  2. 電腦網卡的位址 (Mac Address)
  3. 全球的網頁數
  4. 地球的沙子

可數的 Id 相對於不可數的是比較少,數量級大概會少於 100 以內,比較多的會是 1000 以內,不管怎樣,會 有明確上限,可以 正面表列列舉清單 。像是:

  1. 台灣郵遞區號
  2. 國際電話號碼的前幾碼
  3. AWS region 代號

可討論:

  • 電腦的編碼系統 (Unicode) 嚴格講是個可數系統,因為字元 (character) 會直接正面表列被定義,像是英數字的數量、雙位元語系數量 (中日韓)、後來定義的 emoji 等符號。不過 Unicode 的數量很大,版本 15.1 共定義了 149,813 個字元。

設計系統的時候,要稍微思考一下,這個東西的數量級是可數 or 不可數。

可數有點像是 寵物 ,不可數像是 畜生 來想,更多參閱 Cattle vs Pets | DevOps Explained


格式

設計 Id 最多人討論的是格式 (Format / Pattern),格式要考慮的有幾點:

  1. 數量級 (scale):可數 (countable)、不可數 (uncountable)
  2. 理解性 (understandability):可理解、部分理解、完全不可理解

這樣的設計角度,會有以下六種排列組合的設計思路:

A1 可數 + 可讀性

可數 代表數量極有限,數量 <= 100 or 1000,通常會搭配 可讀性設計原則,也就是定義一個命名規則,讓我們使用上可以一目瞭然。

幾個來自 AWS 的經典例子:

  1. region code: ap-northeast-1, us-east-1, us-west-2 … etc,詳細參閱 Regions and Zones
  2. EC2 Instance Type: t2.small, c5.large, r5.xlarge … etc,詳細參閱 Instance types
  3. Security Group Name: 允許使用者輸入一段自己定義的名稱,官方文件說明,命名規則可以參閱我的設計:Plan and Design Multiple VPCs in Different Regions

這類通常在程式語言或者 API,都會直接用 enum 的方式列舉。

A2: 可數 + 部分理解

可數 可以搭配固定理解的組合,像是:

  1. 系統 Log 的 ErrorCode:E10-03 這樣代表一個錯誤訊息, 有個編碼 [IEW][0-1]{2}-[0-1]{2} 這樣的規則….
  2. 台灣身分證字號:規則可以參閱 如何寫程式處理中華民國的身份證字號?, Rust 實作
  3. HTTP Status Code:5XX, 4XX … 等,可數、有明確上限

A3: 可數 + 完全不可理解

可數 + 完全不可理解 這個組合比較少用,我會用在資訊敏感的,卻要對外直接揭露的,像是企業內部對外 egress 的節點。

實際上有些公司對外的 egress 可能是不可數的 XDD

B1: 不可數 + 可讀性

不可數 + 可讀性:這個設計沒啥意義,但是工程師喜歡挑戰這種東西,像是 …. docker container 的 random name …

B2: 不可數 + 部分理解

不可數 + 部分理解,這其實跟 可數 + 部分理解 是類似的概念。

最經典的例子是 UUID 的設計,基本上就是懶人設計。

另外一個很常見的應用案例,AWS 的 resourceId (insanceId, vpc id, ebs id … etc) 都是這種設計.

著名的專案管理系統 JIRA 的 Issue number 可以前綴專案代號,也是一樣的概念。

B3 不可數 + 不可理解

不可數 + 完全不可理解,不知道 UUID 結構的會把它擺這裡。。。基本上這個設計大概就是個只想要一個唯一就好的設計,內容是啥完全不想管。。。像是 MySQL 常用的 auto-increment。

上述六種排列組合,整理常見範例如下表:

實作與 API 設計

可數的 Id 通常在程式語言或者 API 參數,都會直接用 enum 的方式列舉,也就是透過列表方式呈現給使用者查詢。

不可數的 Id 則轉化成 API 讓使用者自行查詢。


常見問題

Q: Id 碰撞

通常是使用不適當的屬性作為 Id,像是電腦的 hostname 當作唯一個識別,這個例子在 Windows 體系的 Active Directory (AD) 很常見。

解法的邏輯就是透過 限縮範圍亂數命名,降低碰撞。

  1. 透過子網域的方式
  2. 透過亂數命名 hostname,但是用 attribute 標記 部門、組織、專案屬性

Q: 數量太龐大,難以使用

資源的數量太多,造成 Id 長度很大,使用時需要提供完整 Id 資訊,才能確認。對於人而言,這是個苦差事。

幾種解法:

  • QR Code:透過圖形化的方式,轉化超長的 Id
  • Bar Code:概念同 QR Code,經常用在水費、電費帳單
  • 縮小範圍:常見的例子是電商購物取貨,透過兩階段範圍限縮,達到精準取貨:
    1. 限縮範圍:超商 本身就是個小範圍的籃子 (Bucket),
    2. 索引反轉:原本訂單編號是難以理解的,反轉成使用者自身的資訊,像是身分證末三碼、手機末三碼,重新對應到訂單編號

常見的實作

幾個常見的 ID 實作 (以下部分由 ChatGPT 產生):

UUID (Universally Unique Identifier)

設計考量: UUID 是由標準定義的一組算法來生成的,其中包括基於時間、隨機數或其他唯一性來確保生成的 ID 全球唯一。常見的版本包括 UUIDv1(基於時間戳)、UUIDv4(隨機生成)等。

適合的場景: UUID 非常適合需要全局唯一性的場景,例如分佈式系統中的唯一標識符,以及需要在不同系統之間進行數據交換的情況下。

針對資料庫設計的 TSID (Time-Sorted Unique Identitifer)

雪花演算法 (Snowflake ID)

設計考量: Snowflake ID 是 Twitter 提出的一種分布式 ID 生成算法,它結合了時間、機器 ID 和序列號來生成全局唯一的 ID。時間戳確保 ID 是有序的,機器 ID 確保唯一性,序列號則確保在同一時間內生成多個 ID 時的唯一性。

適合的場景: Snowflake ID 適用於需要大量且分佈式生成的 ID,例如分佈式系統中的數據庫主鍵、消息隊列的消息 ID 等場景。

Auto-Increment

設計考量: 自增 ID 是由資料庫管理系統(如 MySQL、PostgreSQL)自動生成的,每次插入一條新記錄時,ID 會自動加一。這樣的設計簡單且高效,但僅限於單機系統或資料庫範圍內的唯一性。

適合的場景: 自增 ID 適用於單機系統或者資料庫範圍內的唯一性要求不高的場景,例如小型網站的用戶 ID、文章 ID 等。

KGS (Key Generation Service)

設計考量: KGS 是一種由中央服務器生成的 ID,並使用了分佈式的生成機制,確保全局唯一性。它可以採用類似於 Snowflake 的方法,但由中央服務器統一管理 ID 的生成,以防止 ID 的重複和衝突。

適合的場景: KGS 適用於需要強大唯一性保證和集中管理的分佈式系統,例如大型企業應用系統中的全局唯一標識符、金融交易系統中的訂單 ID 等。


結論

Id 設計背後要思考用途、未來性、實作的選擇,上述還有很多沒有整理:

  • 驗證:如何驗證這個唯一性是否有被篡改過,也是設計要考慮的
  • 外部操作與內部操作:外部指的是 User Interface 的 Id,內部指的系統內部操作。前者是會揭露給使用者,使用者會拿來操作與溝通,後者則是系統內部自己在用的。

這些之後有空再來整理。


題外話: 你是 ID 還是 Id?


延伸閱讀

站內資料

參考資料



Comments

2024/03/24 13:30:00





  • 全站索引
  • 關於這裏
  • 關於作者
  • 學習法則
  • 思考本質
  • 一些領悟
  • 分類哲學
  • ▲ TOP ▲