为你的Django APP 写一层 DAO

如果没有良好的分层,那么一个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?下面是我根据自己的经验,得出的结论:

  1. save(obj)
  2. delete(obj)
  3. update(obj)
  4. findOne/findAll

那么通过什么手段实现呢?得益于 Python 强大的语言特性,让我们的代码可以不必写得像 Java 那样冗长乏味。我的步骤如下:

  1. 首先封装一个基础父类,把所有通用操作,都放置到它里面,就叫做 BaseDAO 吧。
  2. 其余人,继承这个父类。
  3. 在 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 层,让自己的代码越写越舒服。

猜你喜欢

转载自juejin.im/post/5b9dc61d5188255c504716e9