Este ușor de observat faptul că dezvoltarea de servicii REST a devenit una din activitățile centrale în proiectele software. Se pot găsi o multitudine de frameworkuri sau tehnologii care fac munca programatorului mai ușoară, dar ca programator de .net opțiunile sunt destul de limitate. API-uri bazate pe controller au fost și sunt cea mai răspândită modalitate de a aborda proiecte de genul acesta. Însă odată cu lansarea .NET 6 avem și o altă opțiune…
În 2019, o aplicație-model construită cu scopul de a prezenta caracteristicile multi-platformă ale Dapr - un runtime portabil, serverless, destinat micro serviciilor - a iscat discuții cu privire la procesul greoi și complex necesar pentru a scrie API-uri simple folosind dotnet. În exemplul de fașă, s-a implementat un sistem distribuit pentru operații aritmetice simple (un calculator), unde fiecare operație era executată de un micro serviciu diferit, fiecare dezvoltat prin limbaje de programare sau tehnologii diferite: adunarea scrisă în Go, înmulțirea în Python, împărțirea în Node și scăderea în .net.
Acest exercițiu simplu face facilă o comparație a diferitelor tehnologii, când logica implementată e similară și relativ simplă. De câtă structură și cod suplimentar e nevoie pentru a avea un serviciu capabil să facă o operație aritmetică fundamentală? Cu siguranță, mult mai multă în cazul serviciilor .net decât în celelalte.
Se pare totuși că Microsoft a păstrat urechea deschisă la aceste conversații, deoarece .NET 6 vine cu niște noi funcționalități interesante.
REST a devenit standardul implicit în lumea software când se vorbește de comunicarea între sisteme informatice. Există, desigur, și alte opțiuni. Interconectarea sistemelor distribuite e o problemă abordată din diferite perspective de aproape jumătate de secol. Spre exemplu, în anii '70 Remote Procedure Calls (RPC) erau la fel de răspândite cum e REST acum. Totuși, când vorbim de API-uri moderne există o multitudine de opțiuni și tehnologii de care ne putem folosi: REST, OpenData, GraphQL, gRPC și altele asemenea lor.
Toate aceste abordări au meritele lor și rezolvă elegant anumite probleme. Multe din cele considerate până nu demult de nișă, sunt adoptate pe scară largă de inginerii software. Totuși, realitatea este că REST rămâne cea mai populară și des întâlnită modalitate de comunicare între sisteme.
De-a lungul timpului, Microsoft a propus mai multe abordări pentru a dezvolta API-uri REST, dar cea folosită în primul rând este WebApi. Acesta face parte din familia de tehnologii grupată sub numele de ASP.NET MVC și potrivește într-un mod simplu și elegant codul cu conceptele REST. De exemplu, verbele și substantivele din teoria REST sunt cetățeni de prim rang în WebApi. Asta face din WebAPI o tehnologie modernă și adecvată cerințelor actuale. Probabil unul din aspectele nesemnificative și relativ ușor de ignorat e faptul că există o așa numită ceremonie în jurul dezvoltării acestor servicii, chiar și când vine vorba de API-uri foarte simple - vorbim aici de logică suplimentară și convenții de care trebuie ținut cont indiferent de complexitatea operațiilor pe care serviciul trebuie să le facă. E în mod evident un disconfort minor, dar odată cu adoptarea pe scara largă a arhitecturilor bazate pe micro servicii, acest aspect a devenit subiect de conversație în comunitate - în special când se compară lumea .net cu alte tehnologii din industrie.
Încercarea de a dezvolta serviciile într-un mod simplu și elegant nu e ceva nou - nici chiar pentru programatorii .net. Cei mai mulți ne amintim probabil - sau am avut privilegiul să lucram în felul acesta - de frameworkul Nancy. A putea scrie simplu și eficient API-uri e cu siguranță ceva extrem de util oricărui programator. Și exact asta vor să facă așa numitele API-uri minimale - sunt o altă modalitate de a dezvolta API-uri REST în .net. Nu vorbim aici despre modul corect sau incorect de a aborda o astfel de situație ci mai degrabă de o binevenită alternativă. Sunt indiscutabil multe situații în care abordarea clasică, bazată pe Controller, e alegerea mai bună: de pildă, API-uri complexe care au și cerințe mai avansate în ceea ce privește logica de autentificare și autorizare.
Ideea ce stă la baza Minimal API e aceea de a reduce sau elimina logica adițională, de susținere când abordăm API-uri simple. Tot ce trebuie făcut e definirea unor expresii lambda pentru operațiile din API. Poți obține un API complet și funcțional cu o operație de tip GET cu doar trei linii de cod:
var app = WebApplication.CreateBuilder(args).Build();
app.MapGet("/", () =\> "Hello world!");
app.Run();
Pentru persoanele nou-venite în lumea dezvoltării software această variantă e cu siguranță mai ușor de înțeles decât abordarea bazată pe Controller (fără MVC, fără convenții, fără terminologie de nișă). Tot ce trebuie făcut e specificarea unei rute (de exemplu /) și o funcție care să se execute când un Request pentru acea rută și acea acțiune ajunge la server. Aceste trei linii de cod sunt (dacă vă vine să credeți?) un program c# complet și valid.
Lucrul acesta e posibil datorită unor noi funcționalități adăugate în C#10 și .Net 6.
Directive using globale. Dacă ne folosim de această funcționalitate, directivele using folosite des sunt incluse în mod implicit - una din acestea este Microsoft.AspNetCore.Builder. Prin urmare, nu trebuie să adăugăm nicio instrucțiune de tip using la începutul fișierului.
Instrucțiuni de tip Top Level. Acestea evită necesitatea de a stabili punctul de start al execuției unui program într-o metoda statică a unei clase. Se reduce astfel cantitatea de cod necesară (lucru evident doar în programele foarte simple) - spre exemplu, pentru a avea un program care conține o linie de cod executabil, programatorul trebuie să scrie un program de 10-11 linii de cod.
Expresiile lambda vor acum să aibă un tip natural de date: compilatorul poate deduce tipul unui delegat dintr-o expresie lambda. Această funcționalitate ne permite să trimitem orice delegat drept callback în API-ul minimal.
Matrițele pentru rutele din API-urile minimale urmează aceeași logică folosită și de frameworkul MVC. Se pot folosi rute simple, static ca "api/todos", dar sintaxa specifică pentru parametrii continuă să funcționeze - chiar și în cazul constrângerilor pe parametri.
Când se injectează servicii într-un API minimal, acestea sunt specificate ca un simplu parametru în delegat. Aceasta se întâmplă diferit de abordarea actuală, unde dependințele sunt, în general, definite la nivelul clasei. Modul acesta de injectare a dependințelor nu schimba felul în care serviciile sunt administrate. De exemplu, API-urile minimale creează un scope pentru a oferi contextul serviciilor înregistrate scoped.
în exemplul prezentat mai sus am folosit metoda MapGet. Există astfel de metode ajutătoare pentru cele mai comune verbe HTTP: MapPost, MapPut, MapDelete. Pentru celelalte verbe, se poate folosi MapMethods.
API-urile minimale se folosesc de aceleași servicii pentru autentificare și autorizare și permit flexibilitatea de a decide particularitățile de securitate la nivelul fiecărei operații. Dacă în cazul unui API scris într-un Controller se folosește un decorator de tip Authorize, în cazul API-ului minimal se folosește metoda RequireAuthorization după declarare.
Am implementat un mic experiment bazat pe o serie de teste de performanță asupra a două aplicații care execută aceeași operație: căutare într-un dicționar. Toate datele sunt identice, singura diferență fiind modul în care au fost scrise aceste API-uri: o aplicație a folosit abordarea de tip Minimal API, iar cealaltă, abordarea bazată pe Controller. Rezultatele culese din acest mic experiment arată o îmbunătățire a performanței generale cu aproape 30%-40% în favoarea API-ului minimal.
Totuși, în punctul în care ne aflăm, dacă ceva părea prea bun pentru a fi adevărat, probabil că așa este. Scenariul abordat în experiment e foarte rar întâlnit în aplicațiile de producție. În majoritatea scenariilor reale, problemele de întârziere și performanță nu sunt datorate sistemului din spate ci din cauza folosirii unor resurse externe pentru a obține datele cerute. Apelurile către baze de date sau servicii externe vor domina și ascunde complet îmbunătățirile marginale de performanță obținute prin alegerea Minimal APIs.
API-urile minimale sunt o modalitate excelentă pentru cineva care dorește să cunoască pentru prima dată ecosistemul de tehnologii pentru servicii REST oferit de .net. Cel mai probabil, pe măsură ce un API devine mai complex, devin mai evidente și importante funcționalitățile adiționale oferite de API-urile bazate pe Controller. Totuși, grație simplității lor, API-urile minimale sunt o unealtă extrem de utilă pentru dezvoltarea de prototipuri sau aplicații de mici dimensiuni.