Nasza strona używa cookies. Dowiedz się więcej o celu ich używania i zmianie ustawień w przeglądarce. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Jak tworzyć i utrzymywać bibliotekę mobilną (SDK)?

Arkadiusz Pachucy Senior Software Developer
Zobacz konkretne case study, w który celem klienta był rozwój i utrzymanie biblioteki mobilnej ułatwiającej płatności na rynku polskim.
Jak tworzyć i utrzymywać bibliotekę mobilną (SDK)?

Chyba każdy z nas w swoim życiu związanym z branżą IT napisał przynajmniej jedną prostą aplikację - żeby zobaczyć, jak to jest być programistą. Z czasem proste aplikacje pozwalają, tak jak w grach przygodowych, zdobyć wystarczającą liczbę doświadczenia, aby spróbować rozwiązać bardziej złożone problemy. W moim przypadku jest to przygoda ze współtworzeniem i utrzymaniem bibliotek mobilnych. Artykuł ten rozpoczyna cykl związany z ich wspieraniem.

Około trzy lata temu jeden z klientów poprosił nas o pomoc w wsparciu biblioteki mobilnej. Chodziło o rozwój i utrzymanie biblioteki ułatwiającej płatności na rynku polskim. W tamtych czasach biblioteki były napisane natywnie na trzy platformy: Android, iOS, Windows.

Nowe wzorce, niezmienne wyzwania

Pierwsza biblioteka była świetnie napisana i wykorzystywała ówczesne technologie w stu procentach. Jednak wiadomo, że czas pozwala na odkrycie nowych rozwiązań, wzorców i technologii. Dzisiejszy wzorzec może być jutrzejszym antywzorcem. Podczas pracy nad utrzymaniem biblioteki zauważyłem jednak, że niektóre wyzwania są niezmienne i niezależne od zadania, nad którym się aktualnie pracuje - to bardziej abstrakcyjne pojęcia.

  • Cele - zarówno te długo-, jaki i krótkoterminowe (mind mapa), dobrze zsynchronizowany rozwój oraz wdrażany produkt to podstawa utrzymania przy sobie klientów, jak i przyciągnięcia nowych.
  • Narzędzia - dobór odpowiednich narzędzi i bibliotek zawsze będzie miał znaczenie. Zwłaszcza, że nigdy nie wiadomo, jakich narzędzi będzie używał programista integrujący się z biblioteką (stolarz za pomocą pędzla niewiele zdziała). Tutaj zalecam skorzystanie ze stabilnych rozwiązań oraz zastanowienie się, czy na pewno potrzebujemy do tego biblioteki. Sam Android do niedawna był bardzo restrykcyjny co do liczby metod w aplikacji. 65536 metod - z jednej strony to bardzo dużo, a z drugiej wykorzystanie kilku bibliotek, takich jak Guava albo Google Play Services, bardzo szybko może wyczerpać wspomniany zasób. Wiadomo, że zewnętrzna biblioteka może zrobić coś o wiele szybciej i lepiej. Jednak czy na pewno potrzebowaliśmy tej funkcjonalności kosztem takich zasobów? Obecnie większe aplikacje mobilne mają uruchomionego multidexa, który rozwiązuje problem wspomnianego ograniczenia, dzięki czemu aplikacja może się kompilować. Nie rozwiązuje to jednak problemu związanego z jej nadmierną wielkością.
  • Decyzyjność - podczas rozwoju zarówno aplikacji, jak i biblioteki trzeba sobie zadać pytanie: Po czyjej stronie stoi dana funkcjonalność - jestczęścią backendową, a frondtend ma ją tylko zaprezentować, czy jednak to na frontend przerzucamy odpowiedzialność?
  • Koszt integracji - czyli minimalna liczba zadań, jakie musi wykonać programista integrujący się z rozwiązaniem. Chodzi zarówno o stworzoną dokumentację, która w szybki sposób pozwoli na wykorzystanie biblioteki w aplikacji, jednoznaczne nazewnictwo metod oraz o jasne komunikaty błędów, które nie pozostawią programistom pola na domysły.
  • Koszt wdrożenia nowej wersji
    • Koszt wydania biblioteki - związany również z punktem Narzędzia. To od nich w dużej mierze zależy, ile czasu zajmie stworzenie nowej wersji biblioteki (CI/CD). Wydanie nowej wersji to nie tylko dodanie do repozytorium nowych plików binarnych oraz changeloga, ale również poinformowanie osób już zintegrowanych o tym, że aktualizacja jest dostępna.
    • Koszt integracji z nową iteracją biblioteki - jest to koszt, który ponoszą osoby, które zintegrowały się z istniejącym rozwiązaniem. Bardzo ważne jest to, aby zmiany, które są wprowadzane do nowych rozwiązań, były jak najmniej inwazyjne dla programistów wykorzystujących już obecne rozwiązanie lub aby mieli oni „chwilę" na przesiadkę.


Być może jest coś więcej? Zapewne tak i w dużej mierze pozostałe priorytety będą zależały od roli, jaką pełni się podczas tworzenia takiej biblioteki.

Cele oraz decyzyjność są bardzo ważne z punktu widzenia projektu. W „Sztuce Wojny” - starożytnym chińskim traktacie filozofa Sun Tzu - oddanie inicjatywy jest najprostszą drogą do zatracenia się. Rynek e-commerce, z którym związana jest biblioteka, rozwija się w Polsce bardzo intensywnie. Konkurencja nie śpi i stara się nie tylko podążać krok za wprowadzanymi przez nas rozwiązaniami, ale również narzucać niektóre trendy. W związku z tym nieraz zadawaliśmy sobie pytania, dokąd powinna zmierzać biblioteka oraz badaliśmy czy nowa funkcjonalność, którą wprowadziliśmy, sprawdzi się wśród naszych odbiorców.

Kto pyta, sam nie błądzi

Bardzo często podczas tworzenia nowej funkcjonalności, jak również w fazie łatania problemów, zadawaliśmy sobie pytania, które pozwoliły określić pewien kierunek działania:

  • Jak dostosować bibliotekę do szybko zmieniających się trendów na urządzeniach mobilnych?
  • Jak dostosować bibliotekę do szybko zmieniających się trendów w e-commerce?
  • Czy nowa funkcjonalność powinna zostać dodana do backendu, czy też do części mobilnej?
  • Czy nie wykorzystujemy za dużej liczby metod w systemie Android?
  • Kiedy możemy wygasić odpowiednią wersję biblioteki?
  • Czy osoba wykorzystująca nasze rozwiązanie jest w stanie się z nim zintegrować w przejrzysty sposób?
  • Czy w związku z nową funkcjonalnością powinna powstać nowa biblioteka (która będzie rozwiązywała tylko ten konkretny problem) i czy to znaczy, że powinniśmy utrzymywać kolejne rozwiązanie?
  • Czy nowa funkcjonalność jest konfigurowalną opcją, czy jest czymś wymuszonym, na co nie ma wpływu nikt spoza biblioteki?

 

Pytania te dotyczą rozwoju rozwiązania, które już istnieje na rynku. Wiemy, że jest to biblioteka wspierająca e-commerce, nadszedł więc czas na przybliżenie obecnego rozwiązania z punktu widzenia klienta.

Jak działa obecne rozwiązanie?

Wyobraźmy sobie, że chcielibyśmy otworzyć sklep (najlepiej internetowy), który pozwoli nam zarobić pierwszy milion. Potrzebujemy backendu, żeby przechowywać informacje o produktach oraz jakiegoś frontendu (np. strony www lub aplikacji mobilnej), który pozwoli nam dotrzeć do użytkowników końcowych.

W przypadku aplikacji mobilnej można zintegrować się z biblioteką, którą wspieramy, i dodać mały pasek wyboru metod płatności na ekranie podsumowania płatności oraz przycisk Płacę. Dzięki niej użytkownik w łatwy sposób przejdzie proces płatności.

Biblioteka komunikuje się bezpośrednio z dedykowanymi endpointami. Zaletą takiego podejścia jest odciążenie potencjalnego programisty od pisania zapytań do warstwy sieciowej (proces powiela się dla różnych osób chcących się z nią zintegrować). Biblioteka bierze na siebie nie tylko komunikację z backendem, ale również przetwarzanie informacji od backendu w celu podania użytkownikom oraz developerom „jednoznacznych“ komunikatów, które otrzymaliśmy od serwera.

Nasze rozwiązanie jednoznacznie determinowało zarówno przepływ, jak i branding. Narzuciliśmy pewien schemat, którego użytkownik biblioteki nie mógł w żaden sposób zmodyfikować (generalizacja rozwiązania w celu uniknięcia problemów związanych z konfiguracją).

Firmy wykorzystujące nasze rozwiązanie dawały nam różny feedback odnośnie biblioteki - nierzadko wzajemnie sprzeczny. Niektórzy klienci cieszyli się, że po wyborze płatności widać logo oraz branding poważnej instytucji wspierającej proces transakcji, zdarzali się też ci bardziej wymagający, którzy pragnęli dostosować rozwiązanie do swoich potrzeb.

Programiści wykorzystujący nasze rozwiązania w celu stworzenia aplikacji mobilnej korzystali również z innych bibliotek. Pisali aplikacje w różnych językach przy wykorzystaniu różnych wzorców oraz niejednego SDK. W następnym punkcie powiem, z czego my korzystaliśmy.

Proces/narzędzia

Projekt biblioteki został podzielony na wiele modułów, które były odpowiedzialne tylko i wyłącznie za jedna konkretną rzecz, np. moduł warstwy sieciowej. Większość z modułów to moduły javowe, wspólnie pakowane za pomocą Mavena do uber-jara z shadowami najważniejszych bibliotek dostawców trzecich. Celem shadowów jest rozwiązanie problemu zależności od biblioteki warstwy sieciowej dostarczonej przez programistę integrującego się z naszym rozwiązaniem oraz uniknięcie potencjalnych konfliktów pomiędzy wersjami. Dodatkowo wypuszczamy drugą część biblioteki w postaci pliku aar (czyli biblioteki, która poza kodem zawiera również zasoby związane z Androidem - graficzne/tekstowe w katalogu res). Dodawanie tylko dwóch zależności przez programistę jest czytelne i szybkie.

Wykorzystanie Mavena wraz z obecną konfiguracją z czasem stało się coraz bardziej wymagające. Wiele bibliotek zależnych, które wykorzystywaliśmy, z czasem zmieniło rozszerzenia z jar na aar (np. biblioteki supportowe Androida), co wpływa na typ modułów (moduły javowe nie mogą mieć zależności aar’owych - mają problemy z innymi plikami niż klasy javowe, np. z zasobami graficznymi). Tworzenie uber aar’ow nie jest wspierane out-of-the-box przez Mavena, podobnie zresztą jak sam Android.

Maven udostępnia „profile”, z których do niedawna w pełni korzystywaliśmy, łącząc różne konfiguracje wersji bibliotek (produkcyjna/snapshot/debug) z potencjalnymi odbiorcami (klient wewnętrzny/zewnętrzny). Siatka takich zależności wraz z dodaniem kolejnych property może zrobić się bardzo skomplikowana, co zmniejsza czytelność kodu, jak i utrudnia utrzymanie produktu. W gradle są odpowiedniki wspomnianych profili.


Poza wspomnianymi wyzwaniami związanymi z budowaniem biblioteki jest jeszcze inne - związane z wykorzystaniem bibliotek zewnętrznych.

Dobrym przykładem jest Dagger. Biblioteka ta dynamicznie generuje kod. W momencie, kiedy aplikacja klienta wykorzystywała Daggera w wersji niekompatybilnej z Daggerem, który był częścią naszego SDK, generowany kod potrafił doprowadzić do crashu aplikacji lub nie pozwalał na poprawną kompilację, wyświetlając ciężkie do zrozumienia błędy.

Ostatnia klasa problemów wiąże się bezpośrednio z systemem, na którym ma działać SDK. Do niedawna biblioteka była dostępna od wersji Gingerbread (2.3). Powodowało to komplikacje związane z działaniem naszego SDK. Część błędów była poprawiana tylko na wyższych wersjach Androida, przez co niejeden raz musieliśmy opracować workaround dla konkretnej wersji platformy. Dopiero od niedawna - ze względu na podniesienie poziomu bezpieczeństwa - postanowiliśmy porzucić wsparcie dla starszych wersji Androida.

Wspomniane narzędzia pozwalają nam na rozwój oraz tworzenie plików binarnych biblioteki, jednak najważniejsza jest ich dystrybucja. To dzięki niej nasi użytkownicy uzyskują nowe wersje SDK.

Repozytoria biblioteki

Posiadamy dwa repozytoria biblioteki. Jedno na użytek wewnętrzny - artifactory - wykorzystywane przez naszego czołowego (wewnętrznego) klienta oraz drugie, „otwarte” dla wszystkich - znajduje się na GitHubie.

Pierwsze repozytorium populowane jest za pomocą bamboo. CI skonfigurowaliśmy tak, aby przygotowywał wersję snapshot, o ile w przeciągu ostatnich 24h dodaliśmy commita do głównego repozytorium. Wersję release tworzymy, uruchamiając ręcznie inne zadanie.

Biorąc pod uwagę rozwój związany z publikowaniem artefaktów rozważamy migrację z GitHuba na jCenter(). Na GitHubie - poza przechowywaniem aktualnych wersji bibliotek w sztucznym repozytorium mavenowym - przechowujemy również dokumenty pomagające w integracji (również je wersjonujemy). W przypadku release'u do GitHuba wykonujemy go ręcznie. Z upływem czasu zauważyliśmy, że powinniśmy przechowywać tylko ostatnią wersję. Mnogość wyboru dokumentacji może konfundować potencjalnych odbiorców, z jaką wersją się integrować. GitHub ma również jedną wadę - na te rozwiązanie coraz częściej przeprowadzany jest atak typu DDoS. Nie uważam przejęcia przez Microsoft za wadę ;)

GitHub jako repozytorium zawiera aplikację demo. Umożliwia ona podejrzenie, jak można zintegrować się z biblioteką oraz FAQ, które rozwijamy.

Propagacja wersji, czyli aktualizacja

Wypuszczenie nowszej wersji biblioteki to dopiero początek, który czasem może się okazać i końcem biblioteki. Co z tego, że wypuścimy nowe funkcje, jeżeli nie będą one przez nikogo wykorzystywane?

Po wypuszczeniu bibliotek przekazujemy informację wewnątrz firmy oraz do klientów już wykorzystujących rozwiązanie, aby mogli przygotować się w najbliższym procesie wypuszczenia aplikacji do użytkowników końcowych i wprowadzić niezbędne poprawki.

Niestety, ale informacje odnośnie „nowszej” wersji często są „parkowane”. W końcu to tylko biblioteka - taka, która służy do płatności mobilnych, więc niejako odpowiedzialna za przychód naszego klienta.

Upraszając tok rozumowania - nowa wersja biblioteki wymusza nowe wersje aplikacji (pętla zwrotna -> zgłaszanie problemów z integracją). Niestety, ale nie każdy użytkownik ma włączone autoaktualizacje, przez co część użytkowników zostaje „z tyłu”. Dopiero po miesiącu najnowszą wersję aplikacji na Google Play posiada powyżej 70% użytkowników. W związku z tym przerwy między nowymi wersjami biblioteki są nieco większe.

Podsumowanie

Część pierwsza cyklu miała za zadanie przybliżyć czytelnikowi najważniejsze pytania związane z tworzeniem i utrzymywaniem biblioteki mobilnej. Nie jest to jakaś obca biblioteka, gdyż rozwiązuje jeden z ważniejszych problemów na urządzeniach, związany z zakupem towarów oraz usług.

W następnej części poruszę temat kilku faux-pas, które są zawsze wyzwaniami dla programistów oraz podsumuję wady i zalety istniejącego rozwiązania.

Zobacz więcej na Bulldogjob

Masz coś do powiedzenia?

Podziel się tym z 80 tysiącami naszych czytelników

Dowiedz się więcej
Rocket dog