Buduję aplikację do obstawiania wyników MŚ. Część 2
W części pierwszej zbudowałem strukturę bazy danych oraz interfejs.
Backend Workflows
Kluczową funkcją aplikacji jest liczenie wyników. Do tego używam backend workflow iterujący po wszystkich wpisach z typami (Bets) wykonujący serie kroków które sprawdzają sytuacje i przypisują punkty do danego wpisu.
Tabele z meczami - Match - mam z polami Host i Guest (pierwsza i druga drużyna). W wypadku wygranej gospodarza sprawdzam, czy Typowanie też obstawiało wygraną gospodarza. Only when... istnieje taki mecz (dla sprawdzanego Typu) w którym Gospodarz zdobył więcej punktów niż Gość. Drugim warunkiem koniecznym jest sprawdzenie, czy Typ też zakładał, że Gospodarz ma więcej punktów niż Gość.
Ważne by ostatnim warunkiem było "Idealne trafienie". Ono jest logicznie prostsze - szukam tylko takiego meczu który ma wynik równy parametrom ze sprawdzanego Typu
Dodanie dodatkowego pola "Debug Step" to trik który pozwala łatwo sprawdzać w złożonych konstrukcjach które dane są dotykane przez Workflow. W bardziej skomplikowanych przypadkach zamiast cyfr możemy dodawać litery - dzięki temu będziemy dokładnie wiedzieli który krok spełnił warunek i dopisał wartość do bazy.
Lista z punktacją
Do tej pory wyświetlałem wyniki danej kolejki poprzez Repating Group liczący wszystkie Bets w danej Lidze dla danego Meczu. Było to wygodne, nie musiałem tworzyć nowego bytu - wynik był w "Typowaniu" - więc łatwo było zachować integralność danych.
Gdy jednak trzeba było zrobić tablice wyników globalnych uznałem, że sumowanie wszystkich Bets nie będzie dobrym rozwiązaniem. Miałem problem z sortowaniem takich wyników i chciałem też dodać dodatkowe informacje do tabeli. Dlatego zdecydowałem się na nowy Data Type - Scoreboard - czyli listę osób z informacją ile mają punktów, w ilu rundach obstawiali oraz jaki success rate mają w obstawianiu.
Początkowo zrobiłem to iterując po Tabeli Users - po chwili testów dotarło do mnie, że taka architektura nie uwzględnia sytuacji w której jeden użytkownik jest w paru Ligach. Zmiana na iterowanie po Membership rozwiązała problem. Przypomnę, Membership to lista mapująca User - Liga i zastanawiałem się czy to mapowanie zrobić jako Pole "List of Leagues" w Tabeli User czy właśnie jako odręby Data Type - i wydzielenie tego teraz zaprocentowało.
Bubble wykonując operacje (plus item, minus item) na liście kasuje z niej duplikaty. Pierwotnie zakładałem, że będę przesyłał liste Users w której mogą być wielokrotnie ci sami Użytkownicy (bo należa do różnych Lig). Dlatego pobrałem wtyczkę List Popper która zapobiega temu i jest bardzo wygodna w iterowaniu list.
Ostatecznie wykorzystanie Membership sprawiło, że List Popper nie był potrzebny ale zostawiłem go bo jest to wygodne narzędzie.
Następnym ważnym elementem wykrytym w testach była konieczność dodania Constraint = Liga, by zachować multitenantowość.
Generalnie wielotenantowość w tej aplikacji - rozumiana jako możliwość uczestniczenia w wielu ligach przez jednego użytkownika - myślę, że zabrała mi połowę czasu całego developmentu.
Mniej więcej w tym momencie pokazałem koledze efekty pracy - był bardzo zadowolony, ale powiedział, żebym zmienił nazewnictwo Team na League. Dlatego w tym tekście już tak opisuje zespoł ludzi który obstawiają.
Logowanie do różnych Lig
Zakładałem, że szef ligi będzie miał link od rejestracji dla graczy. Ludzie wchodzą na link i rejestrują się - a ja automatycznie dopisuje ich do danej ligi. Proste.
Okazało się, że to był największy koszmar. Ale od początku. Link tworzyłem poprzez wygenerowanie Slug dla danej Ligi. Wchodząc na stronę logowania z parametrem &i=moja-liga
https://funbetswithfriends.com/?p=signup&i=moja-liga
wkładałem to do Custom State a potem przy rejestracji uzupełniałem odpowiednie pola. Przy logowaniu to pole ignorowałem. Po zalogowaniu sprawdzałem czy Użytkownik jest już w takiej lidze, jeśli nie - wyświetlałem mu Onboarding w którym dodawał swoją nazwę i awatar.
Ale część ludzi rejestrowała się nie przez link z parametrem - musiałem dodać sposób by w automatyczny sposób ich dodać do ligi. Więc dodałem ten flow też przy logowaniu. Generalnie było sporo zamieszania a w systemie zaczęli pojawiać się już użytkownicy.
Z perspektywy czasu logikę zrobiłbym inaczej - tworzyłbym wpis w odrębnej tabeli - Invites - w której mapowałbym User - Liga oraz informacja czy zostało zaproszenie przyjęte. Nie zdecydowałem się na szybkie wdrażanie tego bo do turnieju było już bardzo mało czasu a chciałem dodać jeszcze parę funkcji poprawiających czytelność i bezpieczeństwo aplikacji (np blokada edycji typowania po rozpoczęciu meczu, pokazanie typowań pozostałych graczy po rozpoczęciu meczu).
Dobę przed pierwszy gwizdkiem dostałem taką wiadomość...
Prosta rzecz. Ale zupełnie rozbiła mi moją elegancką architekturę w której każde Typowanie zawiera komplet informacji. Czy powinienem stworzyć odrębny Data Type "Champion Bet"? Może. Wtedy musiałbym modyfikować cała logikę liczenia punktów. Zdecydowałem się dodać do Bets pole "Bet Category" które może być albo match albo champion. Stworzyłem prosty Workflow który edytował istniejące Typowania (bo system miał już produkcyjnych użytkowników którzy zaczęli obstawiać mecze).
Na ostatniej prostej zajmowałem się poprawianiem flow logowania oraz panelem zarządcy ligi. Dodałem opisy, opcje dodania regulaminu ligi oraz listę użytkowników. Domyślnie użytkownik był nieaktywny a zarządca musiał go aktywować - często w takich zabawach jest jakieś "składkowe".
Ostatnimi zmianami było dodanie listy zmian i powiadamiania mailem użytkowników o aktywacji konta.
Wrap up
Podsumowując poświęciłem na aplikację około 20 godzin - budowanie i dodawanie funkcji było wciągające, pewnie gdybym miał więcej czasu i dobiłbym do 40 godzin* ale po starcie stwierdziłem, że będę wrzucał tylko krytyczne poprawki. I miałem takowe - np w moim Workflow liczącym punkty nie liczyłem poprawnie remisów które nie były dokładnie takie jak typowanie (czyli mecz 2:2 a typowanie 1:1). A, i dodałem opcje resetowania hasła bo kompletnie o tym zapomniałem wcześniej :)
Bubble jest niesamowitym narzędziem w którym można naprawdę szybko budować.
* - na przykład dziś naszła mnie nieodparta pokusa by ostylować tablicę wyników dodając trzy pucharki odpowiednio dla pierwszego, drugiego i trzeciego miejsca oraz zaznaczyć kolorem wiersz użytkownika który ogląda tablicę. Ale obiecałem sobie, że najpierw napiszę ten tekst.