• Akademia
  • Blog
  • O Serverless
  • O stronie

AWS Step Functions - refaktoryzacja do natywnych integracji z AWS API


AWS Step Functions - refaktoryzacja do natywnych integracji z AWS API

W tym artykule opisz臋 jak pozby艂em si臋 dw贸ch funkcji Lambda w moim procesie 艂aduj膮cym dane do jeziora danych.

Wszystko za spraw膮 zmiany og艂oszonej w pa藕dzierniku 2021, za spraw膮 kt贸rej Step Functions otrzyma艂o natywn膮 integracj臋 przez API z ponad 200 us艂ugami AWS. Musz臋 przyzna膰, 偶e czeka艂em na t臋 aktualizacj臋 ju偶 od wakacji, gdy m贸j TAM zdradzi艂, 偶e zesp贸艂 Step Functions pracuje nad nowymi integracjami.

Co si臋 zmieni艂o w Step Functions?

Pewnym preludium do ostatniej aktualizacji by艂a dost臋pna ju偶 od d艂u偶szego czasu nowa forma wywo艂ywania funkcji Lambda. R贸偶nice by艂o wida膰 tylko w j臋zyku definiuj膮cym maszyn臋 stan贸w. W starej konfiguracji, wywo艂anie Lambdy definiowali艣my tak:

1
2
3
StateName:
Type: Task
Resource: !GetAtt functionName.Arn

W nowej robimy to w nast臋puj膮cy spos贸b:

1
2
3
4
5
6
StateName:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Parameters:
FunctionName: !GetAtt functionName.Arn
Payload.$: $

Na pierwszy rzut oka wida膰 zmian臋. Zamiast w艂asnego zasobu podajemy uniwersalnego klienta w polu Resource, dopiero jemu podajemy nazw臋 naszej funkcji do uruchomienia jako parametr.

Ta zmiana dla nas - u偶ytkownik贸w - nie mia艂a 偶adnego znaczenia, oba zapisy s膮 r贸wnowa偶ne. Domy艣lam si臋 jednak, 偶e stanowi艂a krok milowy na drodze do udost臋pnienia natywnej integracji Step Functions z AWS API pozosta艂ych us艂ug.

Od teraz mamy dost臋p do 200 r贸偶nych integracji, kt贸re wywo艂ujemy w taki spos贸b:

1
2
3
4
5
6
StateName:
Type: Task
Resource: arn:aws:states:::aws-sdk:<serwis>:<akcja>
Parameters:
ParametrApi1: warto艣膰
ParametrApi2: warto艣膰

AWS SDK - cz艂owiek czuje si臋 jak w domu 馃槂

Tym sposobem mo偶esz skopiowa膰 plik z S3 (arn:aws:states:::aws-sdk:s3:copyObject), uruchomi膰 maszyn臋 EC2 (arn:aws:states:::aws-sdk:ec2:startInstances), i tak dalej. Opcji jest multum.

Ale ile to wszystko kosztuje?

I tu jest najwi臋kszy bajer: nic nie kosztuje.

Powtarzam: jest za darmoszk臋 馃槂

W sensie samo wywo艂anie API SDK (o ile jest darmowe, wi臋kszo艣膰 jest, tu si臋 nic nie zmienia). Za sam膮 maszyn臋 stanu i dzia艂anie wywo艂anego serwisu oczywi艣cie p艂acimy, jak dotychczas.

Skoro nic nie kosztuje, to warto zast膮pi膰 funkcje Lambda wywo艂uj膮ce inne serwisy natywn膮 integracj膮. Dzi臋ki czemu oszcz臋dzimy na kosztach Lambdy (tak naprawd臋 to mniej ni偶 groszowe koszty i w szerszym kontek艣cie pomijalne), ale r贸wnie偶 na czasie implementacji funkcji.
No bo jak zawsze: brak kodu to najlepszy kod.

A ile to faktycznie trwa?

Ta oszcz臋dno艣膰 czasu na implementacji to przyznam Ci si臋 szczerze nie jest taka oczywista. Gdy refaktoryzowa艂em dwie funkcje na natywne integracje, to zaj臋艂o mi to 3 godziny. W ciul czasu. No ale jak wszyscy wiemy:

Tak to wygl膮da艂o w moim wypadku, gdy偶 API AWS nie zwraca idealne na tacy tego, co by艣my sobie 偶yczyli akurat w danym momencie. Zwracane warto艣ci trzeba obrobi膰, co jest trudne, jak si臋 nie ma funkcji Lambda do dyspozycji. I nie czyta艂o si臋 dokumentacji 馃ぃ

Refaktoryzacja

W moim wypadku zamieni艂em implementacje po lewej na t臋 po prawej. Przypominam, ca艂y proces przetwarza dane i 艂aduje do Data Lake, st膮d odwo艂ania do serwisu Glue. W Twoim przypadku to mo偶e by膰 dowolna inna us艂uga AWS.

Ca艂a maszyna stan贸w jest wywo艂ywana z payloadem, w kt贸rym jest podana nazwa Glue Crawlera. Lambda oczekiwa艂a zmiennej crawlerName, natomiast API StartCrawler potrzebuje Name.

To nie by艂o jeszcze takie trudne do rozwi膮zania:

1
2
3
4
5
StartCrawler:
Type: Task
Resource: arn:aws:states:::aws-sdk:glue:startCrawler
Parameters:
Name.$: $.crawlerName

Natomiast du偶ym problem dla mnie by艂o ogarni臋cie danych wyj艣ciowych z tego stanu, gdy偶 startCrawler nie zwraca 偶adnych danych, a domy艣lnie StepFunctions przekazuje do nast臋pnego kroku wynik aktualnego. Dwa kroki dalej GetCrawler te偶 b臋dzie potrzebowa艂 nazw臋 Crawlera, aby sprawdzi膰 jego stan (czy jest uruchomiony albo zako艅czy艂 dzia艂anie).

Problem rozwi膮za艂em poprzez si臋gni臋cie do globalnego kontekstu wywo艂anej maszyny stan贸w oraz pos艂u偶enie si臋 filtrem ResultSelector do zbudowania w艂asnego obiektu na wyj艣ciu ze stanu.

1
2
3
4
5
6
7
StartCrawler:
Type: Task
Resource: arn:aws:states:::aws-sdk:glue:startCrawler
Parameters:
Name.$: $.crawlerName
ResultSelector:
crawlerName.$: $$.Execution.Input.crawlerName

To tylko pogl膮dowy przyk艂ad i nie uwzgl臋dnia obs艂ugi b艂臋d贸w i mapowania ich na wyj艣ciu ze stanu.

To spowodowa艂o, 偶e na wyj艣ciu ze stanu mia艂em obiekt ze zmienn膮 crawlerName. O to mi chodzi艂o.

Nast臋pnym krokiem by艂o pobranie aktualnego stanu Crawlera metod膮 GetCrawler w p臋tli, kt贸ra trwa, dop贸ki nie zako艅czy on dzia艂ania. Pos艂u偶y艂em si臋 tutaj podobnym rozwi膮zaniem jak wcze艣niej. Warto艣膰 state jest wykorzystywana przez warunek (Czy Crawler zako艅czy艂 dzia艂anie?) w p臋tli, a crawlerName jest parametrem wej艣ciowym dla kolejnego wywo艂ania stanu GetCrawler. Crawler.State pochodzi z wynik贸w zwr贸conych przez API getCrawler.

1
2
3
ResultSelector:
crawlerName.$: $$.Execution.Input.crawlerName
state.$: $.Crawler.State

 

Czy by艂o warto to zmienia膰?

Oczywi艣cie, 偶e tak, mimo tego, i偶 kosztowo (w tym konkretnym przypadku) to nie by艂o uzasadnione.

Po pierwsze brak kodu to najlepszy kod. Tutaj zast臋pujemy customowy kody Lambdy deklaratywn膮 definicj膮 maszyny stan贸w. To znaczy, 偶e dowolna osoba na 艣wiecie znaj膮ca Step Functions zrozumie, jak to dzia艂a, bez zag艂臋biania si臋 w nasz wspania艂y kodzik Lambdy.

Po drugie 膰wiczenie czyni mistrza. 呕潭o潭n潭g潭l潭o潭w潭a潭n潭i潭e潭 Mapowanie warto艣ci na wyj艣ciu jest trudne tylko na pocz膮tku. Jak si臋 ogarnie jak to dzia艂a, to dalej idzie du偶o sprawniej. Mam nadziej臋, 偶e ten tekst pomo偶e Ci unikn膮膰 wielu problem贸w, kt贸re ja napotka艂em 馃檪

Po trzecie natywne integracje, s膮 na tyle wszechstronne, 偶e od teraz praktycznie w ka偶dej maszynie stanu b臋dzie mo偶na z nich skorzysta膰, a to oznacza, 偶e inwestycja w ich opanowanie na pewno si臋 zwr贸ci przy kolejnej implementacji.

Po czwarte, deklaratywny kod maszyny stan贸w jest 艂atwiejszy do ponownego u偶ycia (copy paste) ni偶 kod Lambdy.