Loading...

Spring MVC: Wie man ein Custom Converter für Typsicherheit in Controller verwendet

Spring
23. August 2023
3 Minuten Lesezeit
Beitrag teilen:
Gefällt dir der Beitrag?
Du wirst den Newsletter lieben!

Sind deine Controller andauernd mit Primitiven voll? Im ArticleController lautet die erste Zeile jeder Methode: articleRepository.findById(articleId). Immer wieder machen wir die gleichen Konvertierungen. In kaum einem anderen Layer beobachte ich so viele Primitive wie im API Layer. Das führt zu viel Boilerplate. Der Code wird schwerer zu testen. Und er ist schwieriger zu lesen. Dabei wissen wir wie viel einfacher es ist, wenn wir typsicher arbeiten. In diesem Artikel zeige ich dir, wie du das Converter Interface nutzen kannst, um deine primitiven automatisch in den korrekten Typ zu überführen.

Ein Converter in Spring ist ein einfaches Interface, das eine Transformation zwischen Typen definiert. Das Interface sieht folgendermaßen aus:

@FunctionalInterface
public interface Converter<S, T> {

	/**
	 * Convert the source object of type {@code S} to target type {@code T}.
	 * @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
	 * @return the converted object, which must be an instance of {@code T} (potentially {@code null})
	 * @throws IllegalArgumentException if the source cannot be converted to the desired target type
	 */
	@Nullable
	T convert(S source);

}

Ein Converter sorgt für Typsicherheit

Die Typsicherheit ermöglicht es, deinen Code robuster und weniger fehleranfällig zu gestalten. Durch die Implementierung eines Converters kannst du sicherstellen, dass die Daten, die durch die verschiedenen Schichten deiner Anwendung fließen, stets den erwarteten Typen entsprechen. Das trägt nicht nur zur Codequalität bei, sondern auch zur Wartbarkeit und Verständlichkeit des Codes.

Zum Beispiel kann ein Converter in Kombination mit @PathVariable genutzt werden

Nehmen wir an, du hast eine API, die unter GET /api/v2/article/myArticleId die Details eines Artikels liefert. Mithilfe von @PathVariable kannst du auf myArticleId zugreifen:

@RestController
@RequestMapping("/api/v2/article")
public class UserController {

    private final ArticleRepository articleRepository;
    
    public UserController(ArticleRepository articleRepository) {
        this.articleRepository = articleRepository;
    }

    @GetMapping("/{myArticleId}")
    public ArticleDTO getArticle(@PathVariable long myArticleId) {
        Article article = articleRepository.findById(myArticleId);
        return ArticleDTO.from(article);
    }
}

In dieser Implementierung werden wir immer wieder die gleiche Konvertierung machen. In allen weiteren Operationen rufen wir zuerst articleRepository#findById auf. Was in diesem kleinen Beispiel kein wirkliches Problem ist, wird zu einem, sobald unsere API komplexer wird.

Mit einem eigenen Converter können wir diese Komplexität verringern

Und das ist gar nicht kompliziert. Um obige Konvertierung zentral zu machen reicht folgende Implementierung:

@Component
public class LongToArticleConverter implements Converter<Long, Article> {

    private final ArticleRepository articleRepository;

    public LongToArticleConverter(ArticleRepository articleRepository) {
        this.articleRepository = articleRepository;
    }
    
    @Override
    public Article convert(Long articleId) {
        return articleRepository.findById(articleId);
    }
}

Durch @Component haben wir den Converter automatisch dem Context bekannt gemacht

Jetzt kannst du Article direkt im Controller verwenden

Der schwierige Teil ist bereits geschafft. Wir können unsere Implementierung von oben durch die folgende ersetzen:

@RestController
@RequestMapping("/api/v2/article")
public class UserController {

    @GetMapping("/{myArticleId}")
    public ArticleDTO getArticle(@PathVariable Article article) {
        return ArticleDTO.from(article);
    }
}

Ist das nicht viel simpler? Unser Controller beschreibt eindeutig seinen Purpose. Und diese Methode ist nicht nur leicht zu lesen. Sie ist auch leicht zu testen. Du kannst jetzt einen Unit-Test schreiben, der ohne einen mock auskommt. Wir haben sauberen, modularen und leicht verständlichen Code geschrieben.

Kennst du schon Marcus' Backend Newsletter?

Neue Ideen. Jede Woche!
Top