!-- Haupt-Meta-Tags -->

Praxis Wednesday #1: Nova’s Task API Code Review – Constructor Injection Schritt für Schritt erklärt❤️

Elyndra Valen Constructor Injection

📋 Kurze Zusammenfassung – Das Wichtigste in 30 Sekunden

Die Big 3 Verbesserungen:

  • Constructor Injection statt @Autowired (bessere Testbarkeit + Immutability)
  • ResponseEntity für korrekte HTTP-Status-Codes
  • Validation für sichere Eingaben
  • Bonus: Lombok Dos & Don’ts für cleanen Code ohne Boilerplate
  • Zeit-Investment: 15 Minuten Refactoring
  • Sofort umsetzbar: Ja, vollständiger Code enthaltenofessionelle HTTP-Responses
  • 🎯 GitHub Repository v1.1 mit allen Code-Beispielen

Zeitaufwand: 12 Minuten Lesezeit | Level: Junior bis Mid-Level | Frameworks: Spring Boot, Lombok

Hi zusammen! 👋

Erstmal zu mir: Ich bin Elyndra, 28 Jahre alt und seit kurzem Senior Entwicklerin bei Java Fleet – nach 4 Jahren intensiver Java-Entwicklung. Davor hatte ich 2 Jahre bei einem ERP-Anbieter gearbeitet, wo ich hauptsächlich Legacy-Systeme modernisiert habe. Das war eine harte aber sehr lehrreiche Schule!

Nach dem großartigen Feedback auf Nova’s ersten Spring Boot Post dachte ich mir: Zeit für einen konstruktiven Code Review!

Als frischgebackene Senior Developerin (ja, das Impostor Syndrome ist real! 😅) weiß ich noch genau, wie es sich anfühlt, den ersten funktionierenden Code zu schreiben. Nova hat fantastische Arbeit geleistet! Aber es gibt immer Raum für Verbesserungen.

Spoiler: Der Code ist richtig gut für den Anfang! Aber natürlich gibt es immer Verbesserungspotential. 😊

💚 Was Nova instinktiv RICHTIG gemacht hat

Bevor wir zu Verbesserungen kommen – let’s celebrate! Diese 3 Dinge zeigen, dass Nova gute Entwickler-Instinkte hat:

1. Saubere Package-Struktur gewählt

src/main/java/com/javafleet/taskmanager/
├── entity/
├── repository/
└── controller/

Why it’s great: Klare Trennung der Verantwortlichkeiten. Viele Anfänger werfen alles in ein Package!

2. H2 für Prototyping verwendet

Während andere Anfänger sich mit MySQL-Setup abmühen, ging Nova den pragmatischen Weg. Smart choice!

3. JpaRepository Interface korrekt erweitert

@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
    // Kein Code nötig! Spring Data generiert automatisch:
    // - findAll()
    // - findById(Long id)
    // - save(Task task)
    // - deleteById(Long id)
    // - count()
    // und viele mehr!
    
    // Aber hier kommt der Clou - Nova entdeckt Custom Finder:
    List<Task> findByCompleted(boolean completed);
}

Das ist bemerkenswert! Nova hat intuitiv verstanden, wie Spring Data JPA Naming Conventions funktionieren. Als jemand, die selbst Jahre gebraucht hat, um Spring Data richtig zu durchblicken, erkenne ich echtes Talent, wenn ich es sehe!

🎯 Warum Nova’s findByCompleted() so wertvoll ist:

Nova hat in ihrem Blog geschrieben:

„Das ist unglaublich: Ich schreibe eine Methodensignatur und Spring Data versteht automatisch, was ich will!“

Und sie hat vollkommen recht! Spring Data JPA analysiert den Methodennamen und generiert automatisch:

/ Ich schreibe:
List<Task> findByCompleted(boolean completed);

// Spring Data JPA generiert automatisch:
// SELECT * FROM tasks WHERE completed = ?
/ Meine Entity hat: title, description, completed
public interface TaskRepository extends JpaRepository<Task, Long> {
    
    // Nach einzelnen Properties suchen:
    List<Task> findByTitle(String title);           // WHERE title = ?
    List<Task> findByCompleted(boolean completed);  // WHERE completed = ?
    
    // Mit Operatoren:
    List<Task> findByTitleContaining(String text);  // WHERE title LIKE %?%
    List<Task> findByTitleStartingWith(String text); // WHERE title LIKE ?%
    
    // Kombinationen:
    List<Task> findByCompletedAndTitleContaining(boolean completed, String title);
    // WHERE completed = ? AND title LIKE %?%
    
    // Sortierung:
    List<Task> findByCompletedOrderByTitleAsc(boolean completed);
    // WHERE completed = ? ORDER BY title ASC
}

🎉 Nova’s Test – Es funktioniert wirklich!

# Test: Nur abgeschlossene Tasks finden
curl -X POST http://localhost:8080/api/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "Erledigt", "completed": true}'

curl -X POST http://localhost:8080/api/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "Offen", "completed": false}'

# Jetzt den Custom Finder testen:
curl http://localhost:8080/api/tasks/completed/true
# Antwort: Nur der "Erledigt" Task! 🎯

🤯 Warum ist das so genial?

1. Kein SQL schreiben: Ich denke in Java-Properties, nicht in SQL-Spalten 2. Type-Safe: Compiler checkt meine Methodensignatur 3. Lesbar: findByCompleted(true) ist selbsterklärend 4. Konsistent: Einmal gelernt, funktioniert überall gleich

Das versteht mein Gehirn noch nicht komplett: Wie kann Spring aus einem Methodennamen wissen, welche SQL-Query es bauen soll?!

📚 Dr. Cassian’s wissenschaftliche Erklärung:

„Nova, das nennt sich ‚Convention over Configuration‘ kombiniert mit ‚Reflection und Metaprogrammierung‘. Spring Data parst zur Laufzeit deine Methodennamen mit einem regulären Ausdruck-Parser, mappt die Wörter auf Entity-Properties und generiert dann die entsprechende SQL-Query über Hibernate’s Criteria API!“

Mein Hirn: „Okay Dr. Cassian, ich verstehe nur die Hälfte, aber es ist FANTASTISCH!“

🔧 Praktische Verbesserungen (die ich sofort umsetzen würde)

Verbesserung #1: Constructor Injection statt @Autowired – Warum das wichtig ist

Nova’s Version:

@RestController
public class TaskController {
    @Autowired
    private TaskRepository taskRepository;
}

Mein Vorschlag:

@RestController
@RequiredArgsConstructor  // Lombok magic
public class TaskController {
    private final TaskRepository taskRepository;
}

Aber Elyndra, warum ist das besser? Das fragen mich viele Junioren, und es ist eine wichtige Frage!

Dependency Injection Deep-Dive

Spring hat drei Arten von Dependency Injection:

1. Field Injection (@Autowired auf Felder) – Was Nova verwendet hat:

@Autowired
private TaskRepository taskRepository; // Spring "injiziert" hier

2. Setter Injection (@Autowired auf Setter):

private TaskRepository taskRepository;

@Autowired
public void setTaskRepository(TaskRepository taskRepository) {
    this.taskRepository = taskRepository;
}

3. Constructor Injection – Was ich empfehle:

private final TaskRepository taskRepository;

public TaskController(TaskRepository taskRepository) {
    this.taskRepository = taskRepository;
}

Warum Constructor Injection die beste Choice ist:

Grund #1: Immutability (Unveränderlichkeit)

private final TaskRepository taskRepository; // final = nie wieder änderbar

Mit final kann das Repository nach der Erstellung nie wieder null oder ein anderes Objekt werden. Thread-safe by design!

Grund #2: Einfacher zu testen(kleiner Teaser Dr. Cassian arbeitet an einer 6 teiligen Testreihe )

@Test
void testCreateTask() {
    // Mit Constructor Injection super easy!
    TaskRepository mockRepo = mock(TaskRepository.class);
    TaskController controller = new TaskController(mockRepo);
    
    // Test läuft...
}

Bei Field Injection müsstest du Reflection oder Spring Test Context verwenden. Constructor Injection = Plain Java!

Grund #3: Explizite Dependencies Der Konstruktor zeigt sofort: „Diese Klasse braucht ein TaskRepository zum Funktionieren!“ Bei Field Injection ist das versteckt.

Grund #4: Fail-Fast bei fehlenden Dependencies Wenn Spring das Repository nicht finden kann, crasht die App beim Start – nicht erst zur Runtime!

@RequiredArgsConstructor – Lombok’s elegante Lösung

@RestController
@RequiredArgsConstructor
public class TaskController {
    private final TaskRepository taskRepository;
    // Lombok generiert automatisch:
    // public TaskController(TaskRepository taskRepository) {
    //     this.taskRepository = taskRepository;
    // }
}

Was macht @RequiredArgsConstructor genau?

  • Generiert einen Konstruktor für alle final Felder
  • Plus alle Felder mit @NonNull
  • Clean Code ohne Boilerplate!

Aber Achtung bei multiple Dependencies:

@RestController
@RequiredArgsConstructor
public class TaskController {
    private final TaskRepository taskRepository;
    private final EmailService emailService;
    private final AuditService auditService;
    
    // Lombok generiert Konstruktor mit ALLEN drei!
}

Wenn du mehr als 3-4 Dependencies hast, ist das ein Code Smell – die Klasse macht zu viel!

Constructor Injection ohne Lombok (falls ihr kein Lombok wollt):

@RestController
public class TaskController {
    private final TaskRepository taskRepository;
    
    // Spring erkennt automatisch: nur ein Konstruktor = autowiring!
    public TaskController(TaskRepository taskRepository) {
        this.taskRepository = taskRepository;
    }
}

Seit Spring 4.3: Wenn eine Klasse nur einen Konstruktor hat, ist @Autowired nicht nötig!

Verbesserung #2: Response Entities für bessere HTTP-Semantik

Nova’s Version:

@PostMapping
public Task createTask(@RequestBody Task task) {
    return taskRepository.save(task);
}

Mein Vorschlag:

@PostMapping
public ResponseEntity<Task> createTask(@RequestBody Task task) {
    Task saved = taskRepository.save(task);
    return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}

Why? HTTP 201 CREATED statt 200 OK für neue Ressourcen!

Verbesserung #3: Validation hinzufügen

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @NotBlank(message = "Title is required")
    @Size(max = 100, message = "Title must be less than 100 characters")
    private String title;
    
    @Size(max = 500, message = "Description must be less than 500 characters")  
    private String description;
    
    private boolean completed = false; // Default value!
}

Plus im Controller:

@PostMapping
public ResponseEntity<Task> createTask(@Valid @RequestBody Task task) {
    Task saved = taskRepository.save(task);
    return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}

🤔 Lombok: Fluch oder Segen? (Die ehrliche Antwort)

Nova fragte mich: „Elyndra, sollte ich Lombok überall verwenden?“

Meine ehrliche Antwort nach 4 Jahren Java-Entwicklung: Es kommt darauf an! Lombok ist mächtig, aber wie Spider-Man sagt: „With great power comes great responsibility.“ 😄

Lombok JA bei:

DTOs und Entities (@Data):

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Task {
    // Lombok generiert: equals, hashCode, toString, getters, setters
    // Das sind ~50 Zeilen Code, die wir nicht schreiben müssen!
}

Constructor Injection (@RequiredArgsConstructor):

@Service
@RequiredArgsConstructor
public class TaskService {
    private final TaskRepository repository;
    private final EmailService emailService;
    // Konstruktor mit beiden Dependencies automatisch generiert
}

Builder Pattern (@Builder):

@Data
@Builder
public class TaskResponse {
    private Long id;
    private String title;
    private boolean completed;
}

// Usage:
TaskResponse response = TaskResponse.builder()
    .id(1L)
    .title("Learn Lombok")
    .completed(true)
    .build();

Lombok NEIN bei:

Komplexer Business Logic in Gettern/Settern:

// NICHT mit Lombok - zu komplex für @Data
public class BankAccount {
    private BigDecimal balance;
    
    public void setBalance(BigDecimal newBalance) {
        if (newBalance.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Balance cannot be negative!");
        }
        auditService.logBalanceChange(this.balance, newBalance);
        this.balance = newBalance;
    }
}

Wenn das Team Lombok nicht kennt: Lombok in einem Team einzuführen, das es nicht kennt, kann Verwirrung stiften. Team-Alignment first!

Bei sehr kritischen Klassen: Payment-Processing, Security-Komponenten – lieber explizit und verständlich.

🎯 Mein Lombok-Pragmatismus:

Für Nova’s Lernphase empfehle ich:

  1. Start small: @Data für Entities
  2. Learn the generated code: Schau dir an, was Lombok generiert (IDE → „Delombok“)
  3. Team erst, Lombok später: Verstehe Spring DI erst ohne Lombok, dann mit
  4. @RequiredArgsConstructor: Sobald Constructor Injection sitzt

Pro-Tipp: IntelliJ zeigt euch den generierten Code: View → Show Bytecode With Lombok

Mehr Lombok-Features und offizielle Dokumentation findet ihr auf: https://projectlombok.org/ – dort sind alle Features mit Beispielen erklärt!

⚠️ Lombok-Gotchas (Fallen, in die ich getappt bin):

1. @Data mit JPA-Entities:

@Entity
@Data  // Dangerous!
public class Task {
    @Id
    private Long id;
    
    @OneToMany
    private List<Comment> comments;
}

Problem: equals() und hashCode() mit Collections = Performance-Disaster + Lazy Loading Issues!

Better:

@Entity
@Getter
@Setter
@ToString(exclude = "comments")  // Exclude collections!
@EqualsAndHashCode(of = "id")    // Only use ID
public class Task {
    // ...
}

2. @AllArgsConstructor mit vielen Feldern:

@AllArgsConstructor
public class User {
    private String firstName;
    private String lastName; 
    private String email;
    private String phone;
    private String address;
    // ... 10 more fields
    
    // Konstruktor mit 15 Parametern = Unmaintainable!
}

Better: @Builder für viele Felder verwenden.

📋 Template: Meine „Task API 2.0“ Struktur

Basierend auf Nova’s Code, so würde ich die nächste Iteration strukturieren:

src/main/java/com/javafleet/taskmanager/
├── dto/
│   ├── TaskCreateRequest.java
│   └── TaskResponse.java
├── entity/
│   └── Task.java
├── repository/
│   └── TaskRepository.java
├── service/
│   └── TaskService.java
└── controller/
    └── TaskController.java

Why Service Layer? Business Logic gehört nicht in den Controller!

💡 Was ich von Nova gelernt habe (Seriously!)

Ehrlich? Nova’s direkter Ansatz hat mich daran erinnert, nicht zu viel zu komplizieren. Als Senior Dev neige ich dazu, sofort an alle Edge Cases zu denken:

„Was ist mit Caching? Brauchen wir ein Service Layer? Was ist mit Monitoring? Sollten wir Event Sourcing verwenden? Microservices? Kubernetes?“

Sometimes simple is better. Nova’s funktionierender Code ist 100x wertvoller als mein „perfekter“ Code, der nie fertig wird! 😊

Das „Senior Developer Paradox“

Je mehr Erfahrung wir sammeln, desto mehr Fallen sehen wir überall. Das kann paralysierend werden:

Nova’s Ansatz:

  1. Problem definieren ✅
  2. Einfachste Lösung implementieren ✅
  3. Funktioniert es? JA ✅
  4. Shipped!

Mein „Senior“ Ansatz früher:

  1. Problem definieren ✅
  2. Alle möglichen Probleme durchdenken 🤔
  3. Perfect Architecture designen 📋
  4. 3 Wochen später… noch nichts shipped 😅

Lesson learned: Working software over comprehensive documentation (Agile Manifesto). Nova lebt das instinktiv!

Nova’s „Hidden Wisdom“

Was ich in Nova’s Code entdeckt habe, das sie vielleicht selbst nicht bemerkt:

1. Pragmatische Tool-Wahl: H2 Database, curl statt Postman, VSCode statt „enterprise“ IDEs. Ship fast, optimize later!

2. Learning by Doing: Statt 3 Spring Boot Bücher zu lesen, hat sie einfach angefangen zu coden. Hands-on beats theory.

3. Community-Driven Learning: Ihr Blogpost, die Offenheit für Feedback – das ist Growth Mindset in Action.

Was ich als Senior noch von Juniors lerne

Fresh Eyes: Nova sieht Komplexitäten nicht, die ich überall sehe. Manchmal ist das ein Vorteil!

Unbiased Tool Choice: Ich bin „Spring Boot biased“ – Nova probiert aus, was funktioniert.

Rapid Iteration: Juniors shippen oft schneller, weil sie weniger Angst vor „imperfektem“ Code haben.

Authenticity: Nova’s ehrliche „Das verstehe ich nicht“-Posts sind wertvoller als meine „Ich weiß alles“-Artikel früher.

🎯 Nächste Schritte für Nova (und andere Juniors)

  1. Testing hinzufügen: @Test für Controller und Repository
  2. Error Handling: Global Exception Handler
  3. Service Layer: Business Logic aus dem Controller raus
  4. Swagger/OpenAPI: API-Dokumentation

Nova, du machst das großartig! Keep coding! 🚀

👥 An die Community: Eure Meinung?

Fragen für euch:

  • Nutzt ihr Constructor oder Field Injection?
  • Lombok: Love it or hate it?
  • Wie macht ihr Code Reviews in euren Teams?

Junior Devs: Schickt mir gerne eure Projekte! Ich mache gerne mehr konstruktive Reviews.

Senior Devs: Wie vermeidet ihr, dass Code Reviews demotivierend werden?


❓ FAQ – Häufige Fragen zu Constructor Injection & Lombok

F: Warum ist @RequiredArgsConstructor besser als @Autowired?
A: Drei Gründe: 1) Immutability durch final, 2) Einfacher zu testen (Plain Java), 3) Fail-fast bei fehlenden Dependencies. Plus: explizite Dependencies sind besser als versteckte.

F: Kann ich @Data für alle meine Klassen verwenden?
A: Nein! Bei JPA-Entities mit Collections kann @Data Performance-Probleme verursachen. Verwende dann lieber @Getter/@Setter mit @EqualsAndHashCode(of = „id“).

F: Was passiert, wenn ich zu viele Dependencies im Konstruktor habe?
A: Das ist ein Code Smell! Mehr als 3-4 Dependencies bedeuten meist: Die Klasse macht zu viel. Überlege dir, ob du die Klasse aufteilen kannst.

F: Muss ich @Autowired beim Constructor weglassen?
A: Seit Spring 4.3: Ja! Wenn eine Klasse nur einen Konstruktor hat, erkennt Spring das automatisch. @Autowired ist nicht mehr nötig.

F: Soll ich Lombok in Production verwenden?
A: Ja, aber mit Bedacht! Für DTOs und einfache Entities ist Lombok super. Bei komplexer Business Logic lieber explizit bleiben. Und: Das ganze Team muss Lombok kennen!


💬 Lass uns diskutieren!

Was denkst du über diese Verbesserungen? Hast du ähnliche Erfahrungen mit Constructor Injection gemacht? Oder nutzt du Lombok anders als ich?

Teile deine Meinung in den Kommentaren! Besonders interessant:

  • Welche Dependency Injection bevorzugst du und warum?
  • Lombok in Production: Eure Erfahrungen?
  • Habt ihr andere Code Review Tipps für Nova?

Junior Developers: Falls ihr Fragen zu den Beispielen habt oder ähnliche Herausforderungen erlebt – schreibt es gerne! Ich antworte auf jeden Kommentar.

Erfahrene Entwickler: Wie macht ihr Code Reviews in euren Teams? Was sind eure Lombok Best Practices?


P.S.: Nova hat schon angekündigt, nächsten Montag Validation zu lernen. Ich bin gespannt! 😄


Elyndra schreibt jeden Mittwoch über die Junior-zu-Senior Transition, Clean Code und praktische Java-Entwicklung hier im Blog! Folgt mir für mehr praktische Java-Tipps und ehrliche Entwickler-Geschichten.


Tags: #CodeReview #CleanCode #SpringBoot #Mentoring #JavaBestPractices #SeniorDeveloper


Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert