如何复用Django业务逻辑同时支持Web视图与REST API?
最优方案:抽离业务逻辑到独立服务层
核心思路是将业务校验与核心逻辑从视图层完全剥离,封装到单独的服务模块中。无论是Django普通视图(用于Web页面交互)还是DRF的API视图/ViewSet(用于REST接口),都直接调用服务层的方法,既实现逻辑复用,又保证业务规则的一致性。
实现步骤与代码示例
1. 定义基础模型(models.py)
先明确业务相关的模型,以订单和库存为例:
from django.db import models class Inventory(models.Model): area = models.CharField(max_length=50, verbose_name="区域") product = models.CharField(max_length=100, verbose_name="产品") stock_quantity = models.IntegerField(verbose_name="库存数量") class Order(models.Model): order_number = models.CharField(max_length=50, unique=True, verbose_name="订单号") area = models.CharField(max_length=50, verbose_name="区域") product = models.CharField(max_length=100, verbose_name="产品") quantity = models.IntegerField(verbose_name="订购数量") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
2. 创建服务层(services.py)
将所有业务逻辑(比如库存校验、订单创建的原子操作)集中在这里:
from django.db import transaction from .models import Inventory, Order from django.core.exceptions import ValidationError class OrderService: @staticmethod def create_order(order_data): """创建订单的核心逻辑:库存校验 + 订单创建 + 库存扣减(原子操作)""" area = order_data.get('area') product = order_data.get('product') quantity = order_data.get('quantity') # 校验区域内是否存在对应库存 try: inventory = Inventory.objects.get(area=area, product=product) except Inventory.DoesNotExist: raise ValidationError(f"区域{area}无{product}库存") # 校验库存是否充足 if inventory.stock_quantity < quantity: raise ValidationError(f"区域{area}内{product}库存不足,当前库存{inventory.stock_quantity},需订购{quantity}") # 原子事务:保证订单创建和库存扣减要么同时成功,要么同时失败 with transaction.atomic(): order = Order.objects.create( order_number=order_data.get('order_number'), area=area, product=product, quantity=quantity ) inventory.stock_quantity -= quantity inventory.save() return order
3. 在Django普通视图中调用服务层(views.py)
用于Web页面的订单提交逻辑,直接复用服务层的业务规则:
from django.shortcuts import render, redirect from django.contrib import messages from .forms import OrderForm from .services import OrderService def create_order_web(request): if request.method == 'POST': form = OrderForm(request.POST) if form.is_valid(): try: OrderService.create_order(form.cleaned_data) messages.success(request, "订单创建成功") return redirect('order_list') except ValidationError as e: messages.error(request, str(e)) else: form = OrderForm() return render(request, 'create_order.html', {'form': form})
4. 在DRF API视图/ViewSet中调用服务层
方式一:APIView(灵活定制接口)
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .serializers import OrderSerializer from .services import OrderService from django.core.exceptions import ValidationError class OrderCreateAPI(APIView): def post(self, request): serializer = OrderSerializer(data=request.data) if serializer.is_valid(): try: order = OrderService.create_order(serializer.validated_data) return Response(OrderSerializer(order).data, status=status.HTTP_201_CREATED) except ValidationError as e: return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
方式二:ViewSet(快速实现CRUD接口)
如果需要完整的CRUD API,可继承ViewSet并覆写create方法复用服务层逻辑:
from rest_framework import viewsets from rest_framework.response import Response from rest_framework import status from .models import Order from .serializers import OrderSerializer from .services import OrderService from django.core.exceptions import ValidationError class OrderViewSet(viewsets.ModelViewSet): queryset = Order.objects.all() serializer_class = OrderSerializer def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) try: order = OrderService.create_order(serializer.validated_data) headers = self.get_success_headers(serializer.data) return Response(OrderSerializer(order).data, status=status.HTTP_201_CREATED, headers=headers) except ValidationError as e: return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)
方案优势
- 逻辑复用:Web视图和API视图共享同一套业务规则,避免重复编码和规则不一致
- 单一职责:服务层专注业务逻辑,视图层只处理请求响应、页面渲染或数据序列化
- 易于维护:业务规则修改只需调整服务层,无需改动多个视图
- 可测试性:服务层可单独编写单元测试,无需依赖视图层的请求上下文
内容的提问来源于stack exchange,提问作者AhmedRaza
相关产品推荐
相关产品推荐

