如果没有良好的分层, 那么一个 web 项目最终会走向崩溃.
缘由
Django 项目, 一般是按照 APP 切分的, 并且每一个 APP 有相似的结构, 大家都是『各自管好自己份内的事情』, 颇有点像微服务的味道. 但是许多人写 Django 的代码, 没有一定的章法, 一千个人一千种风格. 甚至于, 在 Controller 层出现直接裸调用 UserModel.objects.filter 的情况也不少见. 然而, 我们发现, 针对数据库的操作, 很多都是通用的, 这时候, 单独抽取出一层, 就显得很有必要了.
参考的对象
如何组织, 设计我们的这个层呢? 我们没有必要自己绞尽脑汁闭门造车, 可以参考成熟项目的做法. Java Spring 是我参考的对象, 一般的 Spring 项目, 有着很明确分层结构, 虽然初期需要写较多的代码, 但是给后期的代码维护, 着实带来了很多便利.
一般会分为如下层级:
- Controller
- Service
- Repository(DAO)
- (Mapper, 可选, 如果使用了 Mybatis 的话)
- Model
复制代码
结合 Django 的特性, 我们发现 Django 的 Manager 层 (即: XXModel.objects), 其实是对应着 DAO 层的, 只不过大家的叫法不同.
我们不妨将抽取的单独层, 叫做 DAO 好了, 后面我们也会看到, 它其实就是对 Manager 层的 API 进行组合, 对上提供一些通用的操作.
如何写
在正式写之前, 我们可以先根据实际经验, 思考: 应该提供哪些通用的 API? 下面是我根据自己的经验, 得出的结论:
- save(obj)
- delete(obj)
- update(obj)
- findOne/findAll
那么通过什么手段实现呢? 得益于 Python 强大的语言特性, 让我们的代码可以不必写得像 Java 那样冗长乏味. 我的步骤如下:
首先封装一个基础父类, 把所有通用操作, 都放置到它里面, 就叫做 BaseDAO 吧.
其余人, 继承这个父类.
在 Controller 或者 Service 层使用
下面是代码片段:
- # 基于 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 层, 让自己的代码越写越舒服.
来源: https://juejin.im/post/5b9dc61d5188255c504716e9