Files
Orchestrator/bahn/O2C-Harness/clean-code.md
ankn a5f8fb49ab Migrate all repos into monorepo context folders
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.
2026-06-30 20:39:52 +02:00

238 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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**: `@RestControllerAdvice` für einheitliche Error-Responses
- **Keine generischen Exceptions** werfen (nicht `throw new RuntimeException`)
- **HTTP Status Codes** korrekt nutzen (400, 404, 409, 422, 500)
### Pattern
```java
@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
```yaml
# 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
```yaml
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:
```json
{
"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):
```java
@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):
```java
@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
- `@Data` für Entities und DTOs
- `@Builder` für DTOs (immutable Construction)
- `@Value` für immutable Value Objects (statt @Data)
- `@Slf4j` für Logger (statt `private static final Logger log = ...`)
- `@RequiredArgsConstructor` für Constructor-Injection (statt @Autowired)
## Validierung
### Schichten
1. **Bean Validation** (`@Valid`, `@NotNull`, `@Size`) Syntaktische Prüfung im Controller
2. **Business Rules** Fachliche Validierung im Service/Validator (eigene Schicht)
3. **Domain Invarianten** Im Entity selbst (z.B. `@PrePersist`)
### Pattern: Separater Validator
```java
@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