軟體工程實踐之 django/python
軟體工程實踐系列文章, 會著重講述實際的工程專案中是如何協作開發軟體的。 本文主要介紹了 django/python 系列的工具鏈。
outline
本文包括以下內容:
- outline
- django: 一個搭建後端服務的工具箱。
- framework: django vs flask/tornado/spring/laravel
- restful: django/restframework/swagger
- worker: django/uwsgi/gevent/celery/channels
- database: django/mysql/sqlite/migrations
- python: 一門依賴開發者的語言。
- developing: gitlab/pipenv/docker
- quality: unittest/pytest/flake8/pylint/yapf
- deploy: fabric/aws/nginx
- conclusion
django
django 是一個大名鼎鼎的後端開發框架, 它自己的口號是 the web framework for perfectionists with deadlines.
在我用 django 開發的這幾年來, 我覺得它是一個邏輯上自洽, 並且為了邏輯自洽甚至捨棄了一部分功能的框架。
framework
search google for django vs
講框架避免不了的是同行競爭, 比如到網上搜一下 django vs ...
就有一大堆搜尋結果。 其實框架之間的比較是很難的, 每種框架都有自己適合的業務場景。
xkcd-927: standards
django 最大的特點就是 Model
是一等公民 。 在 django 中的所有的操作都會跟 Model
相關, 比如它提供了自帶的強大 ORM, 也有一系列掛載在 Model
上的校驗等。
個人感覺在專案的業務需求達到了某種程度的多樣化以後, 基礎框架用什麼並不重要, 適合開發團隊才是最重要的。
鑑於本文的標題是 django, 所以我們只講 django。
restful
我參與的專案基本都是前後端分離的專案, 後端提供的介面都是用 djangorestframework
寫的。 雖然像 HATEOAS
這樣的高階屬性還沒用到, 但介面是遵循 restful
風格的, 比如像用 http method+status 表達語義, 對資源的定義等。
介面文件我們選用了 drf-yasg
來生成符合 swagger
規範的文件。 曾經我們也試過 django-rest-swagger
這個庫,不過……
another help-wanted project…
使用現成框架的好處是語言表達力極強, 最終我們實現一個“解密微信提供的手機號”介面的~~偽~~業務程式碼大概如下:
class WeChatVS(BaseVS): @with_response(empty=True) @with_request(DecryptionSiri) @action(methods=['post'], detail=True, url_path='decryption/phone') def decrypt_wechat_phone(self, request, uid): """ 解密並修改使用者的手機號 - https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html """ self.check_account_request(request, uid) openid, encrypted_data, initial_vector = self.request_data phone = WeChatManager(openid).decrypt_safely(encrypted_data, initial_vector) # TODO(ldsink): 找產品問一下外國手機號怎麼處理 hutils.check_error(not hutils.is_chinese_phone(phone), 'o(╥﹏╥)o 目前只支援國內的手機號') self.account.modify(phone=phone) return self.empty_response()
這樣的十行程式碼包含了文件、外鏈、錯誤檢查、寫庫, 讓寫業務程式碼本身也有種施法的快感。
worker
最開始伺服器上我們跑的是 django+uwsgi 的普通模式, 用 wrk
去壓一個小介面, 測試環境 4G 記憶體的機器 QPS 只有 40 左右。 後來加上了 gevent, monkey patch 一下,改到了協程模式 同樣的介面同樣的機器 QPS 上升到了大概 600。 調優一下效果會更好。 (需要更高效能的業務可能就根本不用 python 了 Orz)
celery 充當了我們的定時任務+非同步任務框架, 我們也拆分了 讀寫密集型/計算密集型 的兩類佇列以處理不同的事情。 對於業務中的即時通知部分, 我們用了 channels 庫來實現 web socket 的功能。
對於這些大型的框架,其實我們選擇餘地並不大。 比如雖然 django 開發者有說在 3.0 會考慮大幅度重寫非同步呼叫, channels 專案會逐漸棄坑…… 但畢竟 perfectionists with deadlines.
不能說人家功能不完美,我們就不幹活了嘛…
database
我們用到的資料庫也是 mysql/mongo/redis 這御三家, 所以就是每個選取對應的連線庫就是了。
值得一提的是在單元測試裡, 我們用 sqlite(in-memory)
替代了 mysql 資料庫。 sqlite 裡缺失了 mysql 的函式的問題, 也可以用 connection.create_function
的方法來規避掉。
在上線時,還有一個很好玩的東西是 database migration
, 這個基本上跟“給行駛中的火車換輪子”一樣刺激。 詳細的細節以後會專門開篇文章講一下(挖坑預警), 從結果上來說我們做到的是 利用 Django Migration 做到資料庫結構變更全相容 。
python
上面一小節中,我們基本上是走馬觀花地過完了 django 相關的三方庫。 到了真正用 python 開發的時候, 我們遇到的更多的是框架之外的奇遇。
每門語言都有自己的味道。 我很喜歡 python 的一點是: 這門語言有著非常強的表達張力。 就像上面舉的那段典型業務程式碼一樣, 在實際的開發中, python 是能完美表達開發者心中所想的。
但假如開發者自己都沒想清楚自己要寫啥, 這就有點不妙了。
所以我們有一系列的開發工具來保持清醒。
強制清醒.jpg
quality
賀師俊在《如何載入程式員新人按正確的流程開發?》下面一段我很欣賞的回答
除了開發流程上的類似要求, 我們對程式碼本身也執行了類似的嚴格要求:
- 單元測試覆蓋率必須得在 96% 以上 (unittest/pytest/coverage)
- 程式碼的逗號、換行、引號的使用都必須符合規範 (flake8)
- 程式碼強制經過 linter 檢驗,禁止多種黑魔法 (pylint)
- 程式碼的各個模組之間必須符合特定的拓撲順序 (pylint-topology)
- 程式碼風格(如字典的複製、長列表、換行與空行)強制統一 (yapf)
其中 單元測試覆蓋率必須得在 96% 以上 值得被單獨拎出來表揚一下。
業務程式碼的 96% 的覆蓋率是什麼概念呢? 這意味著程式碼裡只有那種真正的邊緣情況是沒被測到的。 (比如為了相容微信 SYSTEM ERROR -1000 寫的程式碼)
為了達到了這麼高的覆蓋率, 我們也專門強化過單元測試的表達力, 比如一段測試建立使用者介面的~~偽~~程式碼可能如下:
def test_create_account(self): """ 測試建立使用者 """ with self.assert_model_increase(Account, delta=1): response = self.client.post(self.account_url(), {'username': 'hulucc'}) self.ok(response, username='hulucc', tags__length=0) with self.assert_model_increase(Account, delta=0): response = self.client.post(self.account_url(), {'username': 'hulucc'}) self.bad_request(response, message=AccountErrors.DUPLICATE.value)
所有這些限制都在 CI 中檢查了,不通過的話是不讓 merge into master 的
developing
我們的合作方式是用 gitlab 作為程式碼託管平臺。 為了團隊的開發效率, 我們還自己寫了個小機器人來處理各種如分支合併、有效性檢查、貼標籤之類的雜活。
gitlab ci 不僅被用來做開發階段的質量保證, 最終我們的構建上線也走的是 gitlab ci (以前我們用的是 jenkins)
對於 python 的依賴管理, 我們用的是 pipenv, pip list
一下大概有了 181 個庫。 (關於 pipenv 的介紹可以參見 《Python 依賴管理的未來 - ldsink》 )
也因為我們線上用的是 docker, 所以不想裝依賴的也可以直接用 docker 的環境開發。
deploy
部署這一塊我們暫時還沒上 k8s, 目前走的是 gitlab ci 中呼叫 fabric + aws(boto3) 直接操作裸 docker 的方式。 aws 的負載均衡器提供了基礎的流量切換服務, 我們也是借用了現成的服務達到灰度釋出、無縫釋出的效果。
用 GitLab CI 部署的步驟圖
conclusion
至此,本文介紹了一遍我們在 Python 業務後端的實踐。
對於高可用、容器化、資料庫等屠龍技, 業界其實有非常多的探討, 大家也很容易找到現成的文章。
但具體到業務後端的工程化實踐, 能借鑑的大型專案並不多。 我讀過的也只有 兩年前的 reddit 程式碼 跟 sentry 這個 django 專案符合要求了。
總的來說,我們用 django 在開發中遵循的約定跟共識有這些:
- 做正確的事情。
- 比如我們在討論過後,一致覺得“線性的 Commit 歷史是最乾淨的”,從當天開始我們的 Commit 歷史就是乾淨的線性歷史了。
- 自動化一切能自動化的工作。
- 用 swagger 自動化生成文件,用 gitlab ci 自動化質量保證。
- 儘可能使用最新的特性,讓程式碼時刻保持嶄新。
- 我們每隔一陣就會把所有依賴升到最新的穩定版。
- 不過因為這個我們也踩了不少坑。
- 不少時候三方庫會引入全新的用法,動輒改動 100+ 的檔案數。
- 這個時候就到了 Vim Macro 展現魔法的時候了。
假如你也在用 Django 作為後端框架的話, 不防嘗試一下上面提到的各類工具, 絕對物超所值噢 :)