存储库模式赋能 Django:让你的代码不那么业余,更具生命力

前言

哈喽,大家好,我是海鸽。

相信很多 python 的小伙伴已经很熟悉Django的开发模式了,但是其中有部分小伙伴,可能会相当苦恼自己的Django正在变成一个由视图和模型组成的巨大乱麻,且它们还在不停地直接交互?

那么,要如何解决这类问题呢?

有一句话是这么说的:没有什么是一包辣条不能解决的!如果有。那就两包。

计算机科学领域的任何问题都可以通过增加一个中间层来解决。
Any problem in computer science can be solved by an additional level of indirection。

楔子

刚好,最近一位小伙伴给海鸽发来一道面试题,其大意如下:

【题目:Django 批量订单处理与商品搜索系统】

  • 背景场景: 您需要基于 Django 搭建一个小型电商模块,要求同时支持"批量订单处理 "与"商品搜索 "两大功能,并能在高并发场景下稳定运行 。该系统可能被多个外部 API 或前端调用,需确保数据的正确性与一致性

【核心需求】:

  • 批量订单处理
  • 一次提交中,可下单多个不同商品;要求逐一扣减库存,并记录订单明细。
  • 任一商品库存不足时,需返回对应的失败信息,不影响其他商品的下单。
  • 要防止超卖、重复扣减等问题,需设计适当的锁或并发控制机制(可使用悲观锁、乐观锁或分布式锁等)。
  • 商品搜索功能
  • 提供简单的商品搜索接口,可按商品名称、关键词等进行查询。
  • 大部分查询场景下会是高频读操作,需要使用缓存(如 Redis)设计好批量数据库搜索。
  • 缓存需与数据库保持一致,当商品信息更新或售罄时,需更新或失效缓存。 数据库与缓存。
  • 请自行设计核心数据表与缓存结构(如存放库存、价格、关键词索引等)。
  • 说明当库存或商品信息改变时,如何更新数据库与缓存?若缓存连接失败或不可用时,如何降级或回退?
  • 异常处理与可扩展性
  • 业务异常:如库存不足、订单格式非法、搜索关键字为空等,要有清晰的捕获与返回。
  • 系统异常:如数据库或缓存宕机,需要记录日志并提供合理的恢复或补偿策略。
  • 要求分层设计(例如:models、views、services/业务逻辑层、cache 管理等),保证后续扩容或二次开发时可顺利集成分库分表、消息队列异步处理、限流等高级功能。

好家伙,现在已经这样面试了吗?其中一项重要要求便是:分层设计

感兴趣的可以试试上面的面试题能得几分,文末有上述需求粗略的评分标准。

这时肯定就有人跳出来了,"使用 Django 的 MVC 模式不就行了"。

是的,没错。

不过,海鸽是见过那种一个视图函数里面塞满业务逻辑的,说多了都是泪啊。

当然,一些有经验的程序员、架构会采用 DDD 的设计,或者至少分出ControlService等层,使逻辑清晰,或便于维护。

对 python 架构感兴趣的可以看看这本书:《Python的架构模式》

今天就引入的其中一个使代码干净的专业技巧: 存储库模式(Repository Pattern)

什么是存储库模式(Repository Pattern)?

存储库设计模式是一种抽象数据访问的结构模式,提供了一种集中的方式来管理数据操作。

通过将数据层与业务逻辑分离,它增强了代码的可维护性、可测试性和灵活性,从而更轻松地在应用程序中使用各种数据源。

简单来说:

**存储库充当数据库(模型)和视图/服务之间的中间层。**它处理所有数据库操作,因此您的视图保持整洁并专注于逻辑。

存储库模式和 ORM 的区别

在 Django 中,存储库模式和直接使用 ORM 存在显著差异,主要体现在代码结构、职责划分和灵活性上,具体区别如下:

1. 核心概念不同

  • 直接使用 ORM :Django 的 ORM(如 Model.objects 方法)允许直接在业务逻辑中编写数据查询代码(如 User.objects.filter(age__gt=18)),数据访问逻辑与业务逻辑紧密结合
  • 存储库模式:引入一个中间层(存储库类),统一负责数据的查询、保存、删除等操作。业务逻辑不直接调用 ORM,而是通过存储库类间接操作数据,将数据访问逻辑与业务逻辑分离。

2. 代码组织方式不同

  • 直接使用 ORM :查询代码通常分散在视图、服务等业务逻辑中。例如:

    python 复制代码
    # 视图中直接使用 ORM
    def get_adults(request):
        adults = User.objects.filter(age__gt=18)
        return render(request, 'adults.html', {'adults': adults})
  • 存储库模式 :将所有数据操作封装在专门的存储库类中,业务逻辑通过调用存储库方法获取数据。例如:

    python 复制代码
    # 存储库类封装 ORM 操作
    class UserRepository:
        @staticmethod
        def get_adults():
            return User.objects.filter(age__gt=18)
    
    # 视图中调用存储库
    def get_adults(request):
        adults = UserRepository.get_adults()
        return render(request, 'adults.html', {'adults': adults})

3. 灵活性与可测试性不同

  • 直接使用 ORM
    • 优点:简单直接,适合小型项目,减少代码量。
    • 缺点:业务逻辑与数据访问耦合度高,若更换数据库或 ORM,需修改大量业务代码;单元测试时难以模拟数据库操作。
  • 存储库模式
    • 优点:解耦业务逻辑与数据访问,更换数据源(如从 MySQL 换为 MongoDB)时,只需修改存储库实现,不影响业务代码;便于编写单元测试(可通过模拟存储库返回固定数据)。
    • 缺点:增加代码层级,小型项目可能显得冗余。

🏗️ 案例

我们还是以电商中的商品服务(product)和 订单服务(orders)为例。

首先,看一下项目结构 🔍:

shell 复制代码
├── django_repo
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── orders
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── repositories
│   │   └── order_repository.py
│   ├── serializers.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── product
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── repositories
│   │   └── product_repository.py
│   ├── tests.py
│   └── views.py
└── utils
    ├── base_repository.py
    └── response.py

公共的基础 repository (utils/base_repository.py):

python 复制代码
from typing import TypeVar, Generic, Optional, List
from django.db import models

ModelType = TypeVar("ModelType", bound=models.Model)

class BaseRepository(Generic[ModelType]):
    def __init__(self, model: type[ModelType]):
        self.model = model

    def get_all(self) -> List[ModelType]:
        return list(self.model.objects.all())

    def get_by_id(self, pk: int) -> Optional[ModelType]:
        return self.model.objects.filter(id=pk).first()

    def create(self, **kwargs) -> ModelType:
        return self.model.objects.create(**kwargs)

    def update(self, instance: ModelType, **kwargs) -> ModelType:
        for attr, value in kwargs.items():
            setattr(instance, attr, value)
        instance.save()
        return instance

    def delete(self, instance: ModelType) -> None:
        instance.delete()

简单统一响应(utils/response.py

python 复制代码
from typing import TypeVar, Optional, Any, Union
from rest_framework.response import Response
from rest_framework import status

T = TypeVar('T')

class ApiResponse:
    @staticmethod
    def success(data: Optional[T] = None, msg: str = "success") -> Response:
        return Response({
            "code": 200,
            "msg": msg,
            "data": data
        })

    @staticmethod
    def created(data: Optional[T] = None, msg: str = "created successfully") -> Response:
        return Response({
            "code": 201,
            "msg": msg,
            "data": data
        }, status=status.HTTP_201_CREATED)

    @staticmethod
    def error(msg: str = "error", code: int = 400, data: Optional[Any] = None) -> Response:
        return Response({
            "code": code,
            "msg": msg,
            "data": data
        }, status=status.HTTP_400_BAD_REQUEST)

商品服务

商品数据库模型(product/models.py)

python 复制代码
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    in_stock = models.BooleanField(default=True)

    def __str__(self):
        return self.name

产品 repository(product/repositories/product_repository.py)

python 复制代码
from utils.base_repository import BaseRepository
from product.models import Product

class ProductRepository(BaseRepository):
    def __init__(self):
        super().__init__(Product)

    def get_available_products(self):
        return self.model.objects.filter(in_stock=True)

订单服务

订单数据库模型(order/models.py)

python 复制代码
from django.db import models
from product.models import Product

class Order(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField()
    ordered_at = models.DateTimeField(auto_now_add=True)

订单repository(order/repositories/order_repository.py)

python 复制代码
from typing import List
from orders.models import Order
from product.models import Product
from utils.base_repository import BaseRepository
from product.repositories.product_repository import ProductRepository

class OrderRepository(BaseRepository[Order]):
    def __init__(self):
        super().__init__(Order)
        self.product_repo = ProductRepository()

    def create_order(self, product_id: int, quantity: int) -> Order:
        product = self.product_repo.get_by_id(product_id)
        if not product:
            raise ValueError("Product not found")
        if not product.in_stock:
            raise ValueError("Product is out of stock")

        return self.model.objects.create(product=product, quantity=quantity)

    def get_all_orders(self) -> List[Order]:
        return list(self.model.objects.select_related('product').all())

订单创建和查询接口(order/views.py)

python 复制代码
from rest_framework.views import APIView
from .repositories.order_repository import OrderRepository
from .serializers import OrderSerializer, OrderCreateSerializer
from utils.response import ApiResponse


class OrderListCreate(APIView):
    order_repo = OrderRepository()

    def get(self, request):
        orders = self.order_repo.get_all_orders()
        serializer = OrderSerializer(orders, many=True)
        return ApiResponse.success(data=serializer.data)

    def post(self, request):
        serializer = OrderCreateSerializer(data=request.data)
        if not serializer.is_valid():
            return ApiResponse.error(msg="Invalid data", data=serializer.errors)

        try:
            order = self.order_repo.create_order(
                product_id=serializer.validated_data['product_id'],
                quantity=serializer.validated_data['quantity']
            )
            return ApiResponse.created(data=OrderSerializer(order).data)
        except ValueError as e:
            return ApiResponse.error(msg=str(e))

订单接口的序列化器

python 复制代码
from rest_framework import serializers
from .models import Order
from product.models import Product

class OrderCreateSerializer(serializers.Serializer):
    product_id = serializers.IntegerField(required=True)
    quantity = serializers.IntegerField(required=True, min_value=1)

    def validate_product_id(self, value):
        try:
            product = Product.objects.get(id=value)
            if not product.in_stock:
                raise serializers.ValidationError("Product is out of stock")
            return value
        except Product.DoesNotExist:
            raise serializers.ValidationError("Product not found")

class OrderSerializer(serializers.ModelSerializer):
    product_name = serializers.CharField(source='product.name', read_only=True)

    class Meta:
        model = Order
        fields = ['id', 'product', 'product_name', 'quantity', 'ordered_at']

OK,测试一下:

存储库模式 虽然很简单,不过确实在一定程度上能帮助我们写出干净的代码,对吧。

面试题评分标准

好了,自测看一下你能得什么等级吧!

shell 复制代码
提交与说明要求:
1、使用 Django 框架,编写主要的核心代码,包括数据模型、视图或接口、缓存/锁机制等。
2、请在 Git 中进行多次提交并标注每次改动与原因
3、若是本地迭代修改,需在文档里记录每次改动的内容与原因。
2或3必须要有一份,否则视为不合格

以下是我们的评分评级标准,可参考:
等级
并发与架构
缓存与数据一致性
安全与异常处理
可扩展性与维护

S 级(卓越)
- 采用成熟的异步高并发方案(如 ASGI 框架、协程/线程池、微服务等),能在极高流量下保持低延迟
- 大规模分层或微服务拆分清晰,可灵活扩展出不同子系统,保证在极端高并发场景下也无超卖问题,系统表现稳定。
- 拥有多级或分布式缓存,高度完善的失效/更新策略。
- 优雅的容灾/降级方案,出现缓存故障或突发并发激增时可快速回退,依旧维护数据一致性。 
-- 业务异常和系统异常分层捕获,并有完善的补偿或幂等机制;    
-- 接口安全全面(加解密、签名、审计、敏感操作多级鉴权),日志精细到每个关键节点,足以支持安全审计和快速排障。 
-- 代码结构高内聚低耦合,文档与注释详尽,具备完整的 Git 提交历史和高质量设计说明;
  -- 可无缝对接高级功能(如分布式事务、熔断限流、机器学习推荐等),后续升级改造成本非常低。

A 级(优秀)
 -- 较完善的"异步化"设计思想,如读写分离、Redis 队列或定时任务,能有效处理中高并发; 
 -- 订单与搜索逻辑边界清晰,有必要的锁或事务控管,保证不发生超卖;  -- 模块划分合理,后续如需微服务拆分,改造成本可控。 
-- 具备较成熟的缓存读写策略,正常场景下可确保库存与商品数据一致; 
 -- 偶尔在极端并发或缓存故障场景下,可能有短时滞后,但有一定的补救策略来保持主要功能可用。
-- 对主要业务异常与系统异常进行捕获与重试或补偿,日志可支持排错;  
-- 接口具备一定的安全措施(身份鉴权、必要操作的审计),满足常规生产环境安全需求。  
-- 代码结构整体清晰,设计文档与 Git 提交记录完整,能较好展现演进过程;  
-- 具备对第三方组件(如消息队列、限流框架等)或其他业务模块的扩展接口。  

B 级(良好)
- 同步为主,关键流程并发管控防超卖。
- 分层合理但耦合中等可维护,中等流量稳定。
- 基础缓存方案到位,可显著减少数据库查询压力。
- 简单失效策略,高频更新可能短暂滞后但无严重不一致。
- 常见异常捕获与日志,少数错误可能需人工补偿。
- 常规鉴权,满足大部分场景安全需求。
- 基本 Git 记录 / 设计文档(简略)。
- 升级异步 / 微服务需较多重构测试。

C 级(合格)
- 同步模式为主,低至中等并发,关键点用锁防超卖
- 分层弱,耦合高,缺乏高级并发手段 
- 简单缓存思路,更新或失效策略较为原始,高并发可能缓存一致性差
- 能满足基础查询需求,无大面积数据不一致
- 仅捕获常规异常,日志缺乏深度,复杂问题需手动分析
- 简单鉴权,依赖外部网关 
- Git 提交和设计说明可用,代码可读性一般
- 引入新组件需高改造成本 

D 级(不合格)
缺乏并发设计,导致IO消耗高,同时没有防止超卖的机制。
- 无缓存或严重数据脱节,或者缓存命中率极低
- 异常处理缺失,日志无法支撑排障 / 审计
- 安全措施形同虚设
- 代码混乱无分层,维护 / 升级极困难 

小结

  • 直接使用 ORM 适合快速开发、业务简单的项目,注重简洁性。
  • 存储库模式适合大型项目或需要频繁变更数据源的场景,注重代码的可维护性、可扩展性和可测试性。

写在最后

今天的分享就到这里。如果觉得不错,点赞在看关注安排起来吧。

相关推荐
一百天成为python专家12 分钟前
python库之jieba 库
开发语言·人工智能·python·深度学习·机器学习·pycharm·python3.11
27669582921 小时前
tiktok 弹幕 逆向分析
java·python·tiktok·tiktok弹幕·tiktok弹幕逆向分析·a-bogus·x-gnarly
cylat1 小时前
Day59 经典时序预测模型3
人工智能·python·深度学习·神经网络
嘉恩督1 小时前
视频人脸处理——人脸面部动作提取
python·音视频
WJ.Polar1 小时前
Python数据容器-集合set
开发语言·python
smppbzyc1 小时前
2025年亚太杯(中文赛项)数学建模B题【疾病的预测与大数据分析】原创论文讲解(含完整python代码)
python·数学建模·数据分析·数学建模竞赛·亚太杯数学建模·亚太杯
xiaocainiao8812 小时前
Python 实战:构建可扩展的命令行插件引擎
开发语言·python
运器1233 小时前
【一起来学AI大模型】PyTorch DataLoader 实战指南
大数据·人工智能·pytorch·python·深度学习·ai·ai编程
音元系统3 小时前
Copilot 在 VS Code 中的免费替代方案
python·github·copilot
超龄超能程序猿3 小时前
(5)机器学习小白入门 YOLOv:数据需求与图像不足应对策略
人工智能·python·机器学习·numpy·pandas·scipy