深入 REST
終于迎來了第二篇教程,大家久等了。本章我們將會深入 REST的概念。這一次,我們將會真正的遵循 REST 設計規范,將我們的應用打造為一個真正的 RESTful 應用。在上一章中,我們使用單文件的 django 制作了一個簡易的在線 python 解釋器,已經實現了基本的功能。這一次,我們將會把它變成一個真正意義上的應用,我們將會把代碼儲存進數據庫,給它添加增刪改查的功能。在本章,我們將會涵蓋以下知識點:
- REST 的一般規范。
- Django 的項目結構設計。
- Mixin 的編寫與應用。
- 了解并運用 UI “狀態” 概念。
- 建立起前端模塊化思想。
在上部分,我們將會學習需要用到的基本技術和概念,在下部分,我們將會動手創建我們的應用。不過,大家也可以先看看我們的應用最終完成是什么樣的:
我們可以很明顯的看到,雖然我們進行了諸多操作,但是頁面從來沒有刷新過。左邊的列表 UI 是動態刷新的。大家可以自己先運行著玩玩。代碼在這里
REST 是什么?
在網上能找到很多的資料,但是大多的 REST 相關的資料都顯得詰屈聱牙,晦澀難懂。其實這也不怪那些作者,因為 REST 這個名字就很讓人感覺到頭痛。 讓我們來慢慢梳理下這個在我們教程標題中的重要概念。
REST 是用來干什么的? REST 這個晦澀的概念如同其它計算機概念一樣,有的時候我們根本不知道它到底是怎么一回事,但是我們卻可以熟練的使用它。最典型的例子就是,雖然我們大家都在寫 Django ,但是又有多少人能清楚的解釋 MTV 模式是什么呢?這就像是做數學題,雖然我不明白解方程中的換元思想,但是我在不知不覺中就用到了它。 當我們提到 REST 的時候,后面通常會跟一個詞——API 。所以在一般的概念里,大家都把 REST 作為一種 API 的設計規范來理解。 所以 REST 就是用來設計 API 的嗎?至少到目前為止,我們所了解的 REST 就是用來設計 API 的。它規定了一堆很煩人的規矩,讓你去遵守它的規則,把你的代碼約束在一個固定的格式里。甚至在有的公司里,他們對 REST 的態度是完全不理睬的。好吧,這是人的天性,不喜歡被各種規矩所束縛,也不能怪他們。正如我不喜歡按時起床一樣,雖然起得來,但是我就是想要躺在床上。感覺自己冥冥之中在和什么東西作斗爭。我們在編寫 API 的時候也在作斗爭,想要清晰明確的表達 API 的語義,又不想將代碼寫的太繁瑣。有的時候為了解析參數,如果設計不當,你還會不得不自己手寫一個 parser 來解析你的 URL 參數。真是煩人。
REST 就是將我們從災難中解救出來的東西。現在讓我們來正式的認識一下它。REST ,全稱是 Representational State Transfer,中文翻譯是: 資源的表現層狀態轉換。好了,我想你看到這第一句就已經不想看了。WTF! 感覺中文都不通啊,這還理解個什么???淡定淡定。僅僅是為了尊重下將這個概念提出來的作者 Roy Felding ,他在他的論文《基于網絡的軟件架構》 中提出了這個概念。 我也不打算把這個概念解釋給你聽。我對于學習的哲學是:知識的習得來源于知識對你的啟發。所以,我會帶著大家來探索下,這個概念是個什么東西。現在,請你忘記 REST 這個概念。 忘記你曾經對 REST 的任何了解。
假設你是個倉庫管理員,管著一個巨大的倉庫。不僅看守著大門,還管理著從庫的進進出出,雖然僅僅是個小小的管理員,但你已經儼然是個倉庫經理的樣子了。為了更好的管理倉庫,你決定,每個進來拿貨的人,都必須出示相關的憑證。憑證上必須有身份證明,如果你是來取貨,那就還必須要有取貨證明,如果是來運貨,那就必須有運貨證明。你每天守在大門前,兢兢業業的工作,但是卻發現,隨著生意到了旺季,每天來來往往的人多起來,你一個人恨不得分身成 8 個,4 個負責管進貨的,4 個管出貨的。倉庫門前那惟一一條路都快被來往的汽車車輪上的泥給染成黃色了。累得夠嗆。感覺身體透支了。你必須想辦法解決下這個問題。
為了能夠快速的檢查憑證,你在倉庫入口的最前面搭了個小棚,專門檢查他們的憑證。而且,為了節約時間,不讓他們一拿就拿一堆資料出來,你規定所以的憑證相關信息都必須分門別類的寫到一張紙上。你現在只看他們一張紙!是的,管理員就是可以為所欲為,通過了驗證就蓋個章;等他們憑證檢查通過之后,再根據他們憑證上提供的信息,看看他們是要干什么。你多開了兩個倉庫進出口,進貨的人就從左邊走,出貨的從右邊走,來打理倉庫的從中間走。不讓他們都擠到一條路上。統一從后門出去。進貨的順著左邊那條路只能到一個空空的倉庫間,供給他們卸貨;出貨的順著右邊的路只能到一個小小的出貨間,他們把車后面的倉庫門對準出貨間的出口,貨物就會順著流水線自己劃到他們的車上。當然,哪些貨物可以被運到車上都是由在門口小棚的你來通過電腦全權控制的。打理倉庫的人只能進到自己的倉庫間,隨便他們在倉庫間里做什么,做完了就從后門走;所有的貨物都用大紙箱包裝,進行統一的管理。這樣,你每天就坐在小棚里,既沒有以前那沒累了,工作也更加的有條理。效率提高了一大截。
這個小故事編的有些蹩腳,不過它還是完美詮釋了對于 REST 的理解。我們的數據庫就是小故事中的倉庫,而 URL 就是我們的大門。 在以前,對數據庫做增加或者修改的操作都是由 POST 來完成的。 正如你改革倉庫之前的那樣,大家都走一條路。 在改革之后,大家各走各的路。 我們的 URL 也需要各走各的路。 往數據庫增加東西,請你用 POST 方法請求,修改請你用 PUT 方法來訪問。
查詢數據庫,請你用 GET 請求。 當然,最重要的是,把憑證都寫到一張紙上。 這張紙是什么呢?就是我們的請求頭,請不要 POST 里面有你的 UserAgent 信息,也不要把你的驗證信息放在 POST 里,把你是誰,你想干什么,你的權限有哪些都放到請求頭里。好讓我一看你的請求頭就知道你要干什么。
到這里,你可能還是會沒多大感覺。不就是 POST 增加, PUT 修改, GET 獲取詳細信息嗎?看看我們在故事里還遺漏了什么?那就是我們多開了出口。出口是什么?就是個管進出的大門嗎?請你換種思維。進出口,也是一種資源。進出口也是你管理的眾多資源之一。同樣的道理,請求頭也是資源。
你修路增加進出口,是在改變“進出口”這個資源,你給憑證上面蓋個章,是在改變憑證這個資源。所以,URL 的 Header 也是資源。在用戶訪問一次之后,你給用戶的 cookie 做變動的時候,就是在改變 cookie 這個資源。當你決定用 PUT 來修改資源的時候,是在改變請求方式這個資源。 不僅僅只有數據庫是資源。把倉庫里的貨物規定統一用大紙箱包裝,不管這些貨物原來是什么樣子,在他們從倉庫里運出來的時候看到的就是一個個個大紙箱。統一他們的規格。我們的 API 也是同理,不管你后端的資源是什么形式,我可以用 JSON ,可以用 XML 來表示他們。這些資源可能原來是在數據庫里的二進制數據,也可能是文本文檔,也可能是一個 excel 表格。但是當他們被展示在前端時,他們都統一轉化為了一模一樣的形式。
所以 REST 是什么,REST 是一套對資源進行操作和展示的規范。這套規范雖然出生于一篇關于網絡的論文,但是這并不妨礙將它運用到倉庫管理上。
設計 API
在了解 REST 的核心概念——一切皆資源之后,讓我們來看看,它是怎么具體運用到 API 上的。
很簡單,就以下幾個點。
一切皆是資源。不僅僅是對數據庫的請求是資源。包括網頁中的圖片,音樂, cookie ,session
都是資源。展示資源。原來的資源可能是表格,可能是文本。但是可以用 XML, JSON 等方式來展示他們
-
每一個資源的每一個狀態都應該有唯一的操作方式。
- POST 增加資源
- GET 請求資源
- PUT 修改資源
- DELETE 刪除資源
所以,資源——視一切為資源,展示——不管資源原來是什么形式,可以用 XML、JSON 等來展示他們,狀態——被增加、被修改、被查看、被刪除。連起來就是 資源的表現層狀態轉換。 這下就清晰多了。
在 API 的設計共識里,我們的 API 應該包含以下幾點:
- 版本信息。讓 API 的使用者清晰的知道使用的 API 是什么版本。
- 使用名詞的復數形式。比如:
http://example.com/users/
- 請求動詞分工明確。GET 就僅僅是請求資源,不會對被請求的資源有任何影響。 POST 就是增加資源,不能修改資源。
- 命名規范統一,不能混用。
- 駝峰式。
http://www.example.com/oldMan/
- 蛇形。
http://www.example.com/old_man/
- 駝峰式。
- 內容協商。內容協商就是,用戶希望以什么形式來展現資源。可以是 JSON 可以是 XML 可以是 IMAGE ,可以是 text 。
- 請求的所有元信息都放在請求頭。
- 當有其它資源動作時,在不違背基本的資源操作前提下,以 URL 參數的形式傳遞動作。比如:
http://www.example.com/users/?age=18&sex=girl
- 返回正確的狀態信息。比如大家最常見的 404 。
根據以上的知識,我們將要為我們的在線 Python 解釋器應用設計 API 。
其中,<arguement>
代表 URL 參數 arguement 。
- 添加代碼:
- POST
/v1/codes/
- POST
- 獲取所有 code 實例:
- GET
/v1/codes/
- GET
- 獲取指定 code 實例:
- GET
/v1/codes/<pk>/
- GET
- 修改指定 code 實例:
- PUT
/v1/codes/<pk>/
- PUT
- 刪除指定 code 實例:
- DELETE
/v1/codes/<pk>/
- DELETE
- 運行代碼:
- POST
/v1/codes/run/
為什么用 POST ? 因為運行代碼也是向后臺傳送新的代碼資源的方式。
- POST
- 運行特定代碼實例:
- GET
/v1/codes/run/<pk>/
- GET
- 運行并保存代碼實例:
- POST
/v1/codes/run/?save=true
- POST
- 修改并運行特定代碼實例:
- PUT
/v1/codes/run/<pk>/?save=true
- PUT
要是用 Django 的方式把我們的 URL 寫出來的話,就是這個樣子的:
code_api = [
url(r'^$', generic_code_view, name='generic_code'), # code 集合操作
url(r'^(?P<pk>\d*)/$', detail_code_view, name='detail_code'), # 訪問某個特定對象
url(r'^run/$', run_code_view, name='run_code'), # 運行代碼
url(r'^run/(?P<pk>\d*)/$', run_code_view, name='run_specific_code') # 運行特定代碼
]
api_v1 = [url('^codes/', include(code_api))] # API 的 v1 版本
api_versions = [url(r'^v1/', include(api_v1))] # API 的版本控制入口 URL
urlpatterns = [url(r'^api/', include(api_versions))], # API 訪問 URL
感覺 URL 的復雜度一下子就提升了不少。淡定,仔細看看它的結構,這樣的結構隨時可以供我們隨時進行修改。比如,我們的 API 升級換代了,隨時可以添加一個 api_v2
版本進去,而不用去硬編碼的修改原來的 API 。這樣既可以做到向后兼容,也可以方便的拓展。
當然,你可以先去體驗一下。本章的代碼我已經寫好。大家可以直接運行試試看,我已經準備好了幾個數據進去,運行 python manage.py runserver
,訪問 http://127.0.0.1:8000
。打開控制臺,看看在不同操作下都發送了哪些請求。代碼在這里。
開發應用
前期的知識準備
上部分只會講解最基本的技術和知識,正式的動手寫代碼會在下部分進行。
Mixin
Mixin 技術是面向對象編程的一個重要技能。它使得代碼的結構更加靈活,提高了代碼的復用性。在實現一個功能時,根據需要的 Mixin 搭積木就好了,特別的方便。在我們的應用中,我們將會大量的使用 Mixin 技術,并且,Mixin 技術在 Django 中也被頻繁的應用。所以掌握 Mixin 是很必要的。
Mixin 技術能夠產生,還是得益于對象的可繼承性。
比如我有一個對象叫做 Man
:
class Man:
def __init__(self,name, age, height):
self.name = name
self.age = age
self.height = height
我想讓他會抽煙,于是我寫了個抽煙 Mixin:
class SmokeMixin:
def smoke(self):
print('飯后一支煙,賽過活神仙!')
我還想讓他會掙錢,于是寫了個掙錢 Mixin:
class MakeMoneyMixin:
def make_money(self):
print('面向工資編程中')
我想讓他有個老婆:
class WifeMixin:
wife = None
def get_wife(self):
return '我的老婆叫{}'.format(self.wife)
最后,這個 Man
成了這樣的人:
class MyMan(SmokeMixin, WifeMixin, MakeMoneyMixin, Man):
wife = '王翠花'
my_man = MyMan(name='老王',age=30,height=170)
my_man.smoke() # 飯后一支煙,賽過活神仙!
my_man.make_money() # 面向工資編程中
my_man.get_wife() # 我的老婆叫王翠花
但是每個男人都是不同的,因為有的男人也單身:
class SingleMan(SmokeMixin, MakeMoneyMixin):
pass
sig_man = SingleMan(name='老李',age=25,height=165)
sig_man.smoke() # 飯后一支煙,賽過活神仙!
sig_man.make_money() # 面向工資編程
這樣我們就可以創造出很多個不同的男人,他們有相同點,也有不同點,我們要做的僅僅是把 Mixin 積木搭上去。我們的第二個男人的定義甚至只有一個 pass
。這種寫法在大型工程里非常常見,只有一個 pass
,但是卻有著豐富的功能。我們項目的視圖就使用了 Mixin 技術:
class APICodeView(APIListMixin, # 獲取列表
APIDetailMixin, # 獲取當前請求實例詳細信息
APIUpdateMixin, # 更新當前請求實例
APIDeleteMixin, # 刪除當前實例
APICreateMixin, # 創建新的的實例
APIMethodMapMixin, # 請求方法與資源操作方法映射
APIView): # 記得在最后繼承 APIView
model = CodeModel # 傳入模型
def list(self): # 這里僅僅是簡單的給父類的 list 函數傳參。
return super(APICodeView, self).list(fields=['name'])
這已經是 CodeAPI 完整代碼了,我們并沒有在視圖中手動的編寫功能,只是簡單的繼承了 Mixin ,我們甚至都沒有寫 get
,post
, 等方法,在 Mixin 中也沒有寫!這些神奇的功能和特性都是由 Mixin 提供的。具體的編寫方法我們將會在第二章的下部分進行講解。保持你的好奇心,或者現在翻到文章開頭,去 github 看看這些神奇的 Mixin 是如何編寫的。
狀態
這是前端的知識點。在大家編寫前端的時候,一定要把邏輯思維轉換為視覺思維。比如,當你看到一盞燈亮了的時候,你的第一反應一定是“燈亮了”,而不是“電流通過導線,讓燈絲發熱發光了”。你看到一輛汽車從禁止變成了運動的狀態,你不可能想到的是“摩擦力反作用于車輪讓汽車動了起來”。所以,當頁面中的 UI 發生變化時,變化的是 UI 的狀態。一盞燈有熄滅和點亮兩種狀態,一輛車有發動和禁止的狀態,一個人有吃飯,睡覺,走路等狀態。拿我們應用中的表格來說,它的狀態就是有多少行。比如,原來只有 5 行,你添了一行上去,就變成了 6 行,此時這張表格從有 5 行的狀態變成了 6 行的狀態。 這就是狀態。
UI 的狀態,就是它的樣子,UI 的樣子是由它相關聯的數據決定的。這是很重要的概念,在我們編寫我們項目的前端時會發揮巨大作用。同時,這也是為講解 flux 和 Vuex 打下基礎。
第二章的上部分就結束了。在下部分,我們將會真正開始構建我們的應用。讓你的應用告別緩慢的刷新,笨搓搓的 POST 之后還要再刷新整個頁面。同時,我們也將會構建我們的靜態文件服務。我們下周見。