Minimal APIs, zwei Jahre später

Minimal APIs, zwei Jahre später

- Matt

Als ich seinerzeit über die Minimal Hosting API geschrieben habe, war ich sofort dabei. Eine Datei, ein paar Zeilen, fertig ist der Endpunkt. Kein Controller, keine Attribute, kein Ordner voller Klassen, die jeweils drei Methoden enthalten. Nach zwei Jahren mit mehreren Projekten fällt meine Bilanz gemischter aus.

Was hält, ist der Einstieg. Ein kleiner Dienst, der drei Endpunkte hat und JSON zurückgibt, ist in fünf Minuten geschrieben und danach immer noch lesbar. Für interne Werkzeuge, kleine Hintergrunddienste und alles, was man sonst als Skript gebaut hätte, ist das nach wie vor die richtige Wahl.

Was nicht hält, ist das Versprechen, dass es so bleibt.

Die Datei wächst

Das erste Projekt begann mit vierzig Zeilen Program.cs. Ein Jahr später waren es sechshundert. Irgendwann sucht man einen Endpunkt nicht mehr über die Projektstruktur, sondern über Strg+F, und das ist der Punkt, an dem die Übersicht weg ist. Bei Controllern hätte mir der Ordner die Antwort geschenkt.

Es gibt Gegenmittel. MapGroup bündelt verwandte Routen samt gemeinsamer Filter:

var kunden = app.MapGroup("/kunden")
    .RequireAuthorization()
    .WithTags("Kunden");

kunden.MapGet("/{id:guid}", KundenHandler.Laden);
kunden.MapPost("/", KundenHandler.Anlegen);

Und Handler müssen keine Lambdas sein. Bei mir wandern sie grundsätzlich in eine statische Methode, auch die einzeiligen, weil eine Datei mit gemischten Formen schlechter zu überfliegen ist als eine mit einer Form. Das ist dann allerdings ziemlich genau das, was ein Controller auch gewesen wäre, nur mit selbstgebauter Ablage. Man landet nach einigen Monaten bei einer Struktur, die dem entspricht, was man eingangs vermeiden wollte.

Wo sie wirklich besser sind

Zwei Punkte finde ich unabhängig von der Projektgröße überzeugend.

Die Parameterbindung ist ehrlicher. Was der Endpunkt braucht, steht in seiner Signatur, inklusive der Abhängigkeiten aus dem Container. Bei einem Controller muss man den Konstruktor mitlesen, um zu wissen, was eine Methode eigentlich benutzt.

Und TypedResults gibt einem Rückgabetypen, mit denen man arbeiten kann:

static async Task<Results<Ok<KundeDto>, NotFound>> Laden(
    Guid id, IKundenService service)
{
    var kunde = await service.Laden(id);
    return kunde is null
        ? TypedResults.NotFound()
        : TypedResults.Ok(kunde);
}

Der Rückgabetyp sagt, welche Antworten möglich sind. Das prüft der Compiler, es landet automatisch in der OpenAPI-Beschreibung, und im Test bekommt man ein typisiertes Ergebnis statt eines IActionResult, das man erst auspacken muss. Bei Controllern gibt es dafür Attribute, die niemand aktuell hält.

Mein Fazit

Ich benutze weiter beides und entscheide inzwischen nach einer einfachen Frage: Weiß ich, dass dieser Dienst klein bleibt? Dann Minimal API. Weiß ich es nicht, dann Controller, weil der Umbau später teurer ist als die zwanzig Zeilen Gerüst am Anfang.

Was mich stört, ist weniger die Technik als die Erzählung drumherum. Minimal APIs wurden als der bessere Weg verkauft, nicht als der bessere Weg für kleine Dienste. Diese Einschränkung fehlt in den meisten Beispielen, und man merkt sie erst, wenn das Projekt sie längst überschritten hat.