piątek, 21 lipca 2017

Elm - frontend, który po prostu działa

Od dłuższego czasu pracuję nad aplikacją InkassoTrade. Projekt jest już uruchomiony i udostępniony publicznie więc mogę opisać nieco technologiczne jego aspekty.

InkassoTrade

Aplikacja skierowana jest głównie do firm mających problemy ze ściąganiem należności. Umożliwia (całkowicie za darmo!) wysyłanie monitów mailowych przy użyciu nieprzeciętnie zaawansowanego mechanizmu reguł. Pozwala też ona sprawnie zarządzać fakturami i wpłatami, a monity można dowolnie dostosowywać do swoich potrzeb.

Już praktycznie na samym początku podjęliśmy decyzję by interfejs pisać w Elmie. Miałem wcześniej już sporo doświadczenia pisania w tym języku ale zdecydowanie jest to mój największy projekt. Mimo, że czasem nie było różowo to w żadnym frameworku JS typu React czy Angular nie byłbym w stanie ukończyć frontendu w takim czasie. Jednak funkcyjny język i to statycznie typowany pozwala uniknąć wielu problemów.

Garść technicznych informacji

Na początek parę informacji o rozmiarze projektu:

  • Liczba linii kodu: 21286
  • Liczba bibliotek zewnętrznych: 15
  • Liczba portów: 2
  • Liczba linii kodu portów: 425
  • Rozmiar skompilowanego kodu (zminimalizowany i spakowany): 99,6 KB

O Elmie

Dla osób nie znających Elma powyższe dane mogą być nieco niezrozumiałe. Szczególnie jeśli chodzi o porty. Elm oprócz bycia funkcyjnym i statycznie typowanym językiem umożliwia podobnie jak Haskell kontrolowanie efektów. Oznacza to, że operacje takie jak pobranie aktualnej daty, wysłanie żądania HTTP czy wykonanie zewnętrznego kodu JavaScript nie da się wykonać bezpośrednio. Odbywa się to w zupełnie innym trybie, przypomina to trochę programowanie asynchroniczne w node.js. W Elmie każda aplikacja dzieli się na trzy podstawowe części:

  1. model - struktura, w której trzymany jest stan aplikacji
  2. view - funkcja, która zwraca kod HTML na podstawie modelu
  3. update - funkcja, która na podstawie akcji/zdarzenia i aktualnego modelu zwraca nowy model (aktualizuje go)

Dla przykładu powiedzmy, że mamy model, który zawiera liczbę kliknięć w jakiś przycisk. Mamy też widok, który rysuje go oraz obok wyświetla licznik kliknięć. Widok oprócz generowania samego kodu HTML "nazywa" także akcję, która ma się wykonać po kliknięciu. Natomiast funkcja update reaguje na daną akcję zwiększając licznik w modelu. Poniższy diagram sekwencji przedstawia ogólną zasadę działania:


Obrazek pochodzi ze strony elmprogramming.com, którą polecam przejrzeć podczas nauki tego języka.

Jeżeli czegoś nie można w danej chwili zrobić w Elmie to zawsze można wykorzystać do tego JavaScript. Taki kod może przesyłać dane do aplikacji przez tzw. port lub może odbierać przez niego dane wysyłane przez aplikację. Jak na takiej wielkości projekt to liczba portów jest niewielka. Wychodzi więc na to, że bardzo dużo rzeczy da się zrobić bezpośrednio w Elmie.

Dobre strony

Zacznę od tych dobrych stron, gdyż jest ich znacznie więcej. Oczywiście większość z nich wynika z samej natury funkcyjnych i statycznie typowanych języków. Jednakże Elm jest unikatowy w paru kwestiach:

  1. Wbudowany debugger - z racji tego, że wszystkie efekty wykonywane są spoza kodu aplikacji możemy ich historię wywołań przeglądać we wbudowanym debuggerze. Wystarczy tylko skompilować kod z opcją --debug i możemy przeglądać jak zmieniał się model aplikacji po każdej akcji. Dodatkowo tester, gdy napotka błąd może taką historię z debuggera wyeksportować i wysłać programiście. Programista wówczas ma ułatwione zadanie bo widzi co dokładnie tester robił i jak zachowywała się aplikacja.
  2. Virtual DOM - nie jest to nic specyficznego tylko dla Elma. Vritual DOM wykorzystuje także np. React ale dzięki niemu Elm jest naprawdę szybki. Nie skupialiśmy się w tej chwili w ogóle na wydajności interfejsu i pisaliśmy go w najprostszy możliwy sposób. Mimo to działa on całkiem sprawnie. Jest to doświadczenie dokładnie odwrotne do tego, które miałem przy pisaniu interfejsów w React'cie czy w ClojureScript'cie.
  3. Refactoring - kod tych rozmiarów co jakiś czas musiał przechodzić refactoring. Jest to normalne, gdyż z czasem jak nasza aplikacja rośnie widać coraz więcej schematów w kodzie, które można uogólnić. Kompilator Elma jest tu nieodłącznym przyjacielem programisty. Dzięki niemu nie tylko nie zapomnimy o żadnym miejscu do poprawy ale przede wszystkim komunikaty jakie zwraca są bardzo przyjemne.
  4. Separacja kodu - każdy programista webowy dobrze zna model MVC. W Elmie jest to oczywiście daleko bardziej posunięte ale przede wszystkim wymuszone. Programista nie może zmienić modelu w widoku, ani też zmienić widoku w funkcji update. Pozwala to uniknąć wielu problemów oraz kod wydaje się być bardziej przejrzysty.
  5. Formatowanie kodu - w tej kwestii Elm przypomina język Go. Dostępne jest świetne narzędzie o nazwie elm-format, które formatuje kod za ciebie. Jest to bardzo wygodne, gdy nie trzeba się zastanawiać ani później poprawiać formatowania.

Mniej przyjemne strony

Pisząć w Elmie natknęliśmy się również na parę trudności. By być uczciwym opiszę je pokrótce. Nie wpłynęły one specjalnie na całokształt projektu ale gdyby ich nie było to oczywiście zakończyłby się on szybciej:

  1. Brak wsparcia dla wysyłania plików - jest to znany problemy i programiści obchodzą go na różne sposoby. Elm potrafi przesyłać dowolne dane oprócz właśnie danych binarnych. Niektórzy obchodzą to przez kodowanie danych w base64 ale wymaga to również obsługi po stronie backendu. My zamiast tego rozwiązania postanowiliśmy wysyłać formularz w tle jako mulitpart/form-data. Tak czy siak obsługa plików wymaga napisania portu w JavaScript'cie, który się tym zajmie.
  2. Widgety/komponenty - najgorętszy temat na wszystkich forach i grupach dyskusyjnych dot. Elma. Nie ma jednej metody na ich definiowanie. Ba, nawet nie jest zalecane by wszystkie widgety tworzyć "na jedno kopyto". To nie jest jednak największy problem. Jest nim brak bibliotek z widgetami. Praktycznie każdą bardziej zaawansowaną tabelkę (z sortowaniem czy filtrowaniem) trzeba pisać od zera. Nie jest to oczywiście dużo roboty ale trzeba na nią poświęcić czas.
  3. Czas kompilacji - jak plików źródłowych przybywa czas potrafi się nieco wydłużyć. Szczególnie jak zmienimy plik, od którego zależą wszystkie moduły. Nie jest to jakoś szczególnie długi czas, w naszym przypadku kompilowanie kodu po raz pierwszy trwa ok. 1 minuty. Podobno planowana nowa wersja Elma ma ten proces znacząco przyspieszyć.
  4. Metaprogramowanie - przy coraz większym kodzie bardzo często chcielibyśmy aby pewne rzeczy działy się automatycznie. Dla przykładu, gdy mamy zdefiniowany typ menu, który może być stroną główną, listą faktur lub listą wpłat to bardzo często definiujemy sobie listę wszystkich możliwych kombinacji. Niestety Elm bez możliwości generowania kodu nie zadba o to by przy każdym dodaniu nowego menu funkcja zwracająca listę wszystkich menu się sama aktualizowała. Kompilator również tutaj nie pomoże w żaden sposób (np. nie wyświetli ostrzeżenia).

To wszystkie problemy, o których pamiętam. Zapewne większość z nich zostanie poprawiona w kolejnych wydaniach języka. Nie są to też problemy, które stanową blokadę przed używaniem tej technologii. Myślę, że zalety są na tyle duże, że nie można przejść obok Elma obojętnie. Jeśli w najbliższym czasie planujesz pisać aplikację webową to daj mu szansę.