Wprowadzenie
Na początku września opublikowałem pierwszy wpis o Azure Logic App. Był on wprowadzeniem do tej usługi – pokazałem jej działanie na prostym przykładzie, który pobierał informacje o pogodzie i zapisywał ją w Azure Storage. W tym wpisie chciałbym pokazać Ci bardziej rozbudowany przykład. Przy okazji którego przedstawię kolejne kwestie związane z budową aplikacji w Azure Logic App.
Przykład
Tym razem przykład będzie dużo bardziej rozbudowany i będzie zawierał większą liczbę elementów oraz dodatkowo dodane warunki. Od strony biznesowej będziemy chcieli co jakiś czas pobrać obrazek z strony http://picsum.photos i zapisać go w Azure Storage.
Następnie z wykorzystaniem usługi Azure Cognitive Services przeanalizujemy, co znajduje się na obrazku. Gdy będzie na nim ocean, to wtedy chcemy wygenerować link do pliku w Azure Storage. A na końcu wysłać wiadomość email z linkiem do pliku.
Na diagramie widać docelowy przepływ jaki chcemy uzyskać:

Aby zbytnio nie rozciągać wpisu, nie będę już pokazywał szczegółowej konfiguracji poszczególnych kroków, jak to było w pierwszym artykule. Skupię się na nowych elementach.
Usługi wykorzystane w tej aplikacji (Azure Storage, Cognitive Services oraz konto email w Outlook) należy przygotować wcześniej. Nie będę pokazywał, jak to zrobić. Nie jest to trudne i utworzenie ich nie powinno stanowić problemu.
Pobranie obrazka
Początek tworzenia nowej aplikacji jest taki sam jak w pierwszym wpisie. Tworzymy ją i zaczynamy od szablonu Recurrence, ustawiamy interwał (u mnie to „co 5 minut”). Następnie dodajemy blok dla wykonania żądania HTTP, wybieramy typ żądania na GET i wpisujemy adres https://picsum.photos/1200/800. Jest to widoczne na poniższym zrzucie:

Wartości liczbowe w adresie (1200 oraz 800) to odpowiednio szerokość oraz wysokość losowego obrazka, który wyświetli nam strona picsum.photos. Gdy chcemy wybrać inne wielkości, możemy odpowiednio zmienić adres url.
W tym momencie warto zapisać aplikację i ją uruchomić. W efekcie otrzymamy coś takiego:

Jak widać wykonanie aplikacji zakończyło się błędem. Jest to spowodowane tym, że żądanie do strony kończy się przekierowaniem, co widać na zrzucie powyżej (status odpowiedzi 302). Dodatkowo zaznaczyłem jeszcze nagłówek Location, w którym znajduje się adres, do którego zostajemy przekierowani. Będzie nam to potrzebne w dalszej części wpisu.
Niestety przekierowanie musimy obsłużyć sami. Azure Logic App samo nie wykona kolejnego żądania automatycznie. Ale dzięki temu w przykładzie możemy zobaczyć kolejne dostępne kroki.
Obsługa przekierowania
Do obsługi przekierowania wykorzystam akcję o nazwie Switch. Działa ona tak samo jak instrukcja switch w językach programowania (przy okazji zachęcam do przeczytania wpisu o switch w c# 8). Czyli określamy wybrane pole na podstawie, którego będzie działać blok. A następnie definiujemy poszczególne przypadki dla różnych wartości tego pola. Możemy dodatkowo określić domyślne zachowanie dla niezdefiniowanych wartości.
W przykładzie zbudujemy działanie Switch na podstawie statusu odpowiedzi (Status code). Dla wartości 302 (przekierowanie) wykonamy kolejną akcję HTTP. Natomiast w pozostałych przypadkach (opcja Default) zakończymy działanie aplikacji (akcja Terminate) z statusem Cancelled. Co widać na zrzucie poniżej:

Gdy w tym momencie zapiszemy aplikację i ją uruchomimy, zobaczymy coś takiego:

Widzimy, że blok Switch nie został wykonany. Jest to spowodowane tym, że przekierowanie w bloku HTTP kończy działanie aplikacji z statusem Failed.
Możemy zmienić to zachowanie. W tym celu klikamy w przycisk z trzema kropkami z prawej strony paska, w którym znajduje się nazwa bloku. A następnie wybieramy opcję: „Configure run after”.

W nowym oknie możemy wybrać, w jakich sytuacjach dany blok ma się wykonać. Domyślnie jest zaznaczony „is successful”. W naszym przypadku musimy jeszcze zaznaczyć „has failed”:

Po tej zmianie konfiguracji akcja Switch będzie już się wykonywała i możemy przejść do wykonania kolejnego żądania.
Kolejne żądanie HTTP
Do bloku Switch dodajemy kolejny blok HTTP dla Case z wartością 302. Ustawiamy typ żądania na GET, a następnie ustawiamy adres żądania, który znajduje się w nagłówku Location w odpowiedzi pierwszego żądania (zaznaczyłem tą wartość na zrzucie z pierwszego wykonania flow).
Tutaj natrafiamy na ograniczenie kreatora Azure Logic App. Panel wyboru Dynamic content bez problemu podpowiada nam takie właściwości pierwszego żądania HTTP jak Body, Status code czy Headers. Ale nie podpowie już nam poszczególnych nagłówków odpowiedzi. Musimy obejść to ograniczenie, a przy okazji zobaczymy jak pod spodem jest zapisana nasza aplikacja.
Aplikacja Azure Logic App zapisywana jest na koniec dnia w formie jsona. Każdy krok, jego konfiguracja, czy jakieś inne elementy. Dzięki czemu później możemy taką aplikację przechowywać na przykład w repozytorium i automatycznie wdrażać zmiany w różnych środowiskach (np. testowe, produkcyjne).
Aby zobaczyć kod naszej aplikacji wystarczy skorzystać z opcji „Code view” z głównego paska (zaraz obok przycisku do uruchomienia aplikacji). Aktualny kod mojej testowej aplikacji wygląda tak (w adresie drugiego żądania wybrałem Headers pierwszego żądania):
{ | |
"definition": { | |
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", | |
"actions": { | |
"HTTP": { | |
"inputs": { | |
"method": "GET", | |
"uri": "https://picsum.photos/1200/800" | |
}, | |
"runAfter": {}, | |
"type": "Http" | |
}, | |
"Switch": { | |
"cases": { | |
"Case": { | |
"actions": { | |
"HTTP_2": { | |
"inputs": { | |
"method": "GET", | |
"uri": "@{outputs('HTTP')['headers']}" | |
}, | |
"runAfter": {}, | |
"type": "Http" | |
} | |
}, | |
"case": 302 | |
} | |
}, | |
"default": { | |
"actions": { | |
"Terminate": { | |
"inputs": { | |
"runStatus": "Cancelled" | |
}, | |
"runAfter": {}, | |
"type": "Terminate" | |
} | |
} | |
}, | |
"expression": "@outputs('HTTP')['statusCode']", | |
"runAfter": { | |
"HTTP": [ | |
"Succeeded", | |
"Failed" | |
] | |
}, | |
"type": "Switch" | |
} | |
}, | |
"contentVersion": "1.0.0.0", | |
"outputs": {}, | |
"parameters": {}, | |
"triggers": { | |
"Recurrence": { | |
"recurrence": { | |
"frequency": "Minute", | |
"interval": 5 | |
}, | |
"type": "Recurrence" | |
} | |
} | |
}, | |
"parameters": {} | |
} |
W elemencie actions (linijka 4) widać dwie akcje HTTP (linijka 5) oraz Switch (linijka 13). W Switch widzimy jeden case (linijka 15) oraz akcję domyślną (linijka 29). Na samym końcu pliku w linijce 53 znajduje się definicja triggera, która powoduje uruchomienie aplikacji.
W linijce 17 znajduje się natomiast definicja drugiego żądania HTTP, które chcemy, aby pobrało obrazek. Linijka 20 zawiera właściwość uri, które określa właśnie adres żądania. Po wybraniu w kreatorze pola Header tutaj znajduje się coś takiego:
„uri”: „@{outputs(’HTTP’)[’headers’]}”
Gdy chcemy określić nagłówek Location, musimy ręcznie zmienić tę linijkę na (wybieramy pole Location z headers):
„uri”: „@{outputs(’HTTP’)[’headers’][’Location’]}”
Po powrocie do kreatora widać, że w polu URI znajduje się już wybraną wartość Location (niestety nie mamy tutaj informacji, że Location pochodzi z Headers):

W tym momencie, gdy zapiszemy i uruchomimy aplikację, widzimy, że wykonana się ona poprawnie (czyli wykonała dwa żądania HTTP), a w drugiej odpowiedzi znajduje się obrazek:

Zapis obrazka w Azure Storage
Po pobraniu obrazka chcemy zapisać go w Azure Storage. Sam krok jest identyczny jak w pierwszym wpisie. Różnica jest jedynie w tym, że podczas określania zawartości pliku w Dynamic Content widzimy dwa żądania. HTTP jest pierwszym, które skończyło się przekierowaniem. Natomiast HTTP 2 jest tym, które pobrało obrazek.
Niestety po wybraniu właściwości Body z HTTP 2 (zrzut niżej) nie widać tego w samej konfiguracji kroku tworzenia nowego bloba. Możemy zobaczyć to poprzez najechanie myszki na dane pole (zrzut niżej).
Poniżej konfiguracja tego kroku:

Analiza zawartości obrazku
Kolejny krok naszej aplikacji to analiza zawartości obrazka. W tym celu dodajemy nowy krok i w polu wyszukiwania wpisujemy „Describe image content”. W efekcie otrzymamy connector „Computer Vision API”, a w akcjach „Describe Image Content”, którą wybieramy:

Po dodaniu akcji musimy w pierwszej kolejności dodać nowe połączenie do usługi Cognitive Services. Niestety nie jest to takie przyjemne jak w przypadku połączenia do Azure Storage, gdzie wybieraliśmy interesujące nas konto. Tutaj musimy podać nazwę połączenia, a następnie ustawić klucz api oraz adres. Obie wartości są w sekcji „Keys and Enpoint” w panelu usługi.

Następnie musimy skonfigurować nasz krok, w którym należy podać zawartość obrazka do oceny. W tym celu skorzystamy, podobnie jak w przypadku zapisu pliku w Azure Storage, z treści odpowiedzi drugiego żądania HTTP:

Możemy teraz zapisać i uruchomić aplikację. W wyniku jej działania w ostatnim kroku otrzymujemy coś takiego:

W wyniku widzimy opis obrazka oraz tagi zawierające rzeczy znajdujące się na obrazku. Właśnie w tagach za chwilkę będziemy szukali słowa ocean i na podstawie tego wysyłali lub nie wiadomość email.
Warunek oceanu na zdjęciu
Do tego kroku wykorzystamy krok Condition, który jest w pewnym sensie odpowiednikiem if w językach programowania. Możemy tutaj budować rozbudowane warunki z wykorzystaniem operatorów logicznych takich jak And oraz Or.
W warunkach możemy oczywiście korzystać z Dynamic Content oraz wbudowanych funkcji. W naszym przykładzie sprawdzimy, czy kolekcja Tag Names zawiera słowo ocean. Jeśli tak, to za chwilkę dodamy kolejne kroki. Jeśli nie, to anulujemy działanie aplikacji:

Generowanie adresu SAS
Gdy na zdjęciu będzie ocean, będę chciał za chwilkę wysłać wiadomość email z wiadomością o zdjęciu oraz linkiem do niego. W tym celu muszę wygenerować link SAS (Shared Access Signatures). W dużym skrócie umożliwia on dostęp do prywatnego pliku znajdującego się w Azure Storage (w tym momencie nie chcę głębiej wchodzić w ten temat).
Do naszego flow dodajemy nowy krok (Create SAS URI by path), w którym, jak sama nazwa mówi, generujemy link SAS na podstawie ścieżki do pliku. W wyniku akcji dodania pliku do Azure Storage (jeden z wcześniejszych kroków) mamy właśnie dostęp do tej informacji w formie Dynamic Content. I właśnie tego użyjemy w konfiguracji kroku:

W kroku możemy jeszcze określić uprawnienie (Permissions). W tym przypadku wybrałem Read, które umożliwia tylko zobaczenie pliku. Możemy też określić reguły generowania linków SAS z wykorzystaniem pola „Group Policy Identifier”. Jak pisałem wyżej, nie będę tego tematu poruszał bardziej szczegółowo w tym wpisie.
Wysłanie wiadomości email
Pozostało nam dodać ostatni krok naszej aplikacji, czyli wysyłkę wiadomości email. Skorzystam tutaj z connectora do Outlook.com i akcji o nazwie „Send an email (V2)”.
Po dodaniu akcji, musimy w pierwszej kolejności zalogować się na konto, które ma zostać użyte do wysyłki. W tym momencie Azure Logic App utworzy nowe połączenie. A następnie możemy przejść do zdefiniowana treści wiadomości, która może wyglądać tak:

Poszczególne pola to odpowiednio: adresat wiadomości, tytuł oraz jej treść. W treści użyłem jedno pole z Dynamic Content. Web Url z akcji tworzenia linka SAS zawiera właśnie wygenerowany link do pliku z poprzedniego kroku. Więc użytkownik może kliknąć w niego i zobaczyć obrazek z oceanem.
Teraz możemy przejść do testowania całej naszej aplikacji.
Ustawienie danych do testów
Testowanie naszej aplikacji jest trochę problematyczne. Z racji tego, że bazujemy na losowych obrazkach, to może się okazać, że kroki, które mają zostać wykonane dla obrazków z oceanem będą wykonywane tylko co któreś wykonanie. Podczas pracy nad wpisem, nastąpiło to dopiero za 10 razem 🙂
Na szczęście Azure Logic App ma wsparcie dla testowania takich sytuacji. Możemy skorzystać z mechanizmu „Static result” dla poszczególnych akcji i ustawić dla nich na sztywno jakiś wynik. Dzięki czemu możemy łatwiej kontrolować działanie aplikacji podczas testów.
W przykładzie dodam testowy wynik dla drugiej akcji HTTP, która pobiera zdjęcie. W tym celu najlepiej wykonać aplikację i w sytuacji, gdy zdjęcie nam odpowiada, możemy wykorzystać taką odpowiedź. Aby ją skopiować wystarczy skorzystać z przycisku „Show raw outputs” z bloku tej akcji:

Następnie wracamy do kreatora aplikacji i dla akcji wybieramy opcję „Static result (Preview)” z menu dostępnego pod trzema kropkami z prawej strony paska tytułowego akcji:

W nowym oknie w pierwszej kolejności włączamy Static result (slider na samej górze), a później możemy ustawić treść odpowiedzi. Można to wyklikać lub ustawić całego jsona ręcznie korzystając z przycisku z ikoną T:

Po kliknięciu w ten przycisk możemy kleić odpowiedz, którą wcześniej skopiowaliśmy. Odpowiedź wklejamy do elementu „outputs”. Dodatkowo musimy jeszcze zmienić wartość w polu statusCode. Skopiowana odpowiedź ma wartość liczbową (bez cudzysłowów), natomiast tutaj musimy podać to jako tekst (wartość w cudzysłowach):

Po zatwierdzeniu odpowiedzi przyciskiem Done, zobaczymy, że w pasku tytułowym akcji pojawiła się nowa ikona:

W każdej chwili możemy włączać i wyłączać Static result. Azure Logic App będzie pamiętać ostatnio ustawione Static result, więc po ich ponownym włączeniu, nie będziemy musieli tego jeszcze raz ustawiać.
Podsumowanie
W tym wpisie pokazałem Ci bardziej rozbudowany przykład aplikacji w Azure Logic App. Przy okazji zobaczyliśmy kilka drobny problemów z ich rozwiązaniem.
Po długości wpisu możesz mieć wrażenie, że utworzenie tej aplikacji jest dość czasochłonne, ale tak naprawdę zajęło mi to kilka minut. Zdecydowanie dużo szybciej niż próba stworzenia takiej aplikacji np. w .NET Core.
Przykład ten pokazuje, że dzięki Azure Logic App można szybko przygotować test jakiejś usługi. W tym przypadku jest to Cognitive Services i analiza zawartości obrazka. Można również przygotować proste PoC, aby w praktyce zobaczyć pomysł na działanie jakiegoś przepływu.
W kolejnym wpisie z serii o Azure Logic App pokażę Ci koszty działania takiej aplikacji.
Bardzo fajny artykuł moją uwagę szczególnie przykuł fragment o środowiskach czekam na rozwinięcie tematu o ile zmierzasz oraz testowanie logic apps, static result robi robotę 😀
Co do POC to zgadzam się, w kilka godzin można zrobić prototyp, a czasami już gotową apke spełniająca wszystkie założenia, zostaje jeszcze pricing, ale POC’a to nie dotyczy