Django RESTful 系列教程(二)(上)
深入 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 之后还要再刷新整个页面。同时,我们也将会构建我们的静态文件服务。我们下周见。