django-clean-drf
Django Clean DRF Architect
Overview
This skill provides expert guidance for creating Django REST Framework applications following Clean Architecture principles. It enforces a strict layered architecture that separates concerns, maximizes testability, and produces maintainable production-quality code.
Architecture Layers:
HTTP Layer (Views/Serializers)
↓
Application Layer (Use Cases + DTOs)
↓
Domain Layer (Services + Models)
↓
Infrastructure Layer (ORM, External APIs, Cache)
Key Capabilities:
- Layered architecture with strict dependency rules (dependencies flow inward only)
- Use Case pattern with dataclass Input/Output DTOs
- Service layer for domain logic (static queries, instance mutations)
- Thin views that delegate to use cases
- Separate Read vs Create/Update serializers
- Explicit error handling without exceptions for business logic
- Modern Python 3.12+ and Django 5+ features
- Full type hints and testability by design
When to Use
Invoke this skill when you encounter these triggers:
New Application Setup:
- "Create a new Django API for..."
- "Start a new DRF project with clean architecture"
- "Set up a new app with layered architecture"
- "Initialize a domain-driven Django application"
- "Scaffold a Django app following SOLID"
Use Case Implementation:
- "Create a use case for..."
- "Implement the business logic for..."
- "Add a new operation/action for..."
- "Handle this transaction atomically"
Service Layer:
- "Create a service for..."
- "Where should this business logic go?"
- "Implement domain validation for..."
- "Query data following clean architecture"
Architecture Questions:
- "How do I structure this Django app?"
- "What layer should handle..."
- "How do I avoid fat views/models?"
- "How do I make this testable?"
- "Apply SOLID principles to Django"
API Design:
- "Create API endpoints for..."
- "Design the serializers for..."
- "Implement CRUD for this entity"
Instructions
Follow this workflow when handling Django Clean DRF requests:
1. Analyze Requirements and Establish Context
Understand the domain:
- What entities/models are involved?
- What operations/actions are needed?
- What business rules must be enforced?
- Are there external dependencies (APIs, queues)?
Check existing project structure:
- Review existing apps and their organization
- Identify patterns already in use
- Check Django/Python versions for feature availability
Determine scope:
- Single use case or full CRUD?
- New app or extending existing?
- API-only or includes admin?
2. Load Relevant Reference Documentation
Based on the task, reference the appropriate bundled documentation:
Architecture:
- New app setup →
references/project-structure.md - Use case implementation →
references/use-cases-pattern.md - Service layer →
references/services-pattern.md - Models and domain logic →
references/models-domain.md - Views and serializers →
references/views-serializers.md - Testing →
references/testing-clean-arch.md - Complete examples →
references/examples.md
API Development:
- Query optimization / N+1 →
references/query-optimization.md - Pagination, filtering, search →
references/api-patterns.md - Authentication / JWT / Permissions →
references/authentication.md - Production deployment →
references/production-api.md - Django Admin for APIs →
references/django-admin.md
Code Quality:
- Coding standards / Ruff / Security →
references/coding-standards.md
3. Implement Following Clean Architecture Principles
CRITICAL: Layer Dependency Rules
Views → Use Cases → Services → Models
↓ ↓ ↓ ↓
Serializers DTOs (internal) ORM
- Dependencies flow INWARD only (outer layers depend on inner)
- Never import views in use cases, or use cases in services
Use Case Pattern:
from dataclasses import dataclass
from uuid import UUID
from django.db import transaction
@dataclass(frozen=True, slots=True)
class CreateOrderInput:
customer_id: UUID
items: list['OrderItemInput']
notes: str | None = None
@dataclass(frozen=True, slots=True)
class CreateOrderOutput:
success: bool
order_id: UUID | None = None
error: str | None = None
class CreateOrderUseCase:
def __init__(
self,
order_service: OrderService,
inventory_service: InventoryService,
):
self._order_service = order_service
self._inventory_service = inventory_service
@transaction.atomic
def execute(self, input_dto: CreateOrderInput) -> CreateOrderOutput:
# Validate
is_valid, error = self._inventory_service.validate_availability(input_dto.items)
if not is_valid:
return CreateOrderOutput(success=False, error=error)
# Execute
order = self._order_service.create(
customer_id=input_dto.customer_id,
items=input_dto.items,
)
return CreateOrderOutput(success=True, order_id=order.id)
Service Pattern:
class OrderService:
# STATIC methods for QUERIES (no state changes)
@staticmethod
def get_by_id(order_id: UUID) -> Order | None:
return Order.objects.select_related('customer').filter(id=order_id).first()
# INSTANCE methods for MUTATIONS (state changes)
def create(self, customer_id: UUID, items: list[OrderItemInput]) -> Order:
order = Order.objects.create(customer_id=customer_id)
for item in items:
OrderItem.objects.create(order=order, **item.__dict__)
return order
# VALIDATION returns tuple[bool, str]
def validate_can_cancel(self, order: Order) -> tuple[bool, str]:
if order.status == Order.Status.SHIPPED:
return False, "Cannot cancel shipped orders"
return True, ""
Thin View Pattern:
class CreateOrderView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request: Request) -> Response:
serializer = CreateOrderSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
use_case = CreateOrderUseCase(
order_service=OrderService(),
inventory_service=InventoryService(),
)
input_dto = CreateOrderInput(**serializer.validated_data)
output = use_case.execute(input_dto)
if not output.success:
return Response({'error': output.error}, status=400)
return Response({'order_id': str(output.order_id)}, status=201)
Serializer Separation:
# WRITE serializer (input validation)
class CreateOrderSerializer(serializers.Serializer):
items = OrderItemInputSerializer(many=True, min_length=1)
notes = serializers.CharField(max_length=500, required=False)
# READ serializer (output display)
class OrderReadSerializer(serializers.ModelSerializer):
customer_name = serializers.CharField(source='customer.name', read_only=True)
class Meta:
model = Order
fields = ['id', 'customer_name', 'status', 'created_at']
read_only_fields = fields
Model with Domain Logic:
class Order(models.Model):
class Status(models.TextChoices):
PENDING = 'pending', 'Pending'
CONFIRMED = 'confirmed', 'Confirmed'
SHIPPED = 'shipped', 'Shipped'
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
customer = models.ForeignKey('Customer', on_delete=models.PROTECT, related_name='orders')
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
indexes = [models.Index(fields=['customer', 'status'])]
@property
def is_cancellable(self) -> bool:
return self.status in (self.Status.PENDING, self.Status.CONFIRMED)
4. Validate and Finalize
Architecture Checklist:
- Views are thin (< 20 lines of logic)
- Use cases handle single operations
- Services contain reusable domain logic
- Dependencies flow inward only
- No circular imports between layers
Code Quality Checklist:
- Type hints on all public interfaces
- Dataclass DTOs are frozen with slots
- No exceptions for business logic errors → return Output
- Validation returns
tuple[bool, str] -
@transaction.atomicwraps state changes
Performance Checklist:
-
select_relatedfor ForeignKey access -
prefetch_relatedfor reverse relations - Indexes on frequently queried fields
- No N+1 queries in serializers
Directory Structure
apps/<app_name>/
├── __init__.py
├── models.py # Domain entities with @property logic
├── views.py # Thin HTTP handlers
├── serializers.py # Read and Write serializers
├── urls.py # Route definitions
├── admin.py # Django Admin
├── services/
│ ├── __init__.py # Export services
│ └── <entity>_service.py
├── use_cases/
│ ├── __init__.py # Export use cases
│ └── <action>_<entity>.py # One file per use case
├── tests/
│ ├── __init__.py
│ ├── test_models.py
│ ├── test_services.py
│ ├── test_use_cases.py
│ ├── test_views.py
│ └── factories.py # Factory Boy factories
└── migrations/
Bundled Resources
references/ - Comprehensive Clean Architecture and API documentation
Architecture Patterns
-
references/project-structure.mdDirectory layout, file naming, module organization, dependency rules -
references/use-cases-pattern.mdInput/Output DTOs, constructor injection, transaction handling, error handling -
references/services-pattern.mdStatic vs instance methods, validation patterns, cross-service communication -
references/models-domain.mdDomain logic in models, TextChoices, UUID keys, indexes -
references/views-serializers.mdThin views, permissions, Read vs Write serializers -
references/testing-clean-arch.mdUnit testing use cases, mocking services, integration tests -
references/examples.mdComplete CRUD example, complex workflows, testing examples
API Best Practices
-
references/query-optimization.mdN+1 queries, select_related/prefetch_related, indexes, bulk operations, aggregations -
references/api-patterns.mdPagination, filtering, searching, ordering, API versioning, error handling, throttling -
references/authentication.mdJWT authentication, permissions, API keys, role-based access, security headers -
references/production-api.mdSettings structure, database config, caching, logging, Sentry, health checks, Docker -
references/django-admin.mdModelAdmin configuration, inlines, custom actions, query optimization, permissions, security
Code Quality
references/coding-standards.mdRuff configuration, import ordering, security practices, YAGNI, logging, documentation
Core Principles
No Exceptions for Business Logic
# BAD
def create_order(self, data):
if not self.can_create():
raise ValidationError("Cannot create order")
return Order.objects.create(**data)
# GOOD
def create_order(self, data) -> CreateOrderOutput:
if not self.can_create():
return CreateOrderOutput(success=False, error="Cannot create order")
order = Order.objects.create(**data)
return CreateOrderOutput(success=True, order_id=order.id)
Validation Returns Tuples
# BAD
def validate(self, order) -> bool:
return order.status != 'shipped'
# GOOD
def validate(self, order) -> tuple[bool, str]:
if order.status == 'shipped':
return False, "Cannot modify shipped orders"
return True, ""
Constructor Injection for Testability
# BAD - Hard to test
class CreateOrderUseCase:
def execute(self, input_dto):
service = OrderService() # Hardcoded dependency
return service.create(input_dto)
# GOOD - Easy to mock
class CreateOrderUseCase:
def __init__(self, order_service: OrderService):
self._order_service = order_service
def execute(self, input_dto):
return self._order_service.create(input_dto)
Additional Notes
Python Version: 3.10+ required (for X | None union syntax), 3.12+ recommended
Django Version: 4.2 LTS minimum, 5.0+ recommended
Naming Conventions:
- Use cases:
<action>_<entity>.py(e.g.,create_order.py) - Services:
<entity>_service.py(e.g.,order_service.py) - Serializers:
<Entity>ReadSerializer,<Entity>CreateSerializer
Integration with Other Skills:
- Use
django-celery-expertfor background tasks - Use
refactordjangoto migrate existing code to this architecture
More from agusabas/django-skills
refactordjango
Refactor Django/Python code to improve maintainability, readability, and adherence to best practices. Transforms fat views into Clean Architecture with Use Cases and Services. Applies SOLID principles, Clean Code patterns, Python 3.12+ features like type parameter syntax and @override decorator, Django 5+ patterns like GeneratedField and async views. Fixes N+1 queries, extracts business logic from views, separates Read/Write serializers, and converts exception-based error handling to explicit return values. Use when refactoring Django code, applying Clean Architecture, or modernizing legacy Django projects.
18django-celery-expert
Expert Django and Celery guidance for asynchronous task processing. Use when designing background tasks, configuring workers, handling retries and errors, optimizing task performance, implementing periodic tasks, or setting up production monitoring. Follows Celery best practices with Django integration patterns.
7google-merchant-feed
>
1