Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
8.0 KiB
8.0 KiB
Clean Code & Architektur
Architektur: Ports & Adapters (Hexagonal)
┌─────────────────────────────────────────┐
│ Application Core │
│ ┌─────────────────────────────────┐ │
│ │ Domain Model │ │
│ │ (Entities, Value Objects) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Use Cases / Services │ │
│ │ (Business Logic, Ports) │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
▲ ▲
Driving Ports Driven Ports
│ │
┌────────┴───────┐ ┌────────┴───────┐
│ REST API │ │ Repository │
│ (Adapter) │ │ (Adapter) │
│ Controller │ │ JPA/JDBC │
└────────────────┘ └────────────────┘
- Domain: Reine Business-Logik, keine Framework-Abhängigkeiten
- Ports: Interfaces die Use Cases definieren (inbound) und externe Systeme abstrahieren (outbound)
- Adapters: Implementierungen (REST Controller, JPA Repository, Kafka Consumer)
Separation of Concerns
- Controller/Handler: Nur Request/Response Mapping, Validierung, keine Business-Logik
- Service: Business-Logik, Orchestrierung, Transaktionen
- Repository/DAO: Datenzugriff, Queries
- Model/Entity: Datenstrukturen, Domain-Logik
- DTO: Daten-Transfer zwischen Schichten (nicht Entity direkt exponieren)
- Mapper: Entity ↔ DTO Konvertierung
- Config: Konfiguration, Beans, Dependency Injection
Exception Handling
Strategie
- Domain Exceptions: Fachliche Fehler (z.B.
AddressNotFoundException) - Global Exception Handler:
@RestControllerAdvicefür einheitliche Error-Responses - Keine generischen Exceptions werfen (nicht
throw new RuntimeException) - HTTP Status Codes korrekt nutzen (400, 404, 409, 422, 500)
Pattern
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(EntityNotFoundException ex) {
return ResponseEntity.status(404)
.body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleValidation(ConstraintViolationException ex) {
return ResponseEntity.status(400)
.body(new ErrorResponse("VALIDATION_ERROR", ex.getMessage()));
}
}
Observability (Logging, Tracing, Metrics)
Logging
- Structured Logging (JSON) mit SLF4J/Logback
- Log-Level: ERROR (Fehler), WARN (unerwartetes Verhalten), INFO (Business-Events), DEBUG (Entwicklung)
- Correlation-ID in jedem Log-Eintrag (aus Request-Header oder generiert)
- Keine sensiblen Daten loggen (Passwörter, Tokens, PII)
Tracing (OpenTelemetry)
- OTEL für Distributed Tracing (Spring Boot Actuator + Micrometer)
- Trace-ID und Span-ID in Logs propagieren
- Externe Calls (DB, HTTP, Kafka) automatisch instrumentiert
# application.yml
management:
tracing:
sampling:
probability: 1.0
otlp:
tracing:
endpoint: http://k8s-monitoring-alloy-receiver.monitoring.svc.cluster.local:4317
# CNP Grafana Alloy - OTLP gRPC Port 4317
# Doku: https://cnp.gitpages.tech.rz.db.de/core/docs/cnp/latest/cnp-portfolio/observability/tracing.html
Metrics
- Spring Boot Actuator Endpoints (
/actuator/health,/actuator/metrics,/actuator/prometheus) - Custom Business-Metrics wo sinnvoll (z.B. Bestellungen pro Minute)
- Prometheus-Format für Grafana-Dashboards
Health Checks
management:
endpoint:
health:
show-details: always
health:
db:
enabled: true
kafka:
enabled: true
Clean Code Prinzipien
- Single Responsibility: Eine Klasse/Funktion = eine Aufgabe
- DRY: Keine Code-Duplikation, gemeinsame Logik extrahieren
- KISS: Einfachste Lösung die funktioniert
- YAGNI: Nichts implementieren was nicht gefordert ist
- Dependency Injection: Abhängigkeiten injizieren, nicht selbst erstellen
- Interface Segregation: Kleine, fokussierte Interfaces
- Open/Closed: Offen für Erweiterung, geschlossen für Änderung
Naming
- Klassen: PascalCase, Substantive (UserService, OrderRepository)
- Methoden: camelCase, Verben (findById, createUser, validateInput)
- Variablen: camelCase, sprechend (userCount statt n, isActive statt flag)
- Konstanten: UPPER_SNAKE_CASE
- Packages: lowercase, Singular (controller, service, model)
Methoden
- Max 20 Zeilen (Richtwert)
- Max 3 Parameter (sonst Object/DTO)
- Keine boolean-Parameter (Split in zwei Methoden)
- Early Return statt tiefe Verschachtelung
Error Responses (API)
Einheitliches Format:
{
"error": "NOT_FOUND",
"message": "Address with id 42 not found",
"timestamp": "2026-05-17T19:00:00Z",
"path": "/api/addresses/42"
}
Configuration
- application.yml für Defaults
- application-{profile}.yml für Stage-spezifisch (dev, iu, prod)
- Secrets NICHT in application.yml (über Env-Variablen oder SecretProviderClass)
- @ConfigurationProperties statt @Value für typsichere Config
Mapper: MapStruct
Entity ↔ DTO Mapping über MapStruct (compile-time, kein Reflection):
@Mapper(componentModel = "spring")
public interface AddressMapper {
AddressDto toDto(Address entity);
Address toEntity(AddressDto dto);
List<AddressDto> toDtoList(List<Address> entities);
}
- Kein manuelles Mapping in Services
- MapStruct generiert Implementierung zur Compile-Zeit
- Bei komplexen Mappings:
@Mapping(source = "...", target = "...")
Lombok
Getter, Setter, Builder über Lombok (kein Boilerplate):
@Data // Getter + Setter + toString + equals + hashCode
@Builder // Builder-Pattern
@NoArgsConstructor // JPA braucht Default-Konstruktor
@AllArgsConstructor // Für Builder
@Entity
public class Address {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String street;
private String city;
private String zip;
}
Konventionen
@Datafür Entities und DTOs@Builderfür DTOs (immutable Construction)@Valuefür immutable Value Objects (statt @Data)@Slf4jfür Logger (stattprivate static final Logger log = ...)@RequiredArgsConstructorfür Constructor-Injection (statt @Autowired)
Validierung
Schichten
- Bean Validation (
@Valid,@NotNull,@Size) – Syntaktische Prüfung im Controller - Business Rules – Fachliche Validierung im Service/Validator (eigene Schicht)
- Domain Invarianten – Im Entity selbst (z.B.
@PrePersist)
Pattern: Separater Validator
@Component
@RequiredArgsConstructor
public class OrderValidator {
public void validate(OrderRequest request) {
if (request.getQuantity() > MAX_QUANTITY) {
throw new BusinessRuleViolationException("Maximale Bestellmenge überschritten");
}
// Weitere fachliche Regeln...
}
}
Konventionen
- Bean Validation für einfache Feld-Prüfungen (nicht null, Format, Länge)
- Eigener Validator für fachliche Regeln (Abhängigkeiten zwischen Feldern, DB-Lookups)
- Business-Regeln NICHT im Controller
- Custom Constraint-Annotations für wiederverwendbare Validierungen