程式設計師不得不知道的 API 介面常識

胡塗阿菌發表於2022-05-02

說實話,我非常希望兩年前剛準備找實習的自己能看到本篇文章,那個時候懵懵懂懂,跟著網上的免費教程做了一個購物商城就屁顛屁顛往簡歷上寫。

至今我仍清晰地記得,那個電商教程是怎麼定義介面的:

管它是增加、修改、刪除、帶參查詢,全是 POST 請求一把梭,比如下面這樣:

修改使用者的收貨地址

POST /xxx-mall/cart/update_address

現在看來,全部用 POST 請求估計是為了傳參方便吧。

那個時候自己也沒有一個 API 介面需要設計 的意識,跟學過類似教程的朋友應該懂的,老師敲一行程式碼學生跟著敲一行。如果沒人提這個事情,正式工作進入團隊後,是很容易出醜的......(作者親身經歷,捂臉)

本文就不用 PPT 教案上的那種官方腔介紹 API 介面是個什麼概念了,阿菌比較希望用一種聊天的方式和大家分享下現有的一丁丁和 API 相關的小心得,文章會分為五小塊:

  1. 初識 API 介面
  2. 關於 API 限流
  3. 關於 API 版本管理
  4. 關於 API 許可權與安全
  5. 關於團隊間的 API 互通

注:這是一篇會羅列很多知識點的文章,您可以按需深度搜尋進行更進一步的學習。當年渴望看到這樣的文章的原因是:學習一個知識點其實只需要時間,對學生而言,時間不是問題,問題在於不知道該往哪些方向學 T_T 。本文希望通過串講,梳理一下個人當前瞭解到的 API 知識體系,整理的同時也希望能對大家有一點點幫助。

1. 初識 API 介面

記得在我初學 web 開發的時候,後端框架相關的教程基本都會教學生寫渲染模版(不分語言),也就是說後端返回的是整個網頁的資料,瀏覽器只負責渲染。

一般這類模版在後端都會對應一個路由,比如前端想登入一個看使用者資訊的頁面,在 url 中輸入的訪問地址大概長這樣:

https://ajun24.com/user

那個時候,我以為這樣的路由地址就是 API 概念的全部了......

值得一提的是:絕大部分後端教程都會簡單教一下前端,在前端的補充教程中有一個必學的知識點,叫:AJAX。

老師大概率會演示一下 AJAX 這個技術怎麼使用,寫個小 Demo,告訴大家可以這樣在頁面上傳送非同步請求。

這個技術請求的後端介面一般不會跳轉或返回一個 html 頁面,大概率會返回一份 json 資料。我一直對這樣的介面和返回頁面資料的介面有著迷之困惑。直到我大三實習時明白了什麼叫前後端分離開發......

但是為了教學方便,完整專案大概率還是會用渲染模版的方式講解,畢竟只在一套系統裡寫程式碼演示會方便很多。

當年就是這樣學完了第一個專案,雖然對如何做一個軟體系統有了整體的認識,但是對 API 設計的認識是非常弱的。

其實我在學 AJAX 這個知識點的時候就在想:有沒有可能全部資料都通過類似 AJAX 這種方式獲取?這樣感覺會更方便一些。

後來實習的時候,前端同學告訴我:開發前需要先定義 API 哦。

當然,他還告訴我:刪除一個東西不能用 POST 請求哦 ^_^(捂臉)

後來導師提醒我:你需要去了解一下如何設計 REST 風格的 API。

自從那次出醜後,我明白了一個事情,一定要敢於把自己的不足"暴露"給願意指點你的人看。就好比我們讀大學的時候最好要努力去找一份實習,每一次被拒以及每一份 offer 都會告訴我們,這個社會需要什麼樣的人才,什麼樣的技能可以幫助我們謀得一份工作。

在正式的面試場合下,或許我們更應該條理清晰地和麵試官介紹什麼是表現層狀態轉換,但是在這篇文章中,我想把 REST 風格的 API 稱為更容易讓人看懂的 API。

大家會發現符合 REST 風格的 API 能非常容易地讓別人知道呼叫這個 API 能幹什麼,比如:

GET     /users                # 查詢使用者資訊
PATCH   /users/{user_id}      # 根據 id 更新某個使用者的資訊,只部分更新客戶端提交的資料

按約定寫 API 就好比在 IT 領域說行話,大家只要看見你的 API,就知道你能提供什麼樣的服務。

有同學可能會好奇為什麼要遵守規範?

假如,我們負責的系統僅聯絡到我們身邊同事的系統,那約定 API 的時候只需要打個招呼,或在聊天工具上簡單說明一下就可以了,甚至可以沒有文件。

但在很多情況下,我們的系統是要被很多其他系統呼叫的,大家想象一下我們去呼叫雲廠商 API 的場景:別人的工程師大概率不是我們的微信好友,大多數時候是沒有人站在我們身邊手把手告訴我們 API 怎麼呼叫的。這個時候想呼叫對方提供的 API,就得看對方提供的 API 文件。如果對方的 API 不按照規範定義,那 API 文件絕對像天書一樣難讀。

看天書的痛苦,保證大家體會一次足以終生難忘。

良好的 API 文件一般會像工具手冊,沒有太多學習成本,否則別人下一次很有可能就不使用我們的服務了 T_T

所以先系統地學習 API 定義約規,再編寫 API 文件,然後根據設計進行開發是一個比較好的研發流程。

接下來的問題是,在瞭解了 API 的規範後,如何寫出良好的 API 文件呢?

眾所周知,寫文件對程式設計師來說是一件非常痛苦的事情,一想到學習寫專業的 API 文件還需要學習成本,實在是勸退。這個時候我們可以通過一些自動化工具輔助我們完成一篇優秀的 API 文件,比如我們可以使用 swagger,它可以通過我們的程式碼自動生成 API 文件。

最近還看到不少基於 API 的研發測試一體化產品和平臺,感覺一站式的、流水線式的研發管理是未來的趨勢呀!

2. 關於 API 限流

API 寫出來後會被呼叫,但由於計算機 & 網路系統的侷限性,我們的 API 介面是不可以被無限制呼叫的。

大家可以隨便到網上挑一個比較專業的 API 文件看,比如大家可以去看雲廠商對外提供的 API,基本都會看到一個介面頻率呼叫限制,比如:單使用者呼叫頻率為 30 次 / 秒。

所以當我們在設計 API 的時候,限流是一個不得不考慮的事情(內部自己弄著玩的不算哈,泛指面向使用者的系統)

在設計限流之前,我們首先要知道自己系統的瓶頸。假設我們的 API 純粹呼叫自家的技術元件,比如資料庫,訊息佇列等中介軟體,這個時候我們可以通過壓測得知一個介面的最大承受能力;假設我們的系統是一箇中間系統,需要依賴其他系統的介面完成業務,那麼這個時候基於木桶原理,我們介面的可訪問頻率就會受限於其他業務系統。

瞭解完自身專案的訪問瓶頸後,需要考慮自身系統的架構,假設我們的系統是單體部署:

那這個時候我們只需要簡單的令牌桶演算法即可以完成限流,下面是一個極簡的令牌桶演算法實現 Demo:

"""
簡單解釋:
實現一個固定容量的桶,按一定的頻率往桶內放令牌直至桶滿,每當執行一個限頻操作需要從桶中獲取一個令牌才能繼續操作,若桶中沒有令牌,則進行等待
往令牌桶中放令牌的操作不便按照原概念實現,所以放令牌這步放到取令牌的時候進行。我們根據當前取令牌的時間減去上一次取令牌的時間差,就能得知這段時間內增加了多少個令牌。
"""

class TokenBucket(object):

    # rate 是令牌桶生產令牌的速率,capacity 是令牌桶生產令牌的速率
    def __init__(self, rate, capacity):
        self._rate = rate
        self._capacity = capacity
        self._current_amount = 0
        self._last_consume_time = int(time.time())

    # token_amount 是執行一次操作需要的令牌數量
    def consume(self, token_amount):
        # 通過時間差乘速率,得到令牌的增量
        increment = (int(time.time()) - self._last_consume_time) * self._rate
        時間差乘速率,得到令牌的增量  
        self._current_amount = min(
            increment + self._current_amount, self._capacity)
        # 令牌數量不夠則不允許操作
        if token_amount > self._current_amount:
            return False
        # 更新最後一次操作時間
        self._last_consume_time = int(time.time())
        # 結算當前的令牌數量
        self._current_amount -= token_amount
        return True

但實際工作中,我們部署單體架構的機會不多,現在的大公司都構建有自己的雲生態,業務部門上雲後可快速進行擴縮容,所以我們的系統很有可能會進行叢集部署,使用者的請求通過代理層負載均衡至各個後端節點:

這個時候上面的 15 行程式碼顯然就不符合我們的分散式系統架構,我們得考慮更復雜的限流演算法實現了(這裡不是指令牌桶演算法不合適,是指令牌桶演算法的實現方式需要改進),當然這個實現大概率會放在代理層了,而不是實現在我們的業務層。

大家可以上網看一下主流雲廠商提供的雲服務,很多都會提供 API 閘道器,對應著我們上面提到的代理層。

假如一個公司有統一的 API 閘道器服務,或有類似的代理服務,業務部門是可以在 API 限流這件事情上省下很大功夫的。我有時候想,當越來越多的中小企業基於巨無霸雲廠商搭建業務,大家要考慮的技術性問題就會越來越少,越來越專注於業務,這到底是一件好事還是壞事呢?

3. 關於 API 版本管理

介紹完 API 及限流的基本知識後,談一下和業務比較相關的 API 的版本管理。

在沒真正接觸業務前,我以為只有軟體需要做版本管理,為啥 API 也要做版本管理咧?

其實原理是一樣的,軟體會根據需求不斷迭代版本,API 同樣也會迭代版本,但秉承開閉原則,為了不影響之前的業務,我們最好不要改動原有的 API。

因此,我們設計 API 的時候可以指定版本號,比如上述的例子:

GET     /users  # 查詢使用者資訊

我們可以統一定義成:

GET     /api/v1/users  # 查詢使用者資訊

假設這個介面有了第二個版本,我們就可以通過版本號進行區分了:

GET     /api/v2/users  # 查詢使用者詳細資訊

換作兩年前的我可能會對 API 版本管理無感,但大家嘗試把自己代入以下場景就能明白了:

比如我們的產品讓我們出一套新的查詢使用者 API,假設我們沒有定義版本號,由於 /users 這條路由已經在用了,逼不得已,我們就會定義一個新的:

GET     /get_user_info  # 查詢使用者資訊

新介面和老介面的意思差不多,如果我們一直負責這個系統,那還好說(心裡有不同版本的區分)

但假如這個系統換了另一個接班人,當他面對大量意義接近的介面時,肯定會懷疑人生的......(屎山就是這樣來的)

4. 關於 API 許可權與安全

接著我們要思考一下 API 的許可權與安全問題。

還是回到初學的時候,那個時候我對 API 介面許可權完全沒有任何概念。老師為了快速教會我們開發系統,很多介面的設計是完全裸奔的。如果不瞭解一點點相關的知識,工作中會容易給別人一種考慮事情不周到的感覺。

在實際生產中,介面是不可以不做許可權校驗的,如果我們的系統暴露在公網,還沒有許可權校驗的話,系統估計很快就掛了;內部涉及機密的系統,許可權校驗則更為嚴格。

關於許可權校驗,個人暫時分為三個維度,三個維度或許可以對應三種業務型別:

  1. 第一種是直接針對 IP 設定白名單,這種方式比較適用於客戶端有限且固定的內部系統;
  2. 第二種則是設定許可權校驗流程,比如採用 Token 鑑權,較多用於 ToB 業務。大家在雲廠商註冊賬號後基本都會得到一對金鑰,後續的 API 呼叫一般都需要先根據金鑰進行許可權認證;
  3. 第三種是通過使用者登陸判斷許可權,較多用於 ToC 業務,比如我們登陸京東,登陸淘寶需要賬號,沒有登陸就訪問不了購物車等頁面。

值得一提的是,許可權設計是另一個維度的知識,除了第一個維度,後兩者其實都可以單獨成立一個系統的。比如公司的使用者管理系統,中心化許可權認證系統等等。

許可權校驗關乎著公司財產安全,所以不可忽視,很多時候我們甚至需要在 API 設計層面考慮安全問題。再次引用商城的例子,比如登陸後獲取使用者購物車的訂單,API 大概率會設計成這樣子:

GET /users/287435/orders

但直接暴露使用者 id 或許不是一個明智的選擇,有可能被不法分子利用,我們可以換種方式,比如用以下的方式替代:

GET /users/me/orders

總而言之,API 的設計除了參考規範外,還需要根據自身業務情況進行更進一步的安全考慮。

5. 關於團隊間的 API 互通

最後是一個延展性話題,相信大家都感受到了我們正身處於一個資料時代,我們的個人資訊,包括各類行為喜好,都存放在各家網際網路公司的資料倉儲裡,企業們可能比我們更瞭解我們自身,網上也有很多與資料資產有關的話題。

既然已經把資料比作資產了,而資產流動性又是一個經久不衰的話題,所以各類資料的開放性問題也很受關注。而資料對外開放,必然就會涉及到 API 介面。

當然作為一隻小碼農,我的視野極其有限,很難從一個較高的層次去談論企業的資料問題。但在工作中,當其他業務團隊提出要呼叫自己負責的專案的 API 介面時,也是需要進行多方位考慮的。

本文列出的就是個人會從技術上考慮的點,總結成三句話就是:

  • 你能看懂我的 API 嘛?
  • 別把我的 API 打爆哦!
  • API 要經允許才能使用哦!

由於API 的這個概念實在是太大了,我能接觸的也是一些些皮毛,但時不時總結整理一下還是大有裨益的。

最近要著力負責專案的開發,所以戲謔的漫畫可能會稍微少一些些,會更多地分享自己在實踐過程中關於工程的思考。下期再見咯~

相關文章