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

8.0 KiB
Raw Permalink Blame History

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

@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

  • @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

@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