To nie jest pierwszy raz, kiedy pisze o tym projekciku, ale wyglada na
to, ze ta notka zamkne ten temat na dobre. Jak moga zauwazyc uzytkownicy
przegladarek obslugujacych JavaScript – na blogu pojawila sie zielona, pozioma
belka (po lewej), ktora rozwija sie w okienko chatu po kliknieciu. (Edit:
nieprawda; nie mialem na to jeszcze czasu, bedzie wkrotce) Instrukcja obslugi:
wybrac jeden z dostepnych serwerow (ktory – to zalezy od tego, gdzie akurat
jestem) a potem juz tylko wesolo rozmawiac sobie z innymi uzytkownikami,
polaczonymi z tym samym serwerem.
Oczywiscie, trafienie na kogokolwiek poza mna graniczy z
cudem, ale nie o to chodzi – zeby sie przekonac, ze chat rzeczywiscie
dziala, wystarczy otworzyc ta strone rownolegle w dwoch zakladkach,
rozwinac dwie instancje chatu i cos wpisac. Przy odrobinie szczescia
wpisany w jednym okienku tekst pojawi sie takze w drugiej zakladce.
Jasne, ze ten program nie jest jakos specjalnie imponujacy -
wymagalby mnostwa pracy, na ktora nie mam ani czasu, ani ochoty, by
stac sie uzytkowa aplikacja. Nie mozna sobie wybrac nicka, nie mozna
wybrac koloru, w ktorym wyswietlane sa nasze wiadomosci, nie ma
prywatnych wiadomosci ani praktycznie nic, czego bysmy sie spodziewali
po takiej appce. Hell, nawet data/czas nadeslania wiadomosci sa zle
formatowane. Jaki wiec jest sens pokazaywania jej i jaki byl sens ja
pisac?
Pewien sens tych zabiegow, jak sadze, zawiera sie w kilku
sformulowaniach: asynchronous message passing, ajax long polling,
JSONP, HTTP server i jeszcze paru im podobnym. Piszac ten program
sporo sie nauczylem o tych zagadnieniach – co dla mnie juz jest
dostatecznym uzasadnieniem dla tych kilkunastu (moze
dwudziestu-kilku, ale chyba nie wiecej, niz trzydziestu) godzin
spedzonych nad tym kodem w pociagach i po nocach. Teraz chce sie ta
wiedza podzielic – wiec pokazuje dzialajacy kod, mam nadzieje
efektywnie zabezpieczajac sie przed komentarzami w stylu a co ty
tam wiesz.
Wybierajac technologie, w ktorych implementowalem swoj
blogchat (taki codename, no co?) kierowalem sie calkiem
swiadomie i z premedytacja ku rejonom, ktorych nie znalem dotad za
dobrze – czesc serwerowa aplikacji moglbym z latwoscia zrealizowac w
Pythonie (przy uzyciu Twisted albo gevent, na przyklad – i to
nawet przy zalozeniu asynchronicznosci), co byloby dla mnie
wielokrotnie latwiejsze. Wybralem jednak Erlanga, zeby z jednej strony
ugruntowac sobie jego znajomosc, a z drugiej byc zmuszonym do uzywania
prawie czysto funkcyjnego stylu programowania. Dzieki podjeciu sie
implementacji prawdziwego programu musialem przeczytac duzo
erlangowego kodu, do ktorego nigdy bym nie dotarl wykonujac tylko
cwiczenia z tutoriali.
Jesli chodzi o owoce tego wysilku, to mam mieszane odczucia. Z
jednej strony asynchroniczny workflow jest w przypadku Erlanga
naturalny jak oddychanie oraz wyjatkowo elegancki (czytelnicy znajacy
JS moga z niedowierzaniem pytac, czy to w ogole mozliwe – jak widac,
owszem), z drugiej jednak Erlang zdecydowanie nie zostal stworzony do
obslugi protokolow tak wysokiego poziomu, jak HTTP. Problemy z
obsluga stringow i koniecznosc pisania wlasnych funkcji realizujacych
rzeczy zazwyczaj dostarczane przez biblioteki byly odrobine
rozczarowujace. Najwiekszy chyba jednak zawod sprawil mi Erlang -
erlangowe podejscie do programowania funkcyjnego – w kwestii
czytelnosci kodu. Nie mowie o kodzie wlasnym – nie dosc, ze jest
stosunkowo nieskomplikowany, to jeszcze dolozylem wszelkich staran, by
byl dobrze obkomentowany i tak czytelny, jakim tylko dalem rade go
uczynic. Chodzi mi o biblioteki i projekty, ktorych zrodla czytalem w
poszukiwaniu funkcji na przyklad do odpowiedniego escape’owania znakow
niedozwolonych w URLach – ich kod jest juz dostatecznie skomplikowany,
by byc nieczytelnym. Mysle, ze glownym czynnikiem przeszkadzajacym w
pisaniu czytelnego kodu w Erlangu jest fakt, ze zmiennym mozna
przypisywac wartosc tylko raz, co prowadzi do tworzenia szeregow nazw
typu Var1, Var2, Var3 i tak dalej. W porownaniu jednak ze Scheme
(praktycznie write once, never read jezykiem) Erlang wciaz wypada
lepiej.
Edit: popracowalem w Erlangu jeszcze troche i stwierdzam, ze
nie jest tak zle; to jednak w duzej mierze kwestia przyzwyczajenia, troche jak
w przypadku jezykow naturalnych, gdzie trzeba sie *osluchac* z danym jezykiem,
co zajmuje zazwyczaj wiecej czasu niz sama nauka podstaw.
Ostatecznie jednak pisanie w Erlangu bylo na tyle przyjemne,
ze drobne niedogodnosci nie zniechecily mnie do tego jezyka. Wciaz nie
jestem przekonany, czy gdybym mial projektowac jakis duzy system
chcialbym pisac go w calosci w Erlangu – ale z cala pewnoscia te jego
elementy, ktore powinny byc mozliwie niezawodne, skalowalne i wydajne
nadawalyby sie do implementacji w nim.
Drugim elementem ukladanki jest Java-, a wlasciwie
CoffeeScript. Te dwa jezyki roznia sie od siebie skladnia, CS
dostarcza kilku wygodnych elementow (destructuring assignment,
array comprehensions) ktorych JS nie posiada, ale generalnie jest
wciaz tym samym jezykiem – dzieki temu moglem skorzystac z
przeznaczonej dla JS biblioteki Backbone, z ktora zetknalem sie po
raz pierwszy w pracy. O ile JS i CS nie byly mi obce, o tyle Backbone
(w czasie, gdy pisalem frontend blogchata) byl czyms kompletnie nowym.
Backbone jest reklamowany jako sposob na strukturyzowanie kodu
pisanego w JS w taki sposob, zeby zamiast wielkiego bloba handlerow,
logiki i prezentacji miec czytelnie rozgraniczone elementy
Model-View-Controller. Trzeba przyznac, ze to, co obiecuje, Backbone
dostarcza – ale nie moge w tym miejscu nie wspomniec, ze jest on tylko
jednym ze sposob na strukturyzowanie kodu w JS i ze nie spodobal mi
sie jakos szczegolnie. W trakcie pracy nad blogchatem poznalem
wewnetrzna architekture Backbone i musze przyznac, ze sam nie bardzo
wiem, jak moznaby dostarczane przez biblioteke abstrakcje inaczej
zaimplementowac lub jakimi innymi je zastapic, wiec moja krytyka jest
dosc slaba: to po prostu poczucie, ze „cos jest nie tak”. Ostatnio
przygladam sie bibliotece Knockout, ktora wyglada dosc obiecujaco. Z
drugiej strony Spine jest pisane w CoffeeScripcie i tez przykulo moja
uwage – ale to zagadnienia na pozniej.
Ciezko podsumowac strone frontendowa jednym zdaniem chocby
dlatego, ze takie podsumowanie niczego nie zmieni – innego sposobu
realizacji klienta (w przegladarce, pomijajac applet/flash) obecnie
nie ma. Co jednak moge powiedziec z cala pewnoscia to to, ze
CoffeeScript rzeczywiscie dziala (kod w nim zapisany jest o 40%
krotszy od JSowego) i ze nie korzystanie z jakiejs biblioteki
porzadkujacej kod JSowy to samobojstwo przy wiekszych projektach.
Tyle tytulem wstepu, ogolnego wprowadzenia. Teraz przedstawie
architekture obu czesci aplikacji (klienta i serwera), przyjety
protokol, by potem pokazac szczegoly implementacyjne.
Zeby napisac jakikolwiek kawalek oprogramowania, konieczne
jest wczesniejsze zdefiniowane problemu, jaki chcemy rozwiazac czy zadania,
jakie chcemy wykonac. W przypadku blogchata chcialem umozliwic
uzytkownikom odwiedzajacym rownoczenie te sama strone komunikacje
(tekstowa) miedzy soba. Ze strony uzytkownika najbardziej podstawowa
sesja z aplikacja powinna wygladac tak:
1. Otworzyc adres w przegladarce.
2. Wpisac jakas wiadomosc w okienko – ta wiadomosc zostaje wyswietlona
u wszystkich innych uzytkownikow ogladajacych ta strone.
3. Jesli ktorykolwiek z innych uzytkownikow wpisze jakas wiadomosc, to
pojawi sie ona w okienku.
Do tego postanowilem dolozyc kilka zdroworozsadkowych
ograniczen na sposob implementacji:
1. Opoznienie pomiedzy wyslaniem przez jednego, a otrzymaniem przez
pozostalych uzytkownikow wiadomosci powinno byc jak najmniejsze.
2. Liczba zapytan widocznych w przegladarce (w Firebugu, na
przyklad) powinna byc jak najmniejsza.
3. Nie powinno miec znaczenia, gdzie znajduje sie (geograficznie i
logicznie) ktory element aplikacji.
Majac tak zdefiniowany problem zabralem sie za projektowanie
wlasciwego rozwiazania. Jakkolwiek kuszaco nie brzmialoby utworzenie
sieci p2p miedzy klientami (uzytkownikami ogladajacymi strone) nie
jest to niestety mozliwe bez pisania rozszerzenia dla przegladarek, z
jakich korzystaja. Jedyna wiec mozliwa architektura okazala sie
klasyczna architektura klient-serwer, z jednym centralnym serwerem
przyjmujacym i rozsylajacym wiadomosci do klientow. Zadania klienta
moze wtedy opisac tak:
1. Polacz sie z serwerem, nasluchuj nowych wiadomosci.
2. Jesli uzytkownik cos napisze, to wyslij to serwerowi jako nowa
wiadomosc.
A dzialanie serwera tak:
1. Nasluchuj dwoch rodzajow polaczen: get i post.
2. Polaczenie get obsluz dodajac je do listy oczekujacych na
wiadomosci.
3. Polaczenie post obsluz wysylajac jego tresc do wszystkich polaczen
oczekujacych na liscie.
Zaczalem od – z tego co pamietam – zaimplementowania…
Ciag Dalszy (prawdopodobnie) Nastapi