# ANTI-PATTERN: Fat Views (Business Logic in Views) ## KONTEXT Django-Entwicklung, besonders bei wachsenden Projekten mit komplexer Domain-Logik. ## WAS IST PASSIERT? ```python # 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 ```python # 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) ```python # 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 ```python # 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?