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

▲ TOP ▲