在傳統的Web應用開發中,大多數的程式設計師會將瀏覽器作為前後端的分界線。將瀏覽器中為使用者進行頁面展示的部分稱之為前端,而將執行在伺服器為前端提供業務邏輯和資料準備的所有程式碼統稱為後端。所謂前後端分離的開發,就是前後端工程師約定好資料互動介面,並行的進行開發和測試,後端只提供資料,不負責將資料渲染到頁面上,前端透過HTTP請求獲取資料並負責將資料渲染到頁面上,這個工作是交給瀏覽器中的JavaScript程式碼來完成。
使用前後端分離開發有諸多的好處,下面我們簡要的說下這些好處:
- 提升開發效率。前後端分離以後,可以實現前後端程式碼的解耦,只要前後端溝通約定好應用所需介面以及介面引數,便可以開始並行開發,無需等待對方的開發工作結束。在這種情況下,前後端工程師都可以只專注於自己的開發工作,有助於打造出更好的團隊。除此之外,在前後端分離的開發模式下,即使需求發生變更,只要介面與資料格式不變,後端開發人員就不需要修改程式碼,只要前端進行變動即可。
- 增強程式碼的可維護性。前後端分離後,應用的程式碼不再是前後端混合,只有在執行期才會有呼叫依賴關係,這樣的話維護程式碼的工作將變得輕鬆愉快很多,再不會牽一髮而動全身。當你的程式碼變得簡明且整潔時,程式碼的可讀性和可維護性都會有質的提升。
- 支援多終端和服務化架構。前後端分離後,同一套資料介面可以為不同的終端提供服務,更有助於打造多終端應用;此外,由於後端提供的介面之間可以透過HTTP(S)進行呼叫,有助於打造服務化架構(包括微服務)。
接下來我們就用前後端分離的方式來改寫之前的投票應用。
剛才說過,在前後端分離的開發模式下,後端需要為前端提供資料介面,這些介面通常返回JSON格式的資料。在Django專案中,我們可以先將物件處理成字典,然後就可以利用Django封裝的JsonResponse
向瀏覽器返回JSON格式的資料,具體的做法如下所示。
def show_subjects(request):
queryset = Subject.objects.all()
subjects = []
for subject in queryset:
subjects.append({
'no': subject.no,
'name': subject.name,
'intro': subject.intro,
'isHot': subject.is_hot
})
return JsonResponse(subjects, safe=False)
上面的程式碼中,我們透過迴圈遍歷查詢學科得到的QuerySet
物件,將每個學科的資料處理成一個字典,在將字典儲存在名為subjects
的列表容器中,最後利用JsonResponse
完成對列表的序列化,向瀏覽器返回JSON格式的資料。由於JsonResponse
序列化的是一個列表而不是字典,所以需要指定safe
引數的值為False
才能完成對subjects
的序列化,否則會產生TypeError
異常。
可能大家已經發現了,自己寫程式碼將一個物件轉成字典是比較麻煩的,如果物件的屬性很多而且某些屬性又關聯到一個比較複雜的物件時,情況會變得更加糟糕。為此我們可以使用一個名為bpmappers
的三方庫來簡化將物件轉成字典的操作,這個三方庫本身也提供了對Django框架的支援。
安裝三方庫bpmappers
。
pip install bpmappers
編寫對映器(實現物件到字典轉換)。
from bpmappers.djangomodel import ModelMapper
from poll2.models import Subject
class SubjectMapper(ModelMapper):
class Meta:
model = Subject
修改檢視函式。
def show_subjects(request):
queryset = Subject.objects.all()
subjects = []
for subject in queryset:
subjects.append(SubjectMapper(subject).as_dict())
return JsonResponse(subjects, safe=False)
配置URL對映。
urlpatterns = [
path('api/subjects/', show_subjects),
]
然後訪問該介面,可以得到如下所示的JSON格式資料。
[
{
"no": 1,
"name": "Python全棧+人工智慧",
"intro": "Python是一種計算機程式設計語言。是一種面向物件的動態型別語言,最初被設計用於編寫自動化指令碼(shell),隨著版本的不斷更新和語言新功能的新增,越來越多被用於獨立的、大型專案的開發。",
"is_hot": true
},
// 此處省略下面的內容
]
如果不希望在JSON資料中顯示學科的成立時間,我們可以在對映器中排除create_date
屬性;如果希望將是否為熱門學科對應的鍵取名為isHot
(預設的名字是is_hot
),也可以透過修改對映器來做到。具體的做法如下所示:
from bpmappers import RawField
from bpmappers.djangomodel import ModelMapper
from poll2.models import Subject
class SubjectMapper(ModelMapper):
isHot = RawField('is_hot')
class Meta:
model = Subject
exclude = ('is_hot', )
再次檢視學科介面返回的JSON資料。
[
{
"no": 101,
"name": "Python全棧+人工智慧",
"intro": "Python是一種計算機程式設計語言。是一種面向物件的動態型別語言,最初被設計用於編寫自動化指令碼(shell),隨著版本的不斷更新和語言新功能的新增,越來越多被用於獨立的、大型專案的開發。",
"isHot": true
},
// 此處省略下面的內容
]
關於bpmappers
詳細的使用指南,請參考它的官方文件,這個官方文件是用日語書寫的,可以使用瀏覽器的翻譯功能將它翻譯成你熟悉的語言即可。
接下來我們透過前端框架Vue.js來實現頁面的渲染。如果希望全面的瞭解和學習Vue.js,建議閱讀它的官方教程或者在YouTube上搜索Vue.js的新手教程(Vue.js Crash Course)進行學習。
重新改寫subjects.html頁面,使用Vue.js來渲染頁面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>學科資訊</title>
<style>
/* 此處省略層疊樣式表 */
</style>
</head>
<body>
<div id="container">
<h1>扣丁學堂所有學科</h1>
<hr>
<div id="main">
<dl v-for="subject in subjects">
<dt>
<a :href="'/static/html/teachers.html?sno=' + subject.no">
{{ subject.name }}
</a>
<img v-if="subject.is_hot" src="/static/images/hot-icon-small.png">
</dt>
<dd>{{ subject.intro }}</dd>
</dl>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script>
let app = new Vue({
el: '#main',
data: {
subjects: []
},
created() {
fetch('/api/subjects/')
.then(resp => resp.json())
.then(json => {
this.subjects = json
})
}
})
</script>
</body>
</html>
前後端分離的開發需要將前端頁面作為靜態資源進行部署,專案實際上線的時候,我們會對整個Web應用進行動靜分離,靜態資源透過Nginx或Apache伺服器進行部署,生成動態內容的Python程式部署在uWSGI或者Gunicorn伺服器上,對動態內容的請求由Nginx或Apache路由到uWSGI或Gunicorn伺服器上。
在開發階段,我們通常會使用Django自帶的測試伺服器,如果要嘗試前後端分離,可以先將靜態頁面放在之前建立的放靜態資源的目錄下,具體的做法可以參考專案完整程式碼。