In unseren Kundenprojekten begegnen wir immer wieder einer Situation, die vielen Software-Entwicklern bekannt vorkommen dürfte: Ein erfahrener Entwickler stößt bei der Arbeit an einem Feature auf ein etabliertes Pattern im Code und denkt sich “Das könnte man besser machen!”. Was dann oft folgt, ist ein Pull Request für ein neues Feature, welcher nebenbei ein völlig neues Pattern einführt.
Warum dieser gut gemeinte Verbesserungsversuch ohne entsprechende Kommunikation und Migrationsstrategie oft das Gegenteil bewirkt und die Codebasis nachhaltig verschlechtert - und wie sich das vermeiden lässt - zeigen wir in diesem Artikel.
Ein Beispiel aus der Praxis
Ein typisches Beispiel aus einem Angular-Frontend-Projekt: Das Team verwendet Services mit BehaviorSubjects für das State Management. Der State wird in verschiedenen Feature-Services gekapselt und bei Bedarf in andere Services oder Komponenten injiziert.
Ein Entwickler arbeitet an einem Feature für die Benutzerverwaltung. Bei der Code-Review seines Pull Requests fällt auf: Statt der etablierten Services mit BehaviorSubjects wurde ngRx für das State Management eingeführt.
Die Begründung im PR: “ngRx bietet eine bessere Nachvollziehbarkeit durch Actions, einen zentralen State Store und tolle DevTools fürs Debugging. Außerdem haben wir damit einen standardisierten Ansatz für komplexeres State Management.”
Die Probleme mit diesem Vorgehen
Auch wenn die technische Argumentation valide sein mag, entstehen durch dieses Vorgehen mehrere schwerwiegende Probleme:
1. Inkonsistente Codebasis
Die Codebasis entwickelt sich zu einem “Two-Face”-System: neue Features nutzen ngRx, bestehende Features BehaviorSubjects. Dies führt zu:
- Erhöhter kognitiver Last beim Code-Review
- Unklarheit bei der Implementierung von Features, die beide Bereiche betreffen
- Risiko einer dauerhaften Aufspaltung der Architektur
2. Fehlende Team-Abstimmung
Das Team wird vor vollendete Tatsachen gestellt und hat keine Chance:
- Vor- und Nachteile gründlich abzuwägen
- Eigene Erfahrungen mit beiden Patterns einzubringen
- Alternative Lösungen vorzuschlagen
- Die Auswirkungen auf andere Teile des Systems zu evaluieren
3. Mangelnde Migrationsstrategie
Ohne klaren Plan für die Behandlung des bestehenden Codes entstehen weitere Risiken:
- Unklare Zeitlinie für die vollständige Migration
- Gefahr, dass die Migration nie abgeschlossen wird
- Unsicherheit über Budget und Ressourcen für die Migration
4. Erschwerte Einarbeitung
Neue Teammitglieder stehen vor besonderen Herausforderungen:
- Müssen beide Patterns verstehen und beherrschen
- Brauchen Wissen, wo welches Pattern zum Einsatz kommt
- Haben keine dokumentierte Begründung für die verschiedenen Ansätze
5. Langfristige technische Schulden
Die unkoordinierte Änderung kann zu weitreichenden Problemen führen:
- Erschwertes Refactoring durch inkonsistente Strukturen
- Höherer Testaufwand durch verschiedene Patterns
- Potenzielle Performance-Probleme durch uneinheitliche Implementierungen
Diese Probleme betreffen nicht nur Architekturänderungen wie im obigen Beispiel, sondern auch kleinere Anpassungen wie die Anwendung bestimmter Design Patterns oder Code-Styles.
Eine bessere Lösung: Architecture Decision Records (ADRs)
Architecture Decision Records (ADRs) bieten einen strukturierten Weg für solche Änderungen. Es muss sich dabei nicht um schwerfällige Architektur-Dokumente handeln. Das sollten vielmehr kurze, fokussierte Aufzeichnungen wichtiger Entscheidungen sein.
ADRs können bewusst leichtgewichtig gehalten werden. Ein typischer ADR ist nicht länger als eine Bildschirmseite und konzentriert sich auf das Wesentliche: die Entscheidung und ihre Auswirkungen. Die Dokumentation orientiert sich an einer klaren Struktur, unnötige Formalitäten werden bewusst vermieden.
Der Prozess fügt sich nahtlos in die gewohnte Entwicklungsarbeit ein. Wenn ein Entwickler eine Idee für eine architektonische Verbesserung hat, erstellt er einen ADR als Pull Request - dies dauert meist nur 15-20 Minuten. Das Team kann dann asynchron im PR diskutieren oder, falls nötig, ein kurzes Meeting ansetzen. Sobald ein Konsens erreicht ist, wird der ADR gemerged und die Umsetzung kann beginnen.
Dieser schlanke Prozess schafft echten Mehrwert. Entscheidungen werden transparent und nachvollziehbar, die Dokumentation entsteht quasi nebenbei im normalen Entwicklungsprozess. Neue Teammitglieder können sich schnell einarbeiten, da sie die Gründe für architektonische Entscheidungen nachlesen können. Auch vermeidet man wiederholte Diskussionen über bereits getroffene Entscheidungen.
Ein praktisches Template für ADRs
Folgendes Template für ADRs hat sich in unseren Projekten bewährt. Es ist schlank, aber deckt alle wichtigen Aspekte ab. Wir freuen uns, wenn es breitere Anwendung findet. Es steht unter CC BY 4.0 Lizenz zur freien Verwendung.
# ADR: [Title of the Architectural Decision]
## Date
[YYYY-MM-DD]
## Status
[Proposed | Accepted | Deprecated | Superseded]
## Context
[2-3 sentences describing the problem or situation that drives this decision]
## Decision
[Clear and concise description of the chosen approach/pattern]
## Consequences
### Positive
- [Benefit 1]
- [Benefit 2]
- [Benefit 3]
### Negative
- [Drawback 1]
- [Drawback 2]
- [Drawback 3]
## Migration Plan
[Describe how and when the change will be implemented. For example: "Complete migration in a single PR by end of sprint" or "Step-by-step migration over the next 3 sprints"]
## Notes
- [Additional important information]
- [Related issues or documentation]
- [Special considerations]
Ein konkretes Beispiel: Migration zu ngRx
Schauen wir uns an, wie ein ausgefüllter ADR für unser State Management Beispiel aussehen könnte:
# ADR: Migration from BehaviorSubjects to ngRx for State Management
## Date
2024-12-13
## Status
Proposed
## Context
Our Angular application currently manages state through services using BehaviorSubjects. While this approach has worked well for simpler state management, we're experiencing challenges with state traceability, debugging, and maintaining consistency across larger features. The current solution also lacks standardized patterns for complex state interactions.
## Decision
We will adopt ngRx as our state management solution. ngRx provides:
- A centralized store for application state
- Clear action-based state modifications
- Powerful debugging capabilities through DevTools
- Built-in support for complex state operations
- Strong typing and improved maintainability
We'll implement this as our standard approach for all state management, replacing the current BehaviorSubject pattern.
## Consequences
### Positive
- Better state traceability through explicit actions
- Improved debugging capabilities with DevTools
- Standardized approach across the application
- Better handling of complex state interactions
- Type-safe state operations
### Negative
- Learning curve for team members new to ngRx
- Migration effort for existing state management
- Increased boilerplate for simple state changes
- Initial setup complexity
- Potential performance overhead for simple scenarios
## Migration Plan
Given the size of our application, we'll implement this change gradually over the next quarter:
1. Set up ngRx infrastructure and create common patterns
2. Create documentation and conduct team training
3. Implement new features using ngRx
4. Migrate existing features module by module, starting with user management
5. Remove old BehaviorSubject implementations once all consumers are migrated
## Notes
- Training sessions planned for sprints 1 and 2
- DevTools setup guide to be added to wiki
- Performance monitoring needed during migration
- Consider keeping simple states in services if ngRx would be overengineered
Dieser ADR wird im Code Repository beispielsweise unter docs/adrs/2024-12-13__migration-from-behaviorsubjects-to-ngRx.md
abgelegt und ist so für Entwickler jederzeit zugreifbar und versioniert.
Fazit
Architecture Decision Records wandeln architektonische Änderungen von einer potenziellen Quelle technischer Schulden zu einer Chance für kontrollierte technische Weiterentwicklung. Die initiale Investition von 15-20 Minuten für das Schreiben eines ADRs zahlt sich dabei mehrfach aus.
Kurzfristig profitieren Teams von einer strukturierten Diskussion der Änderung. Technische Argumente werden sachlich abgewogen, Risiken frühzeitig erkannt und alternative Lösungsansätze in Betracht gezogen. Der transparente Prozess sorgt für bessere Akzeptanz der Entscheidung im Team.
Mittelfristig ermöglicht die klare Dokumentation der Entscheidungsgründe und des Migrationspfads eine kontrollierte Umsetzung. Das Team kann den Fortschritt direkt im Code Repository einsehen und bei Problemen gezielt nachsteuern. Die Wahrscheinlichkeit, dass Migrationen in einem inkonsistenten Zustand stecken bleiben, sinkt deutlich.
Langfristig bilden ADRs ein wertvolles Architektur-Logbuch. Neue und bestehende Teammitglieder können jederzeit die Evolution der Architektur und aktuelle Best Practices nachvollziehen. Bei späteren Änderungen lassen sich die Gründe für bestehende Entscheidungen nachschlagen, statt sie mühsam zu rekonstruieren oder blindlings zu verwerfen.
Der vermeintliche Overhead der Dokumentation wird durch diese Vorteile mehr als aufgewogen. Statt ungeplanter Experimente mit unklarem Ausgang entwickelt sich die Architektur in kontrollierten, nachvollziehbaren Schritten. Das sorgt für eine langfristig wartbare Codebasis und ein gemeinsames technisches Verständnis im Team.