Ostatnio wszystkie tematy związane z Chmurą oraz pojęciem Serverless stały się bardzo modne. Jednym z takich elementów są Azure Functions. Na stronie Microsoftu można przeczytać, że:

Azure Functions to rozwiązanie umożliwiające łatwe uruchamianie małych fragmentów kodu („funkcji”) w chmurze. Możesz napisać tylko kod rozwiązujący aktualny problem, nie martwiąc się o całą aplikację ani infrastrukturę do jej uruchomienia. Dzięki usłudze Functions programowanie może być jeszcze wydajniejsze i można korzystać z wybranego języka programowania, takiego jak C#, F#, Node.js, Python lub PHP. Płać tylko za czas działania kodu — platforma Azure jest skalowana zgodnie z potrzebami. Usługa Azure Functions pozwala tworzyć na platformie Microsoft Azure aplikacje niewymagające użycia serwera.

I rzeczywiście mógłbym pisać dużo o komforcie użycia, łatwości wdrożenia i jeszcze kilkunastu innych cechach, które urzekły mnie w tym rozwiązaniu. Super sprawdzają się w przypadku projektów, które nie potrzebują dużego backendu. Przetestowałem to rozwiązanie wielokrotnie i jestem z niego zadowolony.

Nie odbyło się jednak bez błędów, o których chciałbym wspomnieć – a dokładniej dwóch.

Pre-kompilacja

Pierwszym z nich jest pre-kompilacja funkcji. Portal Azure pozwala na tworzenie i edytowanie funkcji bezpośrednio w portalu. Jest to bardzo wygodne ponieważ proste funkcje mogą zostać napisane bez użycia dodatkowych narzędzi. Nie ma jednak róży bez kolców. Jeżeli nie zostaną one wykorzystane choć raz w okresie pięciu minut przechodzą one w stan wstrzymania. Ponowne ich wywołane spowoduje wystąpienia tzw. cold start. Czyli nasze funkcje zostaną ponownie zindeksowane oraz skompilowane. Spowoduje to dłuższe wykonanie takiego zapytania. Opisana sytuacja ma w szczególności miejsce w przypadku modelu płatności za liczbę wykonań.

Rozwiązaniem tego problemu jest wrzucenie na serwer skompilowanej dll, która będzie zawierała metodę, którą chcemy uruchomić oraz odpowiednio przygotowanego pliku z konfiguracją – funcjon.json. Oprócz skrócenia czasu odpowiedzi w przypadku cold startu to podejście ma jeszcze parę istotnych zalet:

[list]

  • możemy korzystać z pełnego wsparcia Visual Studio w trakcie tworzenia kodu,
  • łatwo napisać testy jednostkowe,
  • możliwość podpięcia rozwiązania pod CI,
  • zdecydowanie łatwiej przenieść obecne funkcjonalności do funkcji,
  • nie potrzebujemy pliku project.json do zarządzania zależnościami nugetowymi.

[/list]

Na krótki komentarz zasługują trzy pierwsze punkty. Należy pamiętać, że obecnie nie ma jeszcze dobrego wsparcia narzędziowego do tworzenia Azure Functions. Nie ma rozszerzenia do Visual Studio 2017 (jest ono dostępne dla wersji Preview). W przypadku dodatku do Visual Studio 2015 IntelliSense praktycznie nie działa.

Atomowość

Drugi błąd dotyczył wielkości funkcji, a dokładniej atomowości operacji. Zgodnie z rekomendacją Microsoftu funkcje powinny robić dokładnie tylko jedną rzecz. Z tego powodu zostało nałożone jedno z ograniczeń na długość ich wykonywania. Funkcja może działać najdłużej 5 minut. Po tym czasie jest przerywana.

Tylko co to jest ta atomowość. Zacznę od negatywnego przykładu. Jedna z moich funkcji była w zasadzie takim proxy. Udostępniała WebAPI, które przyjmowało dane z formularza wypełnionego przez użytkownika i przesyłało je do innego systemu przy wykorzystaniu zewnętrznego WebAPI. Dosłownie kilka linijek kodu.

Czy to nie jest wystarczająco atomowe? Niestety nie w przypadku Azure Functions.

Azure Function Flow

Operacja z przykładu powinna zostać podzielona na dwie mniejsze. Po pierwsze musimy zarejestrować dane z formularza i dodać je do późniejszego przetworzenia, wykorzystując np. jakąś kolejkę. Następnie musimy stworzyć funkcję, która będzie przetwarzać dane z kolejki. Takie podejście pozwala wysłać do użytkownika potwierdzenie otrzymania danych w bardzo krótkim czasie. Dodatkowo Microsoft twierdzi, że ich kolejki są bardzo bezpieczne i nie ma prawa wystąpienie utraty danych. Pozostaje nam więc przetworzenie danych. Możemy to zrobić nie blokując użytkownika.

Takie podejście nie jest pozbawione wad. Po pierwsze, zwiększa się ilość elementów, które musimy utrzymywać. Po drugie, należy zastanowić się nad obsługą błędów. W sytuacji, gdy mieliśmy zamkniętą całą logikę w jednej funkcji to o wystąpieniu jakiegokolwiek błędu mogliśmy poinformować użytkownika w bardzo prosty sposób modyfikując odpowiednio odpowiedź z serwera. Jeśli rozdzielimy funkcję na mniejsze elementy to użytkownik będzie poinformowany tylko o tym, że jego prośba została zakolejkowana. Natomiast błędów powstałych na etapie przetwarzania zakolejkowanych elementów nie można przesłać do użytkownika w sposób bezpośredni. Trzeba przygotować jakieś inne rozwiązanie.