Azure Functions – na dzisiaj ślepa uliczka dla API

Azure Functions – obietnica serverless, rzeczywistość dla API

Azure Functions obiecywały wiele: skalowalność, niski koszt, zero zarządzania infrastrukturą. W teorii to idealne miejsce na wystawienie prostego API, zwłaszcza przy nieregularnym ruchu. W praktyce – funkcje nie są kompletnym frameworkiem webowym do budowy interfejsów HTTP. Tworzenie API w tym modelu często kończy się rozczarowaniem. Szczególnie teraz, gdy Microsoft porzuca stary model (in-process), a nowy (out-of-process) wprowadza poważne ograniczenia.

Dwa modele: in-process vs out-of-process

Usługa Azure Functions zadebiutowała na platformie Azure w 2016 roku jako rozwiązanie serverless, które pozwala uruchamiać małe, niezależne funkcje reagujące na zdarzenia – takie jak żądania HTTP, wiadomości z kolejki czy zdarzenia czasowe – bez potrzeby zarządzania infrastrukturą. Początkowo funkcje działały wyłącznie w modelu in-process, czyli były uruchamiane w tym samym procesie, co host Azure Functions. Pozwalało to na korzystanie z naturalnego dla programistów zestawu narzędzi ASP.NET przy budowie API – takiego samego, jak w aplikacjach OnPremises, kontenerach Docker czy Azure App Service.

Z czasem jednak ten model zaczął ujawniać swoje ograniczenia. Współdzielenie procesu z hostem oznaczało brak izolacji środowisk, trudności w aktualizacji wersji .NET, konflikty zależności oraz ograniczoną możliwość niezależnego rozwoju i testowania. Problemem była też niższa niezawodność i większe ryzyko błędów przy uruchamianiu bardziej złożonych projektów.

Aby zaadresować te wyzwania, Microsoft wprowadził w 2020 roku model out-of-process (znany również jako isolated worker). Funkcje działają tu w osobnym procesie .NET, który komunikuje się z hostem za pomocą RPC. Taka separacja pozwala używać dowolnych wersji środowiska .NET niezależnie od platformy Azure Functions i unikać konfliktów wersji.

Model isolated eliminuje wiele ograniczeń technicznych modelu in-process, ale jednocześnie rezygnuje z kilku kluczowych cech. Microsoft zdecydował, że to właśnie model out-of-process będzie jedynym wspieranym w przyszłości. Wsparcie dla Azure Functions in-process zakończy się w listopadzie 2026 roku.

Problemy modelu out-of-process w budowie API

Model out-of-process rozwiązuje pewne problemy techniczne starszego podejścia, ale w kontekście budowy API wprowadza wiele ograniczeń. Trudno je zaakceptować przy pracy nad rzeczywistymi usługami HTTP. Poniżej przedstawiam najważniejsze z nich:

Brak wsparcia dla ASP.NET

Nie można użyć UseMiddleware(), nie ma dostępu do IApplicationBuilder. Oznacza to, że nie zbuduje się automatycznie pełnego pipeline’u z autoryzacją, walidacją modeli, filtrowaniem błędów. Wszystko trzeba implementować inaczej i bardziej ręcznie. W praktyce oznacza to więcej kodu do utrzymania oraz utratę możliwości szybkiego zmianu sposobu hostowania API - gdy zdecydujesz się budowac w Isolated Model, migracja to innego środowiska będzie kosztowna.

Brak możliwości użycia najlepszych narzędzi do generowania dokumentacji API

W Azure Functions Isolated model, nie da się po prostu dodać Swashbuckle/Swagger do generowania dokumentacji jak w Web API. Potrzebna jest osobna biblioteka -Microsoft.Azure.Functions.Worker.Extensions.OpenAPI. Niestety na dzisiaj ma ona ograniczenia. Nie wspiera wielu cech ASP.NET, na przykład generowania dokumentacji dla klas powiązanych dziedziczeniem należących do kontraktu. Dodatkowo konfiguracja biblioteki jest mniej intuicyjna, a dokumentacja bywa niejasna. DX (Developer Experience) wyraźnie się pogarsza.

Cold start i niestabilna wydajność

Model out-of-process wymaga uruchomienia osobnego procesu. W planie konsumpcyjnym może to oznaczać kilkadziesiąt sekund czekania na odpowiedź po dłuższej przerwie. Co gorsza, każdy endpoint HTTP traktowany jest niezależnie – jeśli Twoje API składa się z wielu funkcji, to każda z nich ma własny cold start. W modelu in-process wystarczyło rozgrzać jeden endpoint (np. /health), by uruchomić cały proces hosta i tym samym „wybudzić” wszystkie punkty dostępowe na raz. W modelu isolated to już nie działa. Cold starty są bardziej dotkliwe i trudniejsze do obejścia. W mojej ocenie to show stopper dla większości przypadków tworzenia publicznego API.

Model in-process – umiera, ale był bardziej wygodny

Mimo swoich ograniczeń, model in-process był po prostu praktyczny dla małych API. Działał jak okrojony ASP.NET Web API – wspierał kontrolery, middleware, znane mechanizmy DI, a wiele gotowych narzędzi działało od ręki. Teraz Microsoft oficjalnie każe go porzucić. Od .NET 8 wspierany jest wyłącznie isolated worker. Jeśli korzystałeś wcześniej z in-process, będziesz musiał zaplanować migrację. Jeśli dopiero zaczynasz – warto rozważyć inne podejście do hostowania API, na przykład Azure App Service.

Flex Consumption – odpowiedź na problem cold startów w Azure Functions

Flex Consumption Plan pojawił się w Azure Functions w 2024 roku jako odpowiedź na problem cold startów. Ten model łączy zalety serverless podstawowego planu Consumption z utrzymywaniem funkcji w stanie „ciepłym” podobnie jak w planie Premium. Dzięki temu w API o nieregularnym ruchu pierwsze żądania trafiają do w pełni gotowych instancji, a ryzyko cold startu jest minimalne. Przy skalowaniu Azure korzysta z puli wstępnie przygotowanych instancji, dzięki czemu kolejne instancje uruchamiają się znacznie szybciej niż w klasycznym Consumption.

Niestety korzystanie z opcji Always Ready Instances oznacza stałe koszty – tu nie ma już darmowego miliona requestów miesięcznie, a płaci się cały czas, podobnie jak w App Service. Dodatkowo usługa nie eliminuje pozostałych ograniczeń modelu isolated.

Azure App Service – stary dobry koń roboczy

Jeśli chcesz wystawić produkcyjne REST API, nawet małe, to Azure App Service z Minimal API lub Web API moim zdaniem będzie lepszym wyborem.

Dostajesz:

  • pełne wsparcie powszechnie znanego przez deweloperów środowiska ASP.NET,

  • działające Swashbuckle w 3 linijki,

  • middleware, DI, filtry, walidację modeli,

  • przewidywalne czasy odpowiedzi i brak cold startów,

  • bardzo dobre lokalne testowanie – zwłaszcza jeśli używasz kontenerów

  • możliwość prostrzej migracji rozwiązania w przyszłości na inne platformy

Azure App Service daje elastyczność. Możesz uruchomić API jako zwykłą aplikację lub jako kontener – zarówno na Windowsie, jak i Linuksie.

Choć nie ma planu płatności „pay-as-you-go”, to już dziś można mieć stale działające API produkcyjne na Linuksie za ok. 45 zł miesięcznie.

Jeśli więc chcesz hostować małe API bez obaw o wydajność i przewidywalność działania – moim zdaniem Azure App Service wygrywa jakością i ergonomią z Azure Functions Isolated Model.

Jak używam Azure App Service

Sam wykorzystuję Azure App Service do hostowania mojego pet projektu - aplikacji z żartami Jokes Portal. Jest to aplikajca mobilna, która wykorzystuje Azure App Service. Działa 24/7, obsługuje realnych użytkowników i potrzebuje stabilnego API bez cold startów. Usługa sprawdza się tu znakomicie.

Podsumowanie: Nie każde rozwiązanie to API

W tym artykule skupiłem się wyłącznie na przypadku budowy REST API. Nie twierdzę, że Azure Functions są złe – wręcz przeciwnie. Model isolated to świetne narzędzie do budowy systemów event driven w chmurze Azure. Funkcje wspierają integracje z niemal wszystkimi usługami Azure out-of-the-box i doskonale sprawdzają się w scenariuszach asynchronicznych oraz transakcjach rozproszonych (Durable Functions).

Ze względu na opisane wyżej ograniczenia uważam, że Azure Functions w modelu isolated nie nadają się dziś do budowy synchronicznych REST API. W takich przypadkach warto postawić na Azure App Service. To może nie jest modne, ale po prostu działa.

0
Subscribe to my newsletter

Read articles from Beniamin Lenarcik directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Beniamin Lenarcik
Beniamin Lenarcik

.NET Developer | I build applications from concept to production. On my blog, I share practical examples (.NET “by example”) and thoughts on software architecture and bridging technology with business.