若是沒有良好的分層,那麼一個Web項目最終會走向崩潰。java
Django項目,通常是按照 APP 切分的,而且每個 APP 有類似的結構,你們都是『各自管好本身分內的事情』,很有點像微服務的味道。可是許多人寫Django 的代碼,沒有必定的章法,一千我的一千種風格。甚至於,在Controller層出現直接裸調用UserModel.objects.filter
的狀況也很多見。然而,咱們發現,針對數據庫的操做,不少都是通用的,這時候,單獨抽取出一層,就顯得頗有必要了。python
如何組織、設計咱們的這個層呢?咱們沒有必要本身絞盡腦汁閉門造車,能夠參考成熟項目的作法。Java Spring 是我參考的對象,通常的Spring 項目,有着很明確分層結構,雖然初期須要寫較多的代碼,可是給後期的代碼維護,着實帶來了不少便利。數據庫
通常會分爲以下層級:bash
Controller
Service
Repository( DAO )
(Mapper,可選,若是使用了Mybatis的話)
Model
複製代碼
結合Django的特性,咱們發現Django的Manager層(即:XXModel.objects
),實際上是對應着 DAO 層的,只不過你們的叫法不一樣。app
咱們不妨將抽取的單獨層,叫作DAO 好了,後面咱們也會看到,它其實就是對 Manager 層的API進行組合,對上提供一些通用的操做。微服務
在正式寫以前,咱們能夠先根據實際經驗,思考:應該提供哪些通用的API?下面是我根據本身的經驗,得出的結論:spa
那麼經過什麼手段實現呢?得益於 Python 強大的語言特性,讓咱們的代碼能夠沒必要寫得像 Java 那樣冗長乏味。個人步驟以下:設計
BaseDAO
吧。下面是代碼片斷:code
# 基於 Python 3.5 的代碼, 若是想要放到 Python 2 中的同窗, 能夠去掉 Type Hint
from .BaseModel import BaseModel # 通常的項目, 都會封裝一個基類Model
class BaseDAO:
# 子類必須覆蓋這個
MODEL_CLASS = BaseModel
SAVE_BATCH_SIZE = 1000
def save(self, obj):
"""insert one :param obj: :return: """
if not obj:
return False
obj.save()
return True
def save_batch(self, objs, *, batch_size=SAVE_BATCH_SIZE):
"""insert batch :type objs: list[BaseModel] :param objs: :return: """
if not objs:
return False
self.MODEL_CLASS.objects.bulk_create(objs, batch_size=batch_size)
return True
def delete(self, obj):
if not obj:
return False
obj.delete()
return True
def delete_batch(self, objs):
if not objs:
return False
for obj in objs:
self.delete(obj)
return True
def delete_batch_by_query(self, filter_kw: dict, exclude_kw: dict):
"""批量刪除 """
self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw).delete()
return True
def delete_by_fake(self, obj):
"""假刪除/僞刪除 """
if obj is None:
return False
obj.is_deleted = True
obj.save()
return True
def update(self, obj):
if not obj:
return False
obj.save()
return True
def update_batch(self, objs):
if not objs:
return False
for obj in objs:
self.update(obj)
return True
def update_batch_by_query(self, query_kwargs: dict, exclude_kw: dict, newattrs_kwargs: dict):
self.MODEL_CLASS.objects.filter(**query_kwargs).exclude(**exclude_kw).update(**newattrs_kwargs)
def find_one(self, filter_kw: dict, exclude_kw: dict, order_bys: list):
""" :param query_kwargs: :rtype: BaseModel | None :return: """
qs = self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw)
if order_bys:
qs = qs.order_by(*order_bys)
return qs.first()
def find_queryset(self, filter_kw: dict, exclude_kw: dict, order_bys: list):
""" :param filter_kw: :return: """
return self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw)
def find_all_model_objs(self, filter_kw: dict, exclude_kw: dict, order_bys: list) -> list:
return self.find_queryset(filter_kw, exclude_kw, order_bys).all()
def is_exists(self, filter_kw:dict, exclude_kw:dict) -> bool:
return self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw).exists()
def get_count(self, filter_kw:dict, exclude_kw:dict) -> int:
return self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw).count()
複製代碼
好比在某個 Django APP 中使用:對象
某個Django APP, 這裏是 goods
goods/
views.py
tests.py
dao/ ( 也能夠單獨放到一個 dao.py 中, 看本身喜愛. 我比較喜歡弄一個目錄, 而且每個py 文件一個class, 這裏保持和java同樣的風格)
GoodsDao.py
models.py
GoodsDao.py內容
from ..models import Goods
from common_base import BaseDAO
class GoodsDao(BaseDAO):
MODEL_CLASS = Goods
複製代碼
上層使用:基本能夠很自由的使用。都是一些通用的CURD 操做,變化不大,而且不再用寫冗長的XXModel.objects.filter
了
經過上面總結,咱們能夠看到,確實帶來了一個良好的封裝,雖然初期須要多寫一些代碼,可是後期代碼維護比較舒服。另一個問題是:是否是就該摒棄Goods.objects.filter
這種寫法呢?
我以爲不是的,Goods.objects.filter
仍然能夠自由使用,只不過在 DAO 沒法應對的狀況下(你又懶得再封裝了,由於是低頻操做),就該輪到它出場了。它們二者應該是互爲補充,互相融合,各自都有本身的使用場景。原始的寫法適用於『比較低頻、臨時的CURD操做』,DAO則適用於『比較高頻、通用的CURD操做』。
另外,Python 世界流行的 ORM ,不僅有 Django ORM。好比SQLAlchemy等,你也能夠封裝出一樣相似的 DAO 層,讓本身的代碼越寫越舒服。