You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Django Rest Framework实现DDD洋葱架构的可行性与映射问询

Implementing Onion Architecture with Django REST Framework for DDD

Great question! I’ve spent months tinkering with DDD, Onion Architecture, and Django REST Framework (DRF) together, so I’m excited to share what I’ve learned. The short answer: Absolutely, you can map Onion Architecture concepts to DRF and build a fully DDD-aligned system. Let’s break this down step by step.

First: Why This Works

Onion Architecture’s core rule is dependency inversion: inner layers (domain, application) never depend on outer layers (infrastructure, interfaces). Instead, outer layers depend on abstractions defined in inner layers. DRF is flexible enough to fit this pattern—you just need to decouple your business logic from Django’s built-in MVC-style structure.

Mapping Onion Layers to DRF Project Structure

Here’s how to translate each Onion layer into a Django project layout:

Onion LayerDjango Project Structure & Responsibilities
Core Domain Layercore/domain/: Pure Python classes for entities, value objects, domain services, and abstract repositories.
No Django/DRF dependencies here—this is your business logic heart.
Application Layercore/application/: Use cases (application services) that coordinate domain logic, DTOs for cross-layer communication.
Depends only on the domain layer, not infrastructure.
Infrastructure Layerinfrastructure/: Django ORM models, concrete repository implementations, DRF serializers.
Depends on inner layers, handles framework-specific work.
Interface Layerinfrastructure/api/views/: DRF views/viewsets, URL routes.
Handles HTTP requests/responses, delegates to application services.

Example project structure:

your_project/
├── core/
│   ├── domain/
│   │   ├── entities/
│   │   ├── value_objects/
│   │   ├── services/
│   │   └── repositories/  # Abstract base classes
│   └── application/
│       ├── services/      # Use cases
│       └── dtos/
├── infrastructure/
│   ├── persistence/
│   │   ├── models/        # Django ORM models
│   │   └── repositories/  # Concrete repo implementations
│   └── api/
│       ├── serializers/
│       └── views/
├── config/                # Django settings, urls.py

Key Implementation Steps

Let’s walk through critical parts of the code to make this concrete.

1. Isolate the Domain Layer (No Django Dependencies!)

Domain entities are pure Python classes—no inheriting from models.Model. This keeps your business logic framework-agnostic.

# core/domain/entities/order.py
from dataclasses import dataclass
from core.domain.value_objects.money import Money

@dataclass(frozen=True)
class OrderItem:
    product_id: str
    quantity: int
    price: Money

@dataclass
class Order:
    id: str
    customer_id: str
    items: list[OrderItem]
    status: str = "CREATED"

    def add_item(self, item: OrderItem):
        # Enforce domain rule: No zero-quantity items
        if item.quantity <= 0:
            raise ValueError("Quantity must be positive")
        self.items.append(item)

2. Use the Repository Pattern

Define abstract repositories in the domain layer, then implement them with Django ORM in the infrastructure layer. This decouples domain logic from database specifics.

Abstract Repository (Domain Layer):

# core/domain/repositories/order_repository.py
from abc import ABC, abstractmethod
from core.domain.entities.order import Order

class OrderRepository(ABC):
    @abstractmethod
    def save(self, order: Order) -> None:
        pass

    @abstractmethod
    def get_by_id(self, order_id: str) -> Order | None:
        pass

Django ORM Implementation (Infrastructure Layer):

# infrastructure/persistence/repositories/django_order_repository.py
from core.domain.repositories.order_repository import OrderRepository
from core.domain.entities.order import Order, OrderItem
from infrastructure.persistence.models import DjangoOrder, DjangoOrderItem

class DjangoOrderRepository(OrderRepository):
    def save(self, order: Order) -> None:
        django_order, _ = DjangoOrder.objects.update_or_create(
            id=order.id,
            defaults={"customer_id": order.customer_id, "status": order.status}
        )
        # Sync order items
        existing_product_ids = [item.product_id for item in django_order.items.all()]
        new_product_ids = [item.product_id for item in order.items]
        
        # Update or create new items
        for item in order.items:
            DjangoOrderItem.objects.update_or_create(
                order=django_order,
                product_id=item.product_id,
                defaults={
                    "quantity": item.quantity,
                    "price_amount": item.price.amount,
                    "price_currency": item.price.currency
                }
            )
        # Remove items no longer in the domain order
        DjangoOrderItem.objects.filter(
            order=django_order,
            product_id__not_in=new_product_ids
        ).delete()

    def get_by_id(self, order_id: str) -> Order | None:
        try:
            django_order = DjangoOrder.objects.prefetch_related("items").get(id=order_id)
            items = [
                OrderItem(
                    product_id=item.product_id,
                    quantity=item.quantity,
                    price=Money(item.price_amount, item.price_currency)
                )
                for item in django_order.items.all()
            ]
            return Order(
                id=django_order.id,
                customer_id=django_order.customer_id,
                status=django_order.status,
                items=items
            )
        except DjangoOrder.DoesNotExist:
            return None

3. Build Application Use Cases

Application services (use cases) coordinate domain logic and repositories. They’re the "glue" between your domain and infrastructure, with no framework dependencies.

# core/application/services/create_order_use_case.py
from core.domain.entities.order import Order, OrderItem
from core.domain.repositories.order_repository import OrderRepository
from core.application.dtos.create_order_dto import CreateOrderDTO
from core.domain.value_objects.money import Money

class CreateOrderUseCase:
    def __init__(self, order_repository: OrderRepository):
        self.order_repository = order_repository

    def execute(self, dto: CreateOrderDTO) -> Order:
        # Convert DTO to domain entities
        items = [
            OrderItem(
                product_id=item.product_id,
                quantity=item.quantity,
                price=Money(item.price_amount, item.price_currency)
            )
            for item in dto.items
        ]
        order = Order(
            id=dto.order_id,
            customer_id=dto.customer_id,
            items=items
        )
        
        # Enforce additional domain rules
        total = sum(item.price.amount for item in order.items)
        if total <= 0:
            raise ValueError("Order total must be positive")
        
        # Persist the order
        self.order_repository.save(order)
        return order

4. Connect to DRF (Interface Layer)

DRF views call application use cases, and serializers handle converting between API data and DTOs/domain entities.

Serializers:

# infrastructure/api/serializers/order_serializers.py
from rest_framework import serializers
from core.application.dtos.create_order_dto import CreateOrderDTO
from core.domain.entities.order import Order

class CreateOrderItemSerializer(serializers.Serializer):
    product_id = serializers.CharField()
    quantity = serializers.IntegerField(min_value=1)
    price_amount = serializers.DecimalField(max_digits=10, decimal_places=2)
    price_currency = serializers.CharField(max_length=3)

class CreateOrderSerializer(serializers.Serializer):
    order_id = serializers.CharField()
    customer_id = serializers.CharField()
    items = CreateOrderItemSerializer(many=True)

    def create(self, validated_data):
        return CreateOrderDTO(**validated_data)

class OrderResponseSerializer(serializers.Serializer):
    id = serializers.CharField()
    customer_id = serializers.CharField()
    status = serializers.CharField()
    items = CreateOrderItemSerializer(many=True)

ViewSet:

# infrastructure/api/views/order_views.py
from rest_framework import viewsets, status
from rest_framework.response import Response
from infrastructure.api.serializers.order_serializers import CreateOrderSerializer, OrderResponseSerializer
from core.application.services.create_order_use_case import CreateOrderUseCase
from infrastructure.persistence.repositories.django_order_repository import DjangoOrderRepository

class OrderViewSet(viewsets.ViewSet):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.order_repo = DjangoOrderRepository()
        self.create_order_use_case = CreateOrderUseCase(self.order_repo)

    def create(self, request):
        serializer = CreateOrderSerializer(data=request.data)
        if serializer.is_valid():
            try:
                order = self.create_order_use_case.execute(serializer.validated_data)
                response_serializer = OrderResponseSerializer(order)
                return Response(response_serializer.data, status=status.HTTP_201_CREATED)
            except ValueError as e:
                return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

How This Compares to Frameworks Like Apache Isis

Apache Isis is a purpose-built DDD framework—it comes with out-of-the-box support for entities, repositories, and event sourcing. DRF, by contrast, is a general-purpose API framework, so you’ll need to manually implement DDD patterns like repositories and use cases. The tradeoff is flexibility: you have full control over every layer, and you can adapt the pattern to fit your exact needs, rather than being constrained by a framework’s opinions.

Final Takeaways

  • Stick to the dependency rule: Inner layers (domain, application) should never import from outer layers (infrastructure, DRF).
  • Test your domain logic in isolation—you don’t need to spin up Django to test core business rules.
  • Use dependency injection (even simple constructor injection, as shown above) to keep your code modular and testable.

This approach takes a bit more upfront work, but it pays off in maintainability, testability, and the ability to evolve your system without being tied to Django’s implementation details.

内容的提问来源于stack exchange,提问作者Thinker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:51:30