Wybrane Case Study

Opisaliśmy tutaj jeden z naszych projektów, aby zobrazować sposób, w jaki możemy pracować nad Twoim

System zarządzania automatami paczkowymi

Dla klienta z branży produkcyjnej zaprojektowaliśmy, wykonaliśmy oraz utrzymujemy kompleksowy system zarządzania automatami paczkowymi oparty na chmurze Microsoft Azure. W niniejszym case study przedstawiamy wybrane, najistotniejsze aspekty, z którymi mierzyliśmy się projektując ten system.

Kluczowym założeniem projektu była modułowa architektura aplikacji, pozwalająca dostosowywać niewielkim nakładem pracy rozwiązanie do różnych scenariuszy spedycyjnych, które występują wśród sieci handlowych, w których rozwiązanie zostało wdrożone. Charakterystyka rynku obejmuje istnienie wielu sieci handlowych, których sposoby dostarczania zamówień są zróżnicowane. System do obsługi automatów paczkowych musiał umożliwiać przeprowadzenie, u każdego z tych klientów korporacyjnych, procesów przez pracowników i klientów w sposób zintegrowany z dotychczasowymi procesami sieci, działający jako uzupełnienie istniejących ścieżek.

Projekt rozpoczęty został od analizy funkcjonalnej skupiającej się na wszystkich interesariuszach i określającej obszary, w których różne grupy interesariuszy kontaktują się ze sobą. Dodatkowo ocenie podlegał poziom bezpieczeństwa urządzeń dostępowych, z których planowani interesariusze mają korzystać. Na tej podstawie wypracowany został podział systemu na poszczególne jednostki wykonawcze.

Podzieliliśmy system na odrębne aplikacje obsługujące osobne obszary styku z klientem i infrastrukturą fizyczną. Dzięki temu ryzyka związane z atakiem na jeden obszar mają najmniejszy możliwy wpływ na obszary pozostałych rodzajów użytkowników. Osobne jednostki logiczne (aplikacje uruchomione w wyodrębnionym zasobie chmurowym) odpowiadają za komunikację z klientami końcowymi: pracownikami sklepów, klientami sklepów, automatami paczkowymi, pracownikami serwisu oraz administratorami systemu. Również oddzielenie warstwy administratorów pozwoliło uzyskać dodatkowy, rzadko implementowany benefit w obszarze cyber security, o którym więcej piszemy niżej. Poza obszarami obsługującymi komunikację ze wskazanymi wyżej użytkownikami systemu, w projekcie znalazły się również pomocnicze aplikacje backendowe wspierające agregowanie danych, m.in. w celu efektywnego raportowania oraz monitorowania kryzysowego.
Najważniejszym obszarem systemu jest aplikacja centralna Core. Przygotowana w oparciu o framework Laravel (więcej o naszej polityce wyboru wersji narzędzi tutaj) modułowa aplikacja obsługująca bezpośrednio kolejkowanie operacji głównej bazy danych (Core DB). Istotą prawidłowego przygotowania tej aplikacji było określenie, jaki powinien być poziom dostępu do poszczególnych informacji i ich aktualizacji, określone w dwóch perspektywach – uprawnień oraz szybkości dostępu.

Niektóre procesy w związku ze swoją specyfiką wymagają wykonywania w czasie rzeczywistym, jednak złym pomysłem jest z tej przyczyny zaprojektować cały system w dążeniu do synchronicznego wykonywania ogółu operacji. Konieczna jest analiza spodziewanego czasu reakcji poszczególnych aktorów systemu i na tej podstawie pogrupowanie poszczególnych działań w procesach według najdłuższego komfortowego czasu trwania. Pozwala to zaprojektować system w kompromisie pomiędzy bardzo drogą infrastrukturą w której uruchamiane są wyłącznie zasoby obliczeniowe on-demand w momencie wykonywania operacji, a systemem o stałej ilości mocy obliczeniowej, ale działającym zbyt wolno w stosunku do oczekiwań interesariuszy.

Wyznaczenie odpowiedniego podziału procesów na funkcje pozwoliło zaplanować system kolejek wykonujących odpowiednie działania, mając na uwadze zarówno czas spodziewany, jak i ważność poszczególnych procesów.

Cel został osiągnięty – aktualizacje informacji o zapełnieniu skrytek w automatach dostępne są w ciągu ułamka sekundy (i to rozesłane do każdej aplikacji współpracującej), historie zdarzeń dostępne są po kilkudziesięciu sekundach, natomiast raporty aktualizują się w perspektywie kilku minut. Dodatkowe czynności weryfikujące spójność danych i flagujące ewentualne zdarzenia wymagające ręcznego sprawdzenia przygotowywane są poza godzinami szczytu. Dzięki takiemu kompromisowi czasowemu ilość zasobów obliczeniowych została zminimalizowana, a z tym także ich cena – i to nawet o 66% w stosunku do wariantu, w którym by zaprojektowano system wykonujący wszystkie zadania z równymi horyzontami czasowymi.

Drugi z obszarów wymagający szczególnej uwagi na etapie projektowania Core’a to uprawnienia. Aplikacja ta pełni rolę strażnika spójności informacji w pozostałych podsystemach, w związku z tym konieczne było określenie interfejsów dla pozostałych aplikacji w sposób pozwalający pogodzić odpowiednie role poszczególnych aplikacji z potencjalnym niebezpieczeństwem płynącym z komunikacją z każdą z nich z osobna.

Skąd to niebezpieczeństwo? Generalnie należy zawsze założyć, że aplikacja, której nie mamy pod całkowitą kontrolą (np. jest umieszczona w obcej infrastrukturze klienta, administrowana lub nawet tylko użytkowana przez osoby trzecie) może być potencjalnym źródłem ataków wewnątrz infrastruktury systemu. Dzieje się tak dlatego, że istnieje potencjalna możliwość przejęcia każdego z dostępów interesariuszy przez atakujących i wykorzystania ich do obciążenia innych elementów systemu (tzw. atak lateralny, z ang. lateral movement attack). Dla przykładu, jeśli w systemie istnieją aplikacje A i B, które komunikują się ze sobą, a do aplikacji B dostęp ma np. zewnętrzny serwisant, to gdy celem atakującego jest aplikacja A, może on zamiast szukać drogi dostępu bezpośrednio do niej, zaatakować zewnętrznego serwisanta (np. hakując jego sieć lub urządzenie). Po uzyskaniu od niego dostępów do aplikacji B, atakujący wyznacza procesy, które komunikują się z aplikacją A i stara się ich nadużywać (np. wysyłać ogromne ilości zapytań), aby przeciążyć aplikację A, w nadziei, że ukaże ona w takim scenariuszu jakąś podatność.

Aby zapobiec tym ryzykom Core posiada kilka wyodrębnionych obszarów obejmujących endpointy API udostępniane różnym klasom podsystemów, z których każde jest również wyposażone w szczegółową macierz uprawnień. Dlaczego? Aby instancja innej aplikacji (np. Shepherd, nasz daemon synchronizacji automatów paczkowych w czasie rzeczywistym, o którym piszemy poniżej) miała dostęp wyłącznie do tych elementów bazy danych w swojej klasie, które są do jej działania konieczne. W przypadku próby uzyskania dostępu do innych elementów system podnosi alarm i dokonywana jest weryfikacja, aby możliwie najwcześniej wyizolować potencjalnie zaatakowany fragment systemu. Ponadto rozproszenie dostępu pomiędzy instancjami pozwoliło określić dla nich limity zapytań, co pozwala automatycznie odciąć taki fragment podsystemu w ciągu sekund od wykrycia niepożądanych zachowań.

Dzięki odpowiedniej granulacji systemu tylko niewielka ilość użytkowników końcowych obsługiwana przez daną instancję jest doświadczona przez blokadę, a mechanizm load balancingu pozwala sprawnie (automatycznie) przenieść ich na inną instancję aplikacji, co można powtarzać tak długo, aż wyizoluje się dostęp atakującego (dzieląc przenoszonych użytkowników na więcej instancji).

W sytuacjach, w których konieczna była komunikacja inicjowana również w stronę pozostałych aplikacji, korzystaliśmy z kilku rozwiązań, w zależności od położenia drugiej aplikacji w infrastrukturze. Pomiędzy aplikacjami, nad którymi mamy wyłączną kontrolę, tj. takimi, które są przez nas administrowane w chmurze Microsoft Azure i o których wiemy, że ich architektura została oparta o właściwe założenia dla tej komunikacji, wykorzystujemy Redisa.

Redis jest wysokowydajnym rodzajem bazy danych przechowujących obiekty składające się z kluczy i wartości, a także systemem masowego broadcastowania. Jest popularny w zastosowaniach jako cache ze względu na bardzo krótki czas potrzebny na dostęp do danych (wielokrotnie szybszy niż tradycyjne bazy danych). Z tych zalet korzystamy w komunikacji między aplikacjami.

Drugi rodzaj sytuacji komunikacyjnej „na zewnątrz” obejmuje przypadki, gdy aplikacja otrzymująca dane z Core’a nie jest aplikacją w pełni zaufaną (tj. kiedy nie możemy zagwarantować, że została przygotowana zgodnie z dobrymi praktykami). Jako takie traktujemy wszystkie aplikacje, które są poza naszą infrastrukturą, np. systemy sieci handlowych, z którymi się integrujemy, a także aplikacje, które mają być całowicie wyodrębnione infrastrukturalnie (np. z powodu wymagań klienta umieszczone na innej chmurze, np. na AWSie). W tych przypadkach przyjęliśmy strategię wysyłania do systemów zewnętrznych zapytań REST API. Przygotowana została dokumentacja Core Callback API (ściśle implementująca standard OpenAPI 3.0), która jest następnie implementowana w systemach zewnętrznych (przez nas lub partnerów, w zależności od układu biznesowego). Do tak zaimplementowanego API system Core wysyła zapytania zawierające payload z odpowiednimi danymi, związanymi z poszczególną operacją.
I tu jednak warto pochylić się nad bezpieczeństwem systemu pod kątem spójnościowym – zapytania do systemów zewnętrznych zawsze należy traktować jako niepewne co do skutków. W związku z tym Core nigdy nie odpytuje tych systemów synchronicznie, a odbywa się to w odrębnym, przydzielonym do odpowiedniej kolejki (ze względu na czas spodziewany) wątku. Ponadto, oczekiwana jest od systemu zewnętrznego odpowiedź zawierająca potwierdzenie przyjęcia do przetworzenia. Jeżeli takiej odpowiedzi nie udaje się uzyskać, lub w ogóle nie udaje się nawiązać komunikacji, to takie zdarzenie jest kolejkowane z pewnym niewielkim opóźnieniem, tak aby jak najbardziej prawdopodobne było, że ostatecznie informację udaje się dostarczyć.

Dobrą praktyką, w przypadku ponawiania komunikacji z systemami, które nie mają wysokiej dostępności, jest stosowanie kolejnych ponowień z rosnącym odstępem czasu. Tj. pierwsze ponowienie wyślemy od razu, drugie po kilku sekundach, kolejne po kilkudziesięciu, i tak dalej.

W zależności od tego, jak wysoka jest dostępność systemu zewnętrznego z założenia, po odpowiednio dużej ilości ponowień (a więc odpowiednio długim czasie braku sukcesu) powiadamiamy administratora, aby mógł zweryfikować powód braku komunikacji.
Szczególnym przypadkiem komunikacji według powyższego schematu jest przypadek, gdy system zewnętrzny jest gotowym systemem, wystawiającym protokół komunikacyjny i oczekującym na ustandaryzowane według swojej dokumentacji komunikaty. W takich sytuacjach implementowaliśmy dodatkowe aplikacje proxy, które z jednej strony dla naszego Core’a funkcjonowały jako system zewnętrzny, a z drugiej otrzymane informacje przekazywały w odpowiedni sposób do faktycznego systemu zewnętrznego. Traktowanie takiego proxy już jako zewnętrznego w stosunku do Core pozwoliło zachować integralność metod komunikacji w obrębie projektu i tym samym zwiększyć niezawodność Core’a.
Warto dodać, że aplikacja Core została zaprojektowana w sposób transakcyjny, umożliwiający jej łatwe duplikowanie i load balancing. Wykorzystując udostępniane przez Microsoft narzędzia w postaci Azure Load Balancer i Azure Network Gateway uzyskaliśmy wysoką dostępność, co było kluczowe dla składnika systemu odpowiadającego za uwspólnianie informacji między pozostałymi podsystemami.

Drugi obszar systemu, szczególnie konieczny dla jego funkcjonowania w charakterze sprawnego narzędzia do sterowania fizycznymi urządzeniami i ich diagnostyki, to aplikacja obsługująca komunikację w czasie rzeczywistym, którą nazwaliśmy Shepherd. Zbudowana w środowisku Node.js i obsługująca komunikację socketową przy użyciu socket.io, umożliwia automatom paczkowym podłączanie się i wymianę dużej ilości drobnych komunikatów o zmianie stanu, z których selekcjonowane są informacje istotne dla sprawnego funkcjonowania systemu, które Shepherd podaje dalej.

Założeniem wynikłym z opisanej wyżej analizy bezpieczeństwa było rozdrobnienie Shepherd’a w taki sposób, aby uodpornić się na ewentualne naruszenia bezpieczeństwa płynące ze strony automatu paczkowego. Jest on urządzeniem polowymi i z tego powodu nigdy nie powinien być traktowany jako w pełni bezpieczny, z uwagi na możliwość przeprowadzenia rozległych ataków inkorporujących fizyczną ingerencję w urządzenie. Musieliśmy tak zaprojektować infrastrukturę związaną z obsługą komunikacji z urządzeniami, aby włamanie do jednego z nich nie naraziło całej sieci na przeciążenie. Rozwiązaniem było uruchamianie wielu instancji aplikacji Shepherd, do których przypisane były małe grupy automatów.

Zwrócić należy uwagę na specyficzną sytuację mającą miejsce przy łączeniu się urządzeń z chmurą. W związku z procesem wdrożeniowym urządzenia korzystają z różnego rodzaju dostępu do Internetu, zarówno po kablu (miedź/światłowód), jak i bezprzewodowo (GSM). Niejednokrotnie (zgodnie z naszą rekomendacją) urządzenia mają więcej niż jeden sposób przyłączenia się do sieci, aby zapobiec utracie połączenia na tym polu. Urządzenie po nawiązaniu połączenia z Internetem loguje się do VPN uruchomionego w oparciu o Azure VPN Gateway i od tego momentu może połączyć się z Shepherdem. Z uwagi na specyfikę pracy z Azure VPN Gateway urządzenia dostają zawsze losowe adresy w prywatnej puli. Te okoliczności powodują, że nawiązanie połączenia musi odbywać się z inicjatywy urządzenia i dopiero taka próba połączenia daje nam możliwość sterowania automatem.

Dobrą praktyką jest aby w przypadku urządzeń, które funkcjonują w terenie i same inicjują połączenie z systemem chmurowym, konfigurować w nich więcej niż jeden zestaw poświadczeń i adresów, do których mają się połączyć. Zapobiega to sytuacji, gdy w razie konieczności przeprowadzenia maintenance chmury wymagającego czasowego wyłączenia choćby jednego z elementów kanału dostępu dla urządzeń, urządzenia przestają mieć możliwość połączenia. Gdy urządzenie ma drugi adres, na który może się połączyć w razie niepowodzenia, pozostawiamy sobie przestrzeń na przerwę w działaniu każdego z elementów infrastruktury, również VPNów czy virtualnych przełączników.

Podłączenie się automatu paczkowego do Shepherda rozpoczyna się od autoryzacji, w której generowany jest czasowy token umożliwiający dalszą komunikację. Preferujemy implementację oAuth 2.0 wszędzie tam, gdzie jest to możliwe. Od momentu prawidłowego pozyskania tokenu rozpoczyna się ciągła komunikacja przez socket.io, która polega na wysyłaniu przez urządzenie wszystkich dostępnych informacji o zdarzeniach. Są to m.in. informacje o stanie zamków, czujników ruchu umieszczonych w skrytkach (dla klientów DIY, gdzie stosowane są skrytki wielkogabarytowe istnieje ryzyko zamknięcia się klienta w środku; monitorujemy to ryzyko i powiadamiamy notyfikacjami na telefonach personel dostępny w pobliżu urządzeń).

Również za pomocą socket.io do urządzenia przekazywane są rozkazy. Najbardziej istotnym z nich jest oczywiście rozkaz otwarcia skrytki (lub jej zamknięcia, w przypadku największych skrytek wyposażonych w pionowe rolety). Aby taki rozkaz dotarł do automatu paczkowego, musi najpierw znaleźć się w Shepherdzie. Służy temu opisana już wcześniej komunikacja przez Redisa, do którego rozkazy docierają z Core’a. Warto zaznaczyć, że w Core każda instancja Shepherda ma oznaczone maszyny, które powinny się z nią skomunikować i na tej podstawie informacje przekazywane przez Redis’y są targetowane do odpowiednich Shepherd’ów.

Każdy rozkaz, który ma dotrzeć do urządzenia ma przypisany unikalny identyfikator, który przekazywany jest na całej drodze tego rozkazu, aż do wysłania impulsu elektrycznego do zamka właściwej skrytki. Następnie otwarcie skrytki jest potwierdzane zdarzeniem zwrotnym, przekazywanym do Shepherda. Dzięki takiemu rozwiązaniu jesteśmy w stanie z całą pewnością potwierdzić, że dana skrytka otworzyła się z powodu operacji zainicjowanej w konkretnym miejscu systemu (nazywamy to „impulsem”), przez konkretną osobę. W ten sposób zapobiegamy ryzyku błędów ludzkich, polegających na tym, że dwóch użytkowników w tym samym czasie próbuje otworzyć tą samą skrytkę. Tylko jednemu z nich (temu, którego „impuls” dotrze do urządzenia pierwszy) potwierdzimy, że udało mu się otworzyć skrytkę. Pozostały użytkownik uzyska stosowny komunikat o problemie. „Impuls” przekazany jest również dalej wraz z zamknięciem skrytki, aby dopełnić cyklu otwórz-zamknij i potwierdzić, że przebiegł on w sposób nienaruszony (np. gdy kilka sekund później zamek zostanie ponownie otwarty przez innego użytkownika, jednak skrytka nie została jeszcze zamknięta po poprzednim otwarciu).

Istotne jest planowanie procesów mających konkurencyjny dostęp do tego samego zasobu w taki sposób, aby zawsze istniały w nich ścieżki awaryjne, na wypadek konfliktu między operacjami wykonanymi przez dwóch konkurujących użytkowników zasobu. Ta zasada ma zastosowanie zarówno w przypadkach szeroko opisywanych interakcji z bazami danych, ale powyższy przypadek pokazuje, że należy rozważać te same zagadnienia w przypadku zasobów fizycznych.

Poza komunikacją dotyczącą skrytek, Shepherd odpowiedzialny jest za utrzymanie stanu połączenia poszczególnych automatów paczkowych i powiadamianie Core’a o występujących problemach z połączeniami. Każde urządzenie przekazuje wybrane parametry życiowe, na podstawie których Shepherd flaguje te urządzenia, którego mogą wymagać szczególnej uwagi administratora. Ważnym elementem tej komunikacji jest również ping-pong, który pozwala oszacować zarówno opóźnienia w komunikacji, jak również długość ewentualnych przerw (które przy komunikacji GSM mogą naturalnie występować). Dane te pozwalają wyznaczyć urządzenia, dla których prawdopodobne jest wystąpienie problemów z komunikacją przy odbiorach zamówień. Takie urządzenia oznaczone zostają do weryfikacji przez serwis w systemie ticketowym.
Trzeci obszar systemu obejmuje aplikacje udostępniane pracownikom partnerów (sieci handlowych). Za pomocą tych aplikacji pracownicy (nazywanych przez nas Partner) obsługują zamówienia, które są składane przez klientów partnera. W tym obszarze system chmurowy musiał być wyjątkowo elastyczny, aby pogodzić potrzeby wielu różnych firm, których procesy dostaw nie pokrywają się ze sobą. Aby uzyskać rozwiązanie, które może być dostosowywane do procesów u poszczególnych klientów, zdecydowaliśmy się oprzeć backend Partner’a na Laravel’u, jednak odchodząc od najbardziej popularnej konwencji zdecydowaliśmy się na budowę modułową.

Laravel Modules to biblioteka, która umożliwia dzielenie aplikacji Laravel’owej na dowolną ilość modułów, w których wyodrębniać można modele, kontrolery, widoki i praktycznie wszystkie inne elementy aplikacji. Projektując aplikcje, które mają być uruchamiane z różnych warunkach i cechować się wymiennością funkcjonalności, projektujemy aplikacje w sposób umożliwiający łatwe odpinanie modułów odpowiedzialnych za poszczególne funkcjonalności. Podobną architekturę ma np. popularny framework sklepowy Magento – warto z tych wzorców czerpać.

W efekcie powstało kilkadziesiąt modułów funkcjonalnych, obejmujących swoim zakresem poszczególne zagadnienia związane z obsługą zamówień, m.in. nadawanie, rezerwacje skrytek, zarządzanie produktami, zarządzanie profilami klientów, generowanie zwrotów, tworzenie raportów, obsługa awaryjnych procesów otwierania i wyjmowania zawartości. Dla każdego tak powstałego modułu jego odpowiednie obszary mające styk z aplikacjami frontendowymi lub innymi systemami (np. aplikacjami preparującymi i propagującymi raporty, które można porównać do wcześniej opisywanego proxy komunikacyjnego dla Core’a) zostały udokumentowane zgodnie ze standardem OpenAPI 3.0.

Istotnym aspektem w sytuacji dużego rozdrobnienia codebase’u aplikacji było przyjęcie odpowiedniej struktury wersjonowania i opisywania dependencies pomiędzy modułami backendowymi oraz między modułami backendowymi a frontendowymi. Przyjęlismy politykę numeracji wersji i utrzymujemy ją w projecie, tak aby nie pozostawiać pola do interpretacji. Do każdej wydanej wersji modułu wprowadzane są numery wersji kompatybilnych modułów zależnych. Ta kompatybilność potwierdzana jest poprzez przeprowadzane testy, o których więcej na końcu niniejszego studium.

Composer to wykorzystywany przez nas menedżer pakietów PHP, który pozwala poradzić sobie z zależnościami nawet w bardzo złożonych projektach. Prawidłowe wersjonowanie składowych modułów aplikacji, a także deklarowanie wzajemnych zależności w aplikacji modułowej pozwala na etapie instalacji na nowej maszynie wyeliminować problemy kompatybilnościowe i doprowadzić do konfiguracji środowiska uruchomieniowego w sposób zaprojektowany (i przetestowany) przez autorów oprogramowania.

Z perspektywy klienta korporacyjnego, który korzysta z naszego systemu chmurowego, ważnym aspektem jest bezpieczeństwo jego danych, w szczególności informacji o zamówieniach oraz danych osobowych pracowników, którzy je obsługują. W związku z tym do aplikacji Partner przypięliśmy odrębne bazy danych (Partner DBs), które przechowują te informacje wrażliwe z perspektywy biznesowej. Dzięki takiemu fizycznemu oddzieleniu danych wrażliwych od pozostałych zapewniamy najwyższy możliwy poziom bezpieczeństwa. Dane pracowników sieci handlowych nigdy nie trafiają poza daną instancję Partner’a przeznaczoną wyłącznie dla tej sieci, co pozwala minimalizować ryzyka zgodnie z RODO i rygorystycznymi wymaganiami klientów korporacyjnych.

Od strony frontendowej do przygotowania UI dla pracowników sieci wykorzystaliśmy framework Vue, który świetnie sprawdza się do podobnej architektury modułowej, którą wdrożyliśmy w ramach backendu Partner’a. Widoki zostały oparte o podział funkcjonalny i za pomocą konfiguracji mogą być dostosowywane w obrębie szczegółów prezentacji wizualnej.

Najważniejszą funkcjonalnością aplikacji Partner jest wkładanie zamówień do automatu paczkowego (wraz z powiązanymi funkcjonalnościami w modułach, np. rezerwacją skrytek). W celu maksymalnego ułatwienia zarządzania urządzeniem przez pracowników, do aplikacji frontentowej został przygotowany model 3D, który pokazuje aktualny stan urządzenia, symulując otwarcia drzwiczek w skrytkach i pokazując za pomocą kolorów zmiany w ich zawartości. Dzięki temu rzut oka wystarczy, aby pracownik oszacował zapełnienie urządzenia i dokonał ewentualnych ręcznych przesunięć (np. przekierować dodatkowe zamówienie z punktu obsługi klienta do automatu paczkowego znajdującego się przed drzwiami sklepu).

Przy czym wyżej opisane funkcjonalności ręcznego wyboru skrytek są uruchamiane na życzenie klienta, lub w przypadku, gdy w systemie nie ma danych o rozmiarach produktów, które znajdują się w zamówieniu. Dla klientów korporacyjnych, którzy parametryzują produkty w swojej ofercie przygotowaliśmy moduł, który pozwala automatycznie obliczać najbardziej optymalną wielkościowo skrytkę pozwalającą na obsłużenie danego zamówienia. Pozwala to zaoszczędzić pracownikom sklepu obsługującego automat paczkowy czas, a także zredukować błędy wynikające ze złego oszacowania przez pracownika wielkości potrzebnej skrytki.
Czwarty podsystem w obrębie opisywanego systemu chmurowego to aplikacja administratora (Admin Panel), pozwalająca na sprawne zarządzanie wszystkimi elementami systemu. Obejmuje swoją funkcjonalnością konfigurowanie systemu na etapie wdrażania nowych automatów paczkowych, nowych Partner’ów oraz instancji Shepherd’ów. Pozwala monitorować stany wszystkich aplikacji oraz stany automatów paczkowych.
Zagadnienie administrowania systemem było od początku projektu traktowane jako priorytetowy przedmiot analizy funkcjonalnej. Ze względu na specyfikę systemu, kolejne automaty paczkowe są dokładane cały czas, co powoduje konieczność przekazania dostępów administracyjnych osobom, które powinny mieć ograniczony dostęp do pozostałych elementów systemu. Z tego powodu zaprojektowaliśmy szczegółowy system uprawnień użytkowników, który pozwala nadawać uprawnienia do poszczególnych operacji w Admin Panel’u, zarówno pod kątem poszczególnych operacji, jak i zakresu dostępnych obiektów w systemie. Dzięki takiemu podejściu możemy przypisać osobom odpowiedzialnym za uruchomienie nowych automatów tylko takie uprawnienia, które pozwalają im utworzyć te urządzenia i przeglądać stan ich połączenia, ale pozostawiają niewidocznym całą resztę możliwych operacji administracyjnych.

Aby sprostać temu zagadnieniu przygotowaliśmy kreator automatu paczkowego, który za pomocą planszy 2D umożliwia osobie niemającej umiejętności informatycznych skonfigurowanie z klocków nowego automatu paczkowego, którego ściany widoczne są na wizualizacji w trakcie układania skrytek na planszy. Użytkownik Admin Panel’u odpowiedzialny za konfigurację nowego automatu widzi jak powinien wyglądać automat, który właśnie konfiguruje i może łatwo wyłapać ewentualne błędy w wybranych funkcjonalnościach, numeracji, wielkościach czy nazewnictwie skrytek oraz modułów urządzenia. Na podstawie konfiguratora graficznego generowana jest konfiguracja wgrywana do automatu paczkowego, po czym urządzenie może już łączyć się z systemem chmurowym (wymagane jest tylko sprawdzenie kolejności podłączenia skrytek, aby zgadzała się z wygenerowaną konfiguracją).

Wszystkie funkcjonalności Admin Panel’u w trakcie analizy określiliśmy jako możliwe do synchronizacji w dłuższym okresach czasu (poza czasem rzeczywistym, w skali sekund lub minut, zależnie od konkretnego procesu obsługiwanego przez panel). W wyniku tej analizy komunikacja między Admin Panel a Core oparta została o REST API, które umożliwia interakcję z Core’m zachowującą odpowiedni poziom separacji między aplikacją obsługującą działania administratorów, a „sercem” projektu. Dzięki temu zachowany został wysoki poziom zabezpieczenia Core’a przed ewentualnymi próbami ataków.

Obszary administracyjne systemów są szczególnie narażone na umożliwianie ataków powodujących rozległe szkody. Wynika to z zakresu dostępów, jakimi dysponują administratorzy, a przez to ilości informacji, które konto administratora może pobierać z bazy. Proste ataki obciążeniowe polegające na wywoływaniu wielu zapytań zajmujących moce obliczeniowe, będą miały najpoważniejsze skutki właśnie wtedy, gdy do ich przeprowadzenia atakujący uzyska poświadczenia administratorów, mające zwykle dostęp do milionów elementów w bazach.

W związku ze specyfiką opisywaną już wyżej przy okazji aplikacji Partner, dla Partner DBs powstała osobna aplikacja administracyjna (Partner Admin). Wyodrębnienie do niej narzędzi administracyjnych związanych z zarządzaniem i diagnozą stanu zamówień sklepowych pozwoliło zachować separację między danymi szczególnie wrażliwymi, a pozostałą częścią systemu. Struktura projektu w tym obszarze jest podobna do Admin Panel – rozległy system uprawnień dla administratorów pozwala oddzielić osoby obsługujące techniczne czynności od danych osobowych pracowników i klientów, zgodnie z zasadą najmniejszej potrzebnej dawki informacji.

Dobrą praktyką w projektowaniu procesów utrzymaniowych w systemach informatycznych jest konfigurowanie dostępów dla personelu w oparciu o zasadę najmniejszej potrzebnej ilości informacji – projektujemy aplikacje w taki sposób, aby można było przydzielać poszczególnym osobom wyłącznie te elementy, które konieczne są do wykonywania ich pracy.

Aby ułatwić administratorom monitorowanie floty automatów paczkowych, zarówno Admin Panel, jak i Partner Admin, zostały wyposażone w dashboardy, na których graficznie pokazane zostały statystyki wykrytych potencjalnych problemów i stanów urządzeń, w podziale na grupy i lokalizacje, pozwalające wyznaczyć takie urządzenia, dla których należy rozważyć prewencyjne interwencje serwisowe. Takie podejście do monitorowania pozwala rozwiązywać większość problemów występujących z automatami paczkowymi zanim problemy te staną się bardzo dotkliwe dla użytkowników końcowych.

Praca nad tak rozległym systemem chmurowym wymaga dobrego podziału na kamienie milowe i ich sprawnej realizacji. Nieodzownym elementem procesu wytwórczego jest testowanie oprogramowania. W tym projekcie zastosowaliśmy zarówno testy automatyczne, jak i testy manualne.
W zakresie testów automatycznych obszaru backendowego, dla każdego interfejsu zostały przygotowane testy PHPUnit, obejmujące zróżnicowane przypadki dla każdego z endpointów. Dodawanie nowych funkcjonalności do systemu produkcyjnego poprzedzane było każdorazowo weryfikacją prawidłowości testów. W obszarach, w których procesy łączyły wiele aplikacji, z powodzeniem wykorzystujemy narzędzie Apache JMeter.

Apache JMeter to narzędzie do projektowania i uruchamiania testów, wychodzące poza pojedynczy protokół. Umożliwia opieranie różnych części projektowanego testu automatycznego na komunikacji z różnymi aplikacjami przy użyciu wielu protokołów. W naszym przypadku konieczne było sprzężenie w testach komunikacji w oparciu o REST API oraz Socket.io, co jest możliwe w JMeterze przy użyciu samplerów do WebSocketów (na których Socket.io jest oparte).

Do testów manualnych systemu, którego istotnym elementem jest komunikacja z urządzeniami, przygotowana została aplikacja Simulator. Imituje ona całość protokołów komunikacji automatu paczkowego i pozwala w przystępnym, graficznym środowisku wyobrazić sobie interakcję z fizycznym urządzeniem. Simulator tak jak fizyczny automat generuje zdarzenia, otwiera i zamyka skrytki (pokazując to użytkownikowi na modelu 3D urządzenia).
W związku z potrzebą prezentowania klientom korporacyjnym nowych rozwiązań procesowych w przypadku optymalizacji procesów spedycyjnych w ich implementacjach, aplikacja Simulator obok narzędzia dla developerów stanowi również element środowiska prezentacyjnego, które umożliwia pokazywanie proponowanych nowych funkcjonalności bez konieczności podłączania systemu do fizycznego urządzenia testowego, lub skracania prezentacji o jej najistotniejszy obszar – interakcji z użytkownikiem końcowym, który z automatu ma korzystać.
Nie mniej istotne od prawidłowego zaprojektowania aplikacji wchodzących w skład systemu jest również zaplanowanie jego właściwego deploymentu w oparciu o dostępne zasoby chmurowe. Wiedząc, że opisywane środowisko będzie przedmiotem ciągłego doskonalenia i ewolucji, wybraliśmy Azure DevOps jako podstawowe narzędzie kontroli nad wdrażaniem wszystkich wchodzących w skład systemu aplikacji. Przygotowane pipeline’y pozwalają wyeliminować błąd ludzki z procesu aktualizacyjnego, co przekłada się na stałą, wysoką jakość wdrażania zmian w systemie.

Co zdecydowało o sukcesie projektu?

1. Modułowa budowa

Dzięki podejściu modułowemu możliwe było implementowanie różnych przepływów procesu spedycyjnego dla różnych sieci, przy zachowaniu tych samych elementów aplikacji

2. Oparcie na sprawdzonej chmurze

Wybór Microsoft Azure jako chmury, na której uruchomimy zasoby projektu był kluczowym elementem strategii umożliwiającej szybkie i niezawodne skalowanie wdrożeń. Nowe funkcjonalności, niezależnie jak bardzo wymagające obliczeniowo mogły być wdrażane produkcyjnie niemal bez downtime’u (np. własny model algorytmu plecakowego rozwiązujący problem upakowania towaru w 3D, z uwzględnieniem wagi i specyfiki materiałów opakowaniowych).

3. Separacja aplikacji między klientami

Wyobraź sobie, że obsługujesz jedną usługą dwóch klientów, którzy są dla siebie bezpośrednimi konkurentami – takie sytuacje są częstym problemem handlu, szczególnie branży DIY, gdzie graczy jest niewielu. Zaprojektowaliśmy system w sposób gwarantujący pełną izolację zasobów bazodanowych, tak aby nawet w przypadku błędów lub ataku hakerskiego dane każdego z klientów były bezpieczne i niemożliwe było ich wymieszanie. Osiągnięto to poprzez podzielenie systemu na wiele aplikacji, z których kluczowe komunikują się ze sobą z zachowaniem zasady ograniczonego zaufania i szczegółowego ograniczania uprawnień.

4. Dobre planowanie utrzymania

Kluczowe dla projektu było również dobre zaprojektowanie procesów utrzymania. Sytuacje, w których klienci nie są w stanie odebrać zamówień wielkogabarytowych, takich jak kanapa czy paleta cementu, wymagają traktowania każdej przesyłki z wysoką dbałością. Sprawny system zgłaszania problemów w każdej z aplikacji, z których korzystali klienci i pracownicy sieci handlowych i półautomatyczna weryfikacja zgłoszeń pozwoliły skrócić czas obsługi problemu, tak aby klient nie odszedł od automatu z pustymi rękoma.

Dzięki powodzeniu tej realizacji otrzymaliśmy od klienta mandat zaufania, który objawia się do dziś w postaci umowy serwisowej, w ramach której utrzymujemy i aktualizujemy opisany system chmurowy do obsługi automatów paczkowych. Ponadto zlecony nam został przez tego klienta kolejny system, który opiszemy na naszej stronie w przyszłości.
Znamy klucz do efektywnej współpracy
Zrób to z Addictive!
KONTAKT
Addictive Group Sp. z o. o.
al. Józefa Piłsudskiego 92
92-202 Łódź, Polska
NIP (TAX ID): 725 229 82 15
POLITYKA PRYWATNOŚCI
KONTAKT: HELLO@ADDICTIVE.PL
Addictive 2024. All rights reserved