3.6 KiB
3.6 KiB
ANTI-PATTERN: Fat Views (Business Logic in Views)
KONTEXT
Django-Entwicklung, besonders bei wachsenden Projekten mit komplexer Domain-Logik.
WAS IST PASSIERT?
# SCHLECHT: Alle Logik in der View
@api_view(['POST'])
def create_match_schedule(request, scenario_id):
scenario = Scenario.objects.get(id=scenario_id)
# 50+ Zeilen Business Logic direkt in der View
teams = scenario.teams.all()
venues = Venue.objects.filter(team__in=teams)
# Komplexe Validierung
if len(teams) < 2:
return Response({'error': 'Too few teams'}, status=400)
if not all(t.venue for t in teams):
return Response({'error': 'Missing venues'}, status=400)
# Scheduling-Logik
matches = []
for home in teams:
for away in teams:
if home != away:
# 20 Zeilen Match-Erstellung...
match = Match.objects.create(...)
matches.append(match)
# Email-Benachrichtigung
for manager in scenario.season.league.managers.all():
send_mail(...)
return Response({'created': len(matches)})
WARUM WAR ES SCHLECHT?
- Nicht testbar: View-Tests brauchen HTTP-Request-Mocking
- Nicht wiederverwendbar: Logik nur über HTTP erreichbar
- Schwer lesbar: 200+ Zeilen Views
- Verstößt gegen SRP: View macht Validierung, DB, Email, Response
DIE BESSERE ALTERNATIVE
Fat Models
# models.py
class Scenario(models.Model):
def can_create_schedule(self) -> tuple[bool, str]:
"""Validate if schedule creation is possible."""
if self.teams.count() < 2:
return False, "Too few teams"
if self.teams.filter(venue__isnull=True).exists():
return False, "Missing venues"
return True, ""
def create_round_robin_matches(self) -> list['Match']:
"""Create all matches for round-robin tournament."""
matches = []
teams = list(self.teams.all())
for i, home in enumerate(teams):
for away in teams[i+1:]:
matches.append(Match(
scenario=self,
home_team=home,
away_team=away,
))
return Match.objects.bulk_create(matches)
Service Layer (für komplexe Workflows)
# services/scheduling.py
class SchedulingService:
def __init__(self, scenario: Scenario):
self.scenario = scenario
def create_schedule(self) -> list[Match]:
valid, error = self.scenario.can_create_schedule()
if not valid:
raise ValidationError(error)
matches = self.scenario.create_round_robin_matches()
self._notify_managers(matches)
return matches
def _notify_managers(self, matches: list[Match]):
# Email-Logik isoliert
...
Thin View
# views.py
@api_view(['POST'])
def create_match_schedule(request, scenario_id):
scenario = get_object_or_404(Scenario, id=scenario_id)
try:
service = SchedulingService(scenario)
matches = service.create_schedule()
except ValidationError as e:
return Response({'error': str(e)}, status=400)
return Response({'created': len(matches)})
ERKENNUNGSREGELN
- View-Funktion > 30 Zeilen → Refactoring-Kandidat
- Mehrere
objects.create()in einer View → Service extrahieren - Gleiche Logik in mehreren Views → Ins Model oder Helper
CHECKLISTE
- Views nur für Request/Response Handling?
- Business Logic im Model oder Service?
- Validierung im Model (
clean()) oder Serializer? - Side Effects (Email, Logging) in separater Klasse?