# 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 handleNotFound(EntityNotFoundException ex) { return ResponseEntity.status(404) .body(new ErrorResponse("NOT_FOUND", ex.getMessage())); } @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity 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 toDtoList(List
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