In this thesis, we analyzed technologies for creating web applications, using Ruby and JavaScript. Were chosen two tools for creating frontend and three responsible for the backend. The main emphasis has been on a comparison of selected tools. The preface provides background information to the problem, presents the purpose and division of work. This also explains the motivation to take the topic of work and arrangement of chapters. The theoretical introduction describes the essential issues of web application architecture, the understanding of which is crucial for the realization of the theme. Description of the technology is an important chapter, which describes the selected libraries. These chapters lead to the multivariate analysis. This chapter is a comparison of selected tools, aimed to identify the best in each category. At the end, there is a summary of the work carried out, the conclusions of the study, as well as a subjective assessment of examined technologies. It also takes a polemic about the future direction of web applications.
Master Thesis - Comparative analysis of programming Environments based on Ruby and JavaScript.
1. POLITECHNIKA POZNAŃSKA
WYDZIAŁ ELEKTRYCZNY
PRACA DYPLOMOWA
MAGISTERSKA
ANALIZA PORÓWNAWCZA ŚRODOWISK
PROGRAMISTYCZNYCH OPARTYCH NA
JĘZYKACH RUBY I JAVASCRIPT
Adam SKOŁUDA
Damian ROMANÓW
Promotor:
dr inż. Anna Grocholewska-Czuryło
Poznań, 2015
2.
3. Streszczenie
Analiza porównawcza środowisk programistycznych opartych na językach Ruby i Java-
Script.
W niniejszej pracy poddano analizie technologie do tworzenia aplikacji internetowych
wykorzystujące Ruby i JavaScript. Dwa narzędzia dotyczące tworzenia części klienckich
oraz trzy odpowiedzialne za logikę serwerową. Główny nacisk położony został na porów-
nanie wybranych narzędzi. Wstęp dostarcza informacji wprowadzających do zagadnienia,
a także przedstawia cel i podział pracy. Tłumaczy również motywację podjęcia tematu
pracy oraz układ rozdziałów. Wprowadzanie teoretyczne opisuje niezbędne kwestie archi-
tektury aplikacji internetowych, których zrozumienie jest kluczowe dla realizacji tematu.
Opis technologii to obszerny i ważny rozdział przybliżający wybrane biblioteki. Powyższe
rozdziały prowadzą do wielowymiarowej analizy. Rozdział ten jest porównaniem wybra-
nych narzędzi, mającym na celu wskazanie faworytów w poszczególnych kategoriach. W
zakończeniu znajduje się podsumowanie przeprowadzonych prac, wnioski płynące z ba-
dań, a także subiektywna ocena badanych technologii. Podejmuje również polemikę na
temat przyszłego kierunku rozwoju aplikacji internetowych.
4.
5. Abstract
Comparative analysis of development environments based on Ruby and JavaScript.
In this thesis, we analyzed technologies for creating web applications, using Ruby and
JavaScript. Were chosen two tools for creating frontend and three responsible for the
backend. The main emphasis has been on comparison of selected tools. Preface provides
background information to the problem, presents the purpose and division of work. This
also explains the motivation to take the topic of work and arrangement of chapters.
Theoretical introduction describes the essential issues of web application architecture,
the understanding of which is crucial for the realization of the theme. Description of the
technology is a important chapter, which describes the selected libraries. These chapters
lead to the multivariate analysis. This chapter is a comparison of selected tools, aimed to
identify the best in each category. At the end, there is a summary of the work carried out,
the conclusions of the study, as well as a subjective assessment of examined technologies.
It also takes a polemic about the future direction of web applications.
15. Rozdział 1
Wstęp
Internet jako wynalazek bez wątpienia stawiany jest w jednym szeregu z powstaniem
takich innowacji jak wynalezienie elektryczności, druku czy też maszyny parowej. Każdy
z tych patentów usprawniał w znaczący sposób proces wykonywania pracy. Nie inaczej
jest w przypadku Internetu. W dobie powszechnej informatyzacji bardzo mocno rozwija
się rynek aplikacji internetowych, których w większości głównym zadaniem jest wspo-
maganie procesu wymiany i przetwarzania danych pomiędzy użytkownikami. W związ-
ku z tym powstało wiele różnych metod, architektur, języków programowania, a także
sposobów tworzenia interfejsów użytkownika. Wszystko po to, aby proces korzystania z
aplikacji internetowej był jak najbardziej intuicyjny, wygodny, szybki dla użytkownika.
Współcześni klienci firm tworzących oprogramowanie stawiają duże wymagania. Aplika-
cja w większości przypadków musi być intuicyjna, musi podpowiadać użytkownikowi co
ma zrobić, gdy ten się zgubi, powinna przechowywać dane o każdej czynności użytkow-
nika, powinna mieć dynamiczny charakter, powinna przetwarzać dane w locie, dostęp do
danych musi być zapewniony w każdym momencie a na dodatek aplikacja powinna działać
tak samo dobrze na każdym urządzeniu i platformie. Te i inne wymagania wymusiły na
programistach i firmach tworzących oprogramowanie, podejście do tematu aplikacji inter-
netowych w różny sposób. Dzięki temu, aby ułatwić proces wytwarzania oprogramowania,
które spełniałoby wygórowane wymagania klientów, powstało wiele różnych koncepcji, z
których przetrwały tylko te które miały zasadność istnienia. Natomiast każdy sposób re-
alizacji oprogramowania wiąże się także z odpowiednim doborem technologii do wymagań
dla oprogramowania. Przykładem takiej koncepcji tworzenia oprogramowania mogą być
bardzo popularne ostatnio aplikacje typu Single Page Application, które mają wyraźny
podział na warstwę kliencką i serwerową. Posiadają one bardzo mocno rozbudowaną war-
stwę prezentacji i odpytują tylko serwer celem pobrania danych do wyświetlenia. W tego
typu aplikacjach deweloperzy mają do wyboru wiele różnych technologi do implementacji
15
16. Rozdział 1. Wstęp 16
zarówno części serwerowych np. Ruby on Rails, Python i Django lub PHP i Laravel, a
także części klienckich np. Angular, Ember, React czy też Backbone.
1.1 Motywacje podjęcia tematu
Aktualne trendy na rynku aplikacji internetowych skłoniły nas do zainteresowania się
najczęściej wykorzystywanymi technologiami do ich realizacji. W dzisiejszym świecie, aby
być konkurencyjnym, wiele firm skazanych jest na posiadanie własnych systemów infor-
matycznych. Przekłada się to na stosunkowo dużą liczbę ofert pracy dla programistów w
różnych technologiach oraz na różnych poziomach zaawansowania. Dlatego też posiadanie
wiedzy i umiejętności pozwalających realizować tego typu systemy zwiększa atrakcyj-
ność programisty na rynku pracy. Kolejnym powodem, który skłonił nas do wybrania
realizowanego tematu, był fakt, iż chcieliśmy rozpoznać najpopularniejsze narzędzia do
implementacji części serwerowych i klienckich aplikacji sieciowych, poznać ich zalety i
wady, wyrobić sobie opinię na temat każdego z nich i wybrać najlepszy naszym zdaniem
zestaw bibliotek, który mógłby w przyszłości być przez nas wykorzystany do realizacji
komercyjnego systemu informatycznego. W związku z tym, że podczas przebiegu studiów
mieliśmy okazję poznać wystarczające spektrum języków programowania, podjęliśmy de-
cyzję o wyborze języka Ruby i wokół tego języka zostały dobrane biblioteki w przypadku
części serwerowych. Natomiast jeżeli chodzi o wybór języka do implementacji warstwy
prezentacji aplikacji, to wyboru w zasadzie nie ma i musi być to język JavaScript i to
dla niego wybrane zostały najpopularniejsze biblioteki wspomagające tworzenie bogatych
aplikacji internetowych.
1.2 Podział prac
Damian Romanów Adam Skołuda
1 Wstęp
2 Wprowadzenie teorytyczne
2.1 REST
2.2 AJAX
2.3 SPA
2.4 Model - View - Controller
2.5 Języki
2.5.1 Ruby
17. Rozdział 1. Wstęp 17
2.5.2 JavaScript
2.6 Środowiska programistyczne
2.6.1 Ekosystem Rubiego
2.6.2 Node
2.7 Bezpieczeństwo
2.7.1 Uwierzytelnianie
2.7.2 Autoryzacja
3 Opis technologii
3.1 ReactJS
3.2 AngularJS
3.3 Rails
3.3.1 MVC
3.3.2 DRY
3.3.3 Generatory
3.3.4 Struktura projektu
3.3.5 Konwencja ponad konfiguracją
3.3.6 TDD
3.3.7 Aktywna społeczność
3.4 Sinatra
3.5 Grape
4 Analiza
4.1 AngularJS - ReactJS
4.1.1 Próg wejścia
4.1.2 Komplementarność
4.1.3 Poziom ekspresji kodu
4.1.4 Wydajność
4.1.5 Debuggowanie
4.1.6 Przepływ danych
4.1.7 Szablony
4.1.8 Społeczność i popularność
4.1.9 Podsumowanie
4.2 Rails - Sinatra - Grap
4.2.2 Wydajność
4.2.3 Społeczność i popularność
4.2.4 Podsumowanie 4.2.4 Podsumowanie
18. Rozdział 1. Wstęp 18
5 Zakończenie
1.3 Cel pracy
Głównym celem niniejszej pracy była analiza bibliotek do tworzenia aplikacji interne-
towych w środowiskach Ruby i JavaScript. Brane były pod uwagę narzędzia do tworzenia
zarówno części klienckiej, jak i serwerowej. Podejmowany temat wymagał odpowiedniego
zaznajomienia się z wybranymi technologiami. Analiza została oparta przede wszystkim
o badanie zaimplementowanych aplikacji do tworzenia listy zadań. Dodatkowo, spostrze-
żenia i konkluzje autorów pracy zostały skonfrontowane z zewnętrznymi źródłami, co
pozwoliło szerzej spojrzeć na przedstawiane zagadnienie.
1.4 Układ pracy
Praca została podzielona na rozdziały. Każdy z nich opisuje część rozważań dotyczą-
cych analizowanych technologii. Struktura rozdziałów została zdefiniowana w taki sposób,
aby praca miała logiczny i chronologiczny układ. Wprowadzenie teoretyczne przedstawia
podstawowe informację na temat architektury aplikacji internetowych. Opis technologii
ma na celu szczegółowe przedstawienie wybranych narzędzi i rozwiązań. Rozdział ”Anali-
za” zawiera rozważania dotyczące każdej z wykorzystanych technologii. Rozważania te do-
tyczą samej implementacji, a także porównania pod kątem architektury i bezpieczeństwa.
Zawarte są w nim również wyniki przeprowadzonych prób wydajnościowych poszczegól-
nych bibliotek. Rozdział ”Zakończenie” jest bardzo subiektywnym spojrzeniem na każde
z analizowanych rozwiązań technologicznych oraz wybór naszym zdaniem najlepszej z
punktu widzenia programisty. Zakończenie jest podsumowaniem całej pracy, zawiera spis
zrealizowanych zadań, napotkane trudności oraz próbę prognozowania dalszego rozwoju
badanych technologii.
19. Rozdział 2
Wprowadzenie teorytyczne
Niniejsza praca ma na celu analizę technologii tworzenia aplikacji internetowych (web
application). W poprzednim rozdziale zostało wspomniane, że teraźniejszy użytkownik
oczekuje od nich wiele. Należałoby zatem zdefiniować pojęcie ”aplikacja internetowa”,
czym różni się od strony internetowej i czy w ogóle się od niej różni. Do zdefiniowania
powyższych wymagane będzie przedstawienie kilku innych technologii. Internet nie był-
by tym, czym jest obecnie bez HTTP (Hypertext Transfer Protocol) - protokołu typu
żądanie-odpowiedź działającego w architekturze klient-serwer. Rolę serwera odgrywają
programy osadzone na publicznie dostępnym węźle sieciowym. Ogromną zaletą jest tutaj
dowolność w wyborze języka implementacyjnego. Może to być C, C++, C# czy Java.
Programista może wybrać jeden z języków predestynowanych do tego typu zastosowań
(ze względu na samą składnię czy też zestawy bibliotek ułatwiających tworzenie serwe-
rów) np. PHP, Python, Scala czy też omawiany szerzej w niniejszej pracy Ruby. Bar-
dziej niekonwencjonalne, badawcze podejście doprowadziło nawet do powstania serwerów
w Prologu [27] czy Assemblerze [26]. Jedynym wymogiem jest zgodność ze standardem
HTTP [25]. Specyfikacja HTTP obejmuje kilka metod, z czego najważniejsze z nich to
GET, POST, PUT, DELETE. Architektura usług sieciowych bardzo często, a w wręcz
powinna opierać się na REST (Representational State Transfer). Kolejnym elementem
układanki jest język do opisu struktury dokumentów (stron) w Internecie - HTML (Hy-
pertext Markup Language) [24]. Znaczniki HTML są nośnikami dla tekstów, obrazków czy
hiperłączy. Dokument HTML jest odpowiedzią serwera na zapytanie klienta. Klientem
jest zazwyczaj przeglądarka internetowa, parsująca odpowiedź z serwera i wyświetlająca
ją użytkownikowi. Jak się okazało, te dwie proste technologie stworzyły duet, który prze-
trwał próbę czasu i położył podwaliny pod dzisiejszy kształt aplikacji internetowych. Czy
zatem każda strona internetowa jest aplikacją internetową? Odpowiedź na to pytanie nie
jest oczywista i jednoznaczna. Pomocne może okazać się odwołane do klasycznych aplika-
19
20. Rozdział 2. Wprowadzenie teorytyczne 20
cji - np. okienkowych programów na komputery PC. Aplikacja służy do realizacji celu, jest
narzędziem, dzięki któremu użytkownik może zrealizować swoje potrzeby. Np. program
graficzny pozwala nam na obróbkę czy retusz zdjęć. Czy sam folder ze zdjęciami nazwa-
libyśmy narzędziem, który realizuje jakiś cel? Oczywiście pozwala na ich wyświetlanie.
Nie jest to jednak twórcze działanie, a jedynie sama prezentacja treści. Można powie-
dzieć, że wspólnym mianownikiem wszystkich aplikacji jest możliwość kreacji. Zatem czy
strona internetowa prezentująca zawsze taką samą zawartość jest aplikacją? W świetle
powyższej argumentacji z pewnością nie. Przykładami aplikacji internetowych mogą być
Facebook, dokumenty Google czy Twitter - narzędzia umożliwiające użytkownikowi twór-
cze działanie. Ze względu na swoją budowę aplikację internetową można nazwać również
stroną internetową - jednak w obecnych czasach jest to spore uproszczenie i spłycenie jej
funkcjonalności.
2.1 REST
Protokół HTTP jest wystarczający do tworzenia serwerów dostarczających treści (za-
sobów). Przy małych aplikacjach ścieżki dostępu do zasobów (URI) można definiować
w zasadzie dowolnie. Złożone systemy wymuszają jednak stosowanie pewnych konwencji.
Brak wzorców i konwencji jest jedną z przyczyn regresji. W świecie usług sieciowych liczą
się dwa wzorce czy też style tworzenia systemów - SOAP (Simple Object Access Protocol)
oraz REST (Representional State Transfer). SOAP opiera się na RPC, jest raczej wy-
korzystywany do komunikacji komputer-komputer, zachodzi tutaj silne wiązanie serwera
z klientem. Jest to właściwie protokół. REST, to natomiast to wzorzec architektural-
ny do projektowania złożonych, skalowalnych i wydajnych usług sieciowych. Jest bardzo
elastyczny, o ile jest właściwie zaimplementowany. Opiera się na zasobach, standaryzuje
sposób dostępu do nich. Żeby można było mówić o usłudze sieciowej RESTful konieczne
jest spełnienie wszystkich czterech głównych reguł:
• Model klient-serwer
Polega na podziale odpowiedzialności między tymi dwoma bytami. Klient nie po-
siada informacji o sposobie przechowywania zasobów na serwerze. Nie jest dla nie-
go ważne czy jest to baza danych, zwykły plik czy też inna usługa sieciowa. Nie
jest ważny język programowania, użyte biblioteki czy sam sposób implementacji. Z
drugiej strony serwer nie jest obciążony przechowywaniem stanu klienta, nie mu-
si wiedzieć nic o interfejsie użytkownika. Elementem spajającym jest API. Można
dowolnie zamieniać oba elementy, pod warunkiem utrzymywania jednolitego inter-
fejsu. Cecha ta została wykorzystana w niniejszej pracy. Zaimplementowane zostały
21. Rozdział 2. Wprowadzenie teorytyczne 21
3 serwery HTTP i 2 klienty utrzymując niezmienne API. Pozwoliło to osiągnąć dużą
niezależność poszczególnych elementów
• Bezstanowość
Jak zostało wspomniane wyżej, serwer nie przechowuje stanu czy sesji klienta. Zatem
każde żądanie musi zawierać wszystkie informacje potrzebne do jego obsłużenia.
Korzystając z Internetu, bardzo często użytkownik ulega złudzeniu, że jest inaczej.
Wiele aplikacji oferuje możliwość logowania, a nawet zapamiętywania tego logowania
przez dłuższy okres. Powyższe funkcjonalności nie naruszają jednak architektury
REST. W momencie logowania serwer generuje token, który jest przesyłany do
klienta i zapisywany w tzw. ”ciasteczku” (HTTP cookie). W kolejnych żądaniach
legitymujący się nim klient jest uważany za zalogowanego.
• Cacheability
Cecha, która nie ma dobrego odpowiednika w języku polskim. Chodzi to tu o składo-
wanie wyników poprzednich zapytań, aby umożliwić szybki do nich dostęp w przy-
szłości. Z jednej strony umożliwia dostęp do niektórych zasobów niemal w czasie
rzeczywistym, a z drugiej zmniejsza obciążenie samej sieci oraz serwera.
• Wielowarstwowość
Architektura REST nie wymaga, aby końcowym serwerem była jedna maszyna.
Możliwe jest zastosowanie kilku warstw pośredniczących i sterowanie obciążeniem.
Klient końcowy postrzega taki system w ten sam sposób jak jeden serwer.
2.2 AJAX
Przez długi czas zachowanie stron internetowych było dość sekwencyjne. Użytkownik
klikał w hiperłącze, żądanie było wysyłane do serwera. Po otrzymaniu odpowiedzi cała
strona, a właściwie DOM (Document Object Model) były renderowane. Nie zawsze takie
zachowanie jest jednak potrzebne i pożądane. Zajmuje ono dość sporo czasu oraz powodu-
je specyficzne ”mignięcie” zawartości. Czasem zmianom ulegają bardzo niewielkie części
całego dokumentu. Rozwiązaniem tego problemu jest technika AJAX (Asynchronous Ja-
vaScript and XML) (w większości przypadków używa się jednak formatu JSON zamiast
XML - mówi się wtedy o AJAJ). Dzięki niej komunikacja użytkownika z treścią w Inter-
necie stała się bardziej dynamiczna, poprzez swoją asynchroniczność. Tym samym strony
internetowe bardziej zbliżyły się do standardowych programów komputerowych. Warto
w tym miejscu wspomnieć o bibliotece jQuery, która bardzo uprościła używanie AJAX.
22. Rozdział 2. Wprowadzenie teorytyczne 22
Przykład asynchronicznego zapytania GET w czystym JavaScripcie na listingu 2.1 oraz
przy pomocy jQuery na 2.2.
Listing 2.1: Przykład kodu jQuery
1 $.getJSON(’/my/url’, function(data) {
2 });
Listing 2.2: Przykład kodu czysty Javascript
1 var request = new XMLHttpRequest ();
2 request.open(’GET’, ’/my/url’, true);
3
4 request.onload = function () {
5 if (request.status >= 200 && request.status < 400) {
6 // Success!
7 var data = JSON.parse(request.responseText);
8 } else {
9 // We reached our target server , but it returned an error
10
11 }
12 };
13
14 request.onerror = function () {
15 // There was a connection error of some sort
16 };
17 request.send ();
Wszystkie żadania asynchroniczne wykonywane w języku JavaScript należą do żądań XHR
(XMLHttpRequest). Są nośnikiem obiektów o tej samej nazwie. AJAX posiada jednak pew-
ne ograniczenia. Jego funkcjonowanie opiera się na języku JavaScript. Użytkownik może
wyłączyć jego obsługę w przeglądarce, w takim przypadku AJAX będzie bezużyteczny.
2.3 SPA
AJAX przyczynił się do sporej zmiany Internetu. Uatrakcyjnił interakcję użytkownika
z aplikacją internetową, upodobnił ją do swoich ”desktopowych” pierwowzorów. Jeśli moż-
na część danych pobrać z serwera w tle, na żądanie użytkownika lub wtedy kiedy będzie to
konieczne, to dlaczego nie sprawić, aby cała aplikacja działała w ten sposób? SPA (Single-
page application) to podzbiór aplikacji internetowych charakteryzujący się pojedynczym
przeładowaniem strony, przy jej początkowym wyświetleniu. Dalsza komunikacja odbywa
się za pomocą JavaScripti i żądań XHR.
23. Rozdział 2. Wprowadzenie teorytyczne 23
Zalety:
• Rozdzielenie logiki serwera i klienta
Standardowe serwery HTTP są odpowiedzialne również za tworzenie dokumentu
HTML wraz z osadzonymi w nim zmiennymi, czy tworzenie formularzy. W przy-
padku SPA zadania te zostają w całości przerzucone na język JavaScript. Serwer
udostępnia tylko API, czyli szynę wymiany danych. Podejście takie przynosi wiele
korzyści. Jako że są to tak naprawdę dwie osobne aplikacje - mogą być rozwija-
ne przez niezależne zespoły. Logika biznesowa czy szczegóły działania procesów na
serwerze są niewidoczne dla klienta.
• Elastyczność i atrakcyjność interfejsu użytkownika
HTML generowany serwerowo ma pewne ograniczenia. Interakcja z użytkownikiem
opiera się na formularzach, a te przy skomplikowanych interakcjach stają się często
wąskim gardłem. Problematyczna staje się implementacja funkcjonalności działają-
cych ”na żywo”. Formularze mają swoje prawa, muszą zostać wysłane na serwer,
obsłużone, wtedy klient dostaje odpowiedź. Wykorzystanie JavaScriptu daje możli-
wość niemal natychmiastowego komunikowania o wprowadzonych zmianach, również
wprowadzenia stosownych efektów wizualnych. Przykładem może być załadowanie
zdjęcia profilowego na serwer. W standardowym formularzu HTML możemy wybrać
zdjęcie, po czym widzimy ewentualnie jego nazwę. Dopiero po wysłaniu i przetwo-
rzeniu, serwer jest w stanie pokazać nam wynik tego działania. Przy wykorzystaniu
JS, widzimy natychmiastowo wybrany obrazek. Wiele aplikacji oferuje też możliwość
jego przycięcia. Jest to funkcjonalność nieosiągalna dla zwykłych formularzy.
• Wiele klientów
Jako że serwer nie posiada żadnej wiedzy o kliencie, wprowadzanie kolejnych apli-
kacji klienckich, w dowolnych technologiach nie pociąga za sobą żadnych zmian w
kodzie serwera. W dzisiejszym świecie bardzo często się mówi o tzw. IoT (Internet
of Things). Tworzenie aplikacji z wyraźnym rozdziałem serwera i klienta stwarza
idealne warunki do rozszerzenia systemu o aplikacji mobilne. Jest to bardzo ważny
aspekt z punktu widzenia świata biznesu. Obecnie bardzo często tworzy się całe eko-
systemy powiązanych ze sobą aplikacji - włączając w to klienty www, czy mobilne
np. Android, czy iOS.
Wady:
• Niedojrzałość JS
Technologia tworzenia warstw klienckich aplikacji internetowych całkowicie opar-
24. Rozdział 2. Wprowadzenie teorytyczne 24
tych o język JavaScript jest stosunkowo nowym podejściem. Same frameworki JS-
owe rozwijają się bardzo szybko. Z jednej strony jest to pozytywne zjawisko, rozwój
zawsze jest dobry. Obserwując jednak ewolucję (czy raczej rewolucję) takich rozwią-
zań jak AngularJS czy ReactJS, trudno pozbyć się wrażenia, że programista jest w
pewnym sensie królikiem doświadczalnym, a jego produkt poligonem ćwiczebnym.
Jak wiadomo w świecie technologii rok to bardzo dużo czasu, wiele rzeczy może się
zmienić. W świecie JavaScriptu standardy mogą zmienić się w tydzień. I mowa tutaj
o fundamentalnych kwestiach architekturalnych, a nie kosmetycznych poprawkach.
Brakuje nienaruszalnych zasad i standardów, na których można oprzeć budowanie
aplikacji. Te wszystkie względy czynią użycie wspomnianych rozwiązań w poważ-
nych, komercyjnych aplikacjach co najmniej ryzykownym. Technologie nastawione
raczej na back-end (np. Ruby on Rails, PHP Symphony) istnieją o wiele dłużej. Ich
wybór jest bezpieczniejszym wyjściem.
• Niezawodność
Całkowite rozdzielenie warstw klienta i serwera ma niewątpliwie swoje plusy, nie
jest jednak pozbawione wad. Dwie osobne aplikacje to z pewnością więcej kodu niż
jedna. Dodatkowo warstwa front-endowa cechuje się dużą asynchronicznością, jest
sterowana zdarzeniami (kliknięcia, interakcje). Nie ma tutaj tak jasnego przepływu
danych, jak po stronie serwera (zapytanie - odpowiedź). Wszystko to sprawia, że
całość jest dużo trudniejsza do automatycznego testowania, co za tym idzie bardziej
zawodna. Należy też dodać, że pojedynczy błąd w klienckim kodzie JavaScript,
skutkuje bardzo często zaprzestaniem dalszego wykonywania programu.
• Synchronizacja stanu
Aplikacja kliencka jest w całości odpowiedzialna za generowanie i zmienianie doku-
mentu HTML widocznego dla użytkownika. Szablony do jego tworzenia są ustalone
wcześniej i znane. Zmienne, które trzeba w nim osadzić - nie. Zostają one pobrane
z serwera przy pomocy XHR podczas działania aplikacji. Stan aplikacji po stro-
nie klienta musi być synchronizowany z tym, co rezyduje w bazie danych serwera.
Wprowadza to konieczność częściowej duplikacji logiki serwera, a co za tym idzie
jeszcze bardziej zwiększa objętość kodu.
• Indeksowanie
W projektowaniu aplikacji internetowych bardzo ważnym zagadnieniem jest opty-
malizacja ich zawartości pod kątem algorytmów wyszukiwarek internetowych (SEO).
Właściciel serwisu chce, aby jego strona była atrakcyjna, ale co ważniejsze, aby była
łatwo dostępna dla potencjalnych klientów. Polega to m.in. na odpowiednim osa-
25. Rozdział 2. Wprowadzenie teorytyczne 25
dzeniu słów kluczowych czy meta-opisów. Jeśli chodzi o SPA - zadanie komplikuje
się nieco. Cała zawartość (w tym słowa kluczowe) pobierana jest poprzez AJAX
już po pierwszym załadowaniu strony. Dla użytkownika nie jest to problem, ale
web crawlery nie obsługują tego typu treści. Rozwiązaniem problemu jest dodat-
kowe generowanie dokumentu HTML po stronie serwera, specjalnie dla silników
wyszukiwarek. Zagadnienie to jest jednak dość złożone, nie wszystkie frameworki
dostarczają tego typu rozwiązań. Przy braku powyższych dodatkowych czynności,
strona internetowa widziana przez wyszukiwarkę np. Google jest pusta. Taki stan
rzeczy ma fatalny wpływ na indeksowanie strony i jest nie do przyjęcia.
• Początkowe ładowanie
Kolejną niedogodnością jest tzw. ”slow start”. Aplikacja tego typu może ładować
się znacznie dłużej niż standardowa strona. Oczywiście dzięki temu eliminuje się
późniejsze przeładowania, a sama strona działa dużo płynniej. Co jednak w przy-
padku gdy aplikacja potrzebuje kilku (kilkanastu) sekund do załadowania? (co nie
jest odosobnionym przypadkiem, zwłaszcza przy dużych aplikacjach, a także przy
wolnym połączeniu internetowym) Dzisiejszy użytkownik jest bardzo niecierpliwy i
nieprzyzwyczajony do czekania na treść. Jego pierwsze wrażenie kształtuje się już w
przeciągu ułamków sekundy, a utwierdza się w tym przekonaniu w czasie do kilku
sekund. Co, jeśli przez cały ten czas będzie wpatrywał się w pasek ładowania, albo
co gorsza, pustą stronę?
• Wyłączony JS
Wydaje się, że temat braku obsługi języka JavaScript przez przeglądarki interne-
towe w roku 2015 nie powinien być podniesiony. Wciaż jednak istnieje grupa użyt-
kowników (czasem całych korporacji) korzystająych z przestarzałych przeglądarek
(wczesne wersje IE) z wyłączonym JavaScriptem. Nie jest to duży odsetek, ale pro-
gramista wybierający zestaw technologii powinien się z nim liczyć. W przypadku
gdy głównym odbiorcą aplikacji ma być powyższa grupa, implementacja systemu w
technice SPA byłaby błędem.
2.4 Model - View - Controller
W informatyce bardzo powszechnym podejściem jest dekompozycja problemu. Ła-
twiejsze staje się rozwiązanie trzech małych problemów niż jednego dużego. Na podobną
myśl w odniesieniu do aplikacji z interfejsem użytkownika wpadł już w 1970 roku Try-
gve Reenskaug. Współczesne programy komputerowe potrafią być bardzo złożone, ofe-
26. Rozdział 2. Wprowadzenie teorytyczne 26
rując użytkownikowi cały wachlarz funkcjonalności. Gdyby jednak rozłożyć każdą z nich
na czynniki pierwsze, okazałoby się, że złożony obiekt składa się z małych niezależnych
bloczków - modelu, widoku i kontrolera. Wydawać się może, że tylko 3 byty to za mało
dużych aplikacji. Jak pokazuje jednak historia informatyki, najprostsze rozwiązania są
najlepsze (REST), a utrzymanie prostej i spójnej architektury kluczem do sukcesu. Echo
MVC pobrzmiewa bardzo silnie w ekosystemie WWW. Jak się okazało, forma aplikacji
internetowych, jest bardzo spójna z tym, co proponuje MVC. Współcześnie wszystkie
liczące się technologie do zastosowań webowych korzystają z MVC. Framework ”Rails”
bardzo silnie wypromował wzorzec MVC - do tego stopnia, że programiści zaczęli tworzyć
frameworki w innych językach, inspirowane właśnie Railsami [18]. Kolejnym dowodem
słuszności MVC, może być fakt jego stosowania również po stronie klienta JavaScript.
Początkowo nie było to potrzebne, jednak obecnie ilość potrzebnych informacji i stopień
skomplikowania logiki stał się na tyle duży, że wzorzec MVC (lub jego odmiany) stały
się koniecznością. Brak jego zastosowania bardzo często skutkuje regresją kodu, wzrostem
zawodności oraz spadkiem czytelności (spagetti code). Wzorzec MVC opisuje przeznacze-
nie i sposób interakcji między trzema tytularnymi typami obiektów. Krótki opis każdego
z nich:
• Model
Odpowiedzialny za przechowywanie informacji związanych z obiektem, jedyny no-
śnik logiki biznesowej. W większości przypadków powiązany z bazą danych (ORM
- mapowanie obiektowo-relacyjne). Niezależny od widoku.
• Widok
Sposób prezentacji danych (np. z modelu) oraz interfejs użytkownika.
• Kontroler
Obiekt spinający dwa powyższe - pozwala przechwytać interakcje użytkownika zapo-
czątkowane na komponentach widoku i przekazywać je do modelu. Z drugiej strony
dostarcza danych z modelu do widoku, odpowiedzialny również za dobór właściwego
modelu (np. wpis o odpowiednim id)
2.5 Języki
Języki programowania są podobnie jak języki mowy naturalnej zbiorem pewnych reguł
syntaktycznych oraz semantycznych dzięki którym człowiek w tym przypadku programista
może wydawać zrozumiałe polecenia do wykonania przez komputer. Prawo relatywizmu
językowego zaproponowane ok. 1930r przez Sapira i Whorfa mówi o tym, że używany
27. Rozdział 2. Wprowadzenie teorytyczne 27
język wpływa w większym lub mniejszym stopniu na myślenie i sposób postrzegania ota-
czającego nas świata. Pomimo tego, iż hipoteza ta dotyczy języka mowy, jej założenia
znajdują także odzwierciedlenie w posługiwaniu się językami programowania. Dlatego też
wybór danego języka także w informatyce jest bardzo istotny. Natomiast warto pamiętać
o tym, iż w większości przypadków nowe języki programowania postrzegane są błędnie
jako cudowne narzędzia rozwiązujące wszystkie problemy. Nie istnieje jedno uniwersalne
narzędzie do wszystkiego. Wynika to z tego, iż występuje bardzo duża liczba zbiorów pro-
blemów a każdy z nich ma zbyt dużą ilość ograniczeń. Skutkiem tego jest fakt, iż z reguły
języki programowania specjalizują się w rozwiązywaniu problemów zwykle z wąskiego
zakresu danej dziedziny. Jednakże wśród programistów już zawsze będą trwały swojego
rodzaju ”wojny na języki” podyktowane zazwyczaj indywidualnymi preferencjami, ale
także szerokim wachlarzem dostępnych narzędzi.
2.5.1 Ruby
Ruby jest popularnym, dynamicznym językiem skryptowym, który ma na celu dać
programiście duże możliwości, poczucie swobody oraz ma sprawić, aby programowanie
w tym języku było tak naturalne i przyjemne jak to tylko możliwe. Według jego twórcy
Yukihiro “Matz” Matsumoto Ruby jest językiem starannie dobranej równowagi. Aby to
osiągnąć, jego autor połączył wybrane elementy każdego z języków Perla, Smalltalka, Eif-
fel, Ady, i Lispa, by stworzyć język, który balansuje programowanie funkcjonalne wraz z
programowaniem imperatywnym. Pierwsze publiczne ukazanie języka Ruby nastąpiło w
1995 roku. Natomiast dopiero w 2006 język ten zyskał większą popularność oraz przychyl-
ność środowiska programistów. Stało się tak głównie za sprawą powstania i popularyzacji
frameworka do tworzenia aplikacji internetowych Rails, o którym więcej w rozdziale 3.3.
Kolejnym powodem, dla którego omawiany język stał się tak popularny, co obrazuje Rys.
2.1, jest fakt, iż Ruby jest językiem całkowicie darmowym także w rozumieniu kopiowania,
modyfikowania i rozprowadzania tego języka. Oto najważniejsze cechy języka:
• Ruby jest językiem zwinnym. A to dlatego, że udostępnia programiście, możliwość
dowolnego modyfikowania jego części czego skutkiem jest zachęta społeczności do
ciągłego udoskonalania wykorzystywanego przez nich języka. Ruby stara się nie na-
rzucać żadnych ograniczeń programiście co pozwala w zależności od intencji usunąć
lub przedefiniować podstawowe elementy języka. Przykładowo operację dodawania
wykonuje się za pomocą operatora ”+”. Natomiast istnieje możliwość wykonania
tego działania za pomocą słowa ”plus”. W tym celu należałoby dodać odpowiednią
metodę do klasy ”Numeric”.
28. Rozdział 2. Wprowadzenie teorytyczne 28
Listing 2.3: Elastyczność języka Ruby
1 class Numeric
2 def plus(x)
3 self .+(x)
4 end
5 end
6
7 y = 1. plus 3
Wynikiem działania jest oczywiście 4. Operatory w języku Ruby są tak zwanym
”lukrem” składniowym dla metod, które również można w dowolny sposób przede-
finiować.
• Ruby jest językiem bardzo wysokiego poziomu. Twórcy języka kierowali się zasadą,
że to komputer powinien pracować dla programisty, a nie odwrotnie. Dzięki temu
nawet najbardziej skomplikowane operacje możemy wykonywać za pomocą stosun-
kowo nie dużej ilości kodu w porównaniu z językami niższego poziomu.
• Ruby jest językiem silnie obiektowym. Dzięki temu każda część kodu może mieć
własne atrybuty w postaci zmiennych instancji oraz własne metody. Dzięki temu
możemy wywołać metodę na liczbie:
Listing 2.4: Obiektowość języka Ruby
1 10. times { print "Everything is an object!" }
Istnieje wiele języków programowania, w których np. liczby i inne bazowe typy
nie zachowują się jak obiekty. Inaczej jest w przypadku Rubiego, który tę cechę
odziedziczył po Smalltalku, co znacznie ułatwia korzystanie z języka, dlatego że
zasady dotyczące obiektów mają również zastosowanie dla całego języka.
• Ruby posiada cechy języków funkcyjnych. Dzięki temu, możemy dołączyć do do-
wolnej metody domknięcie, które opisuje sposób działania danej metody. Tego typu
domknięcie nazywa się blokiem i zostało zaczerpnięte przez twórców z języka funk-
cyjnego Lisp.
Listing 2.5: Funkcyjność języka Ruby
1 ruby_traits =
2 %w[Agile Object -oriented High -level ].map do |trait|
3 "Ruby is " + trait.downcase + " language!"
4 end
29. Rozdział 2. Wprowadzenie teorytyczne 29
Rysunek 2.1: Porównanie ilości ofert pracy dla popularnych języków serwerowych, [źródło:
http://www.indeed.com/jobtrends].
Powyższy przykład pokazuje blok, który znajduje się między słowami kluczowymi
”do” i ”end”. Bloki mogą być definiowane w dowolny sposób i realizować złożone
operacje.
• Ruby realizuje paradygmat dziedziczenia. Natomiast robi to inaczej niż w większo-
ści języków programowania. Ruby realizuje celowo tylko dziedziczenie jednokrotne,
natomiast dla programistów tego języka wprowadzono możliwość korzystania z mo-
dułów, które są zbiorami metod. Do każdej klasy może zostać dołączony tego typu
moduł, który rozszerza klasę o implementacje metod z danego modułu. Tego typu
rozwiązanie uznawane jest przez programistów Rubiego za prostsze i wygodniejsze
względem wielokrotnego dziedziczenia, które nakłada wiele ograniczeń i może być
stosunkowo skomplikowane.
• Ruby posiada mechanizm odśmiecania pamięci. Tak jak w wielu nowoczesnych ję-
zykach programowania, tak i w Rubim występuje garbage collector z prawdziwego
zdarzenia typu mark-and-sweep, który wykorzystywany jest dla wszystkich obiektów
żyjących w pamięci obiektowej. Według twórców języka nie istnieje potrzeba prze-
trzymywania informacji na temat liczby odniesień do obiektu tak jak w metodach
odśmiecania typu reference counting.
30. Rozdział 2. Wprowadzenie teorytyczne 30
Rysunek 2.2: Wykres pokazujący ilość ofert pracy dla języków JavaScript, C++, C#,
PHP oraz Python, [źródło: http://www.indeed.com/jobtrends].
2.5.2 JavaScript
JavaScript jest skryptowym językiem programowania, który pojawił się w już w 1995
za sprawą firmy Netscape. Wbrew pozorom geneza jego nazwy ma nie wiele wspólnego
z językiem Java. Powstała ona po prostu w wyniku kontraktów biznesowych pomiędzy
firma Netscape i Sun Microsystems. W 1997 roku ECMA stworzyła standard dla języka
JavaScript zwany ECMAScript. JavaScript zyskał dużą popularność za sprawą rozwoju
dynamicznych aplikacji internetowych, co można zauważyć na wykresie 2.2 trendów od-
nośnie zatrudnienia. Dzisiaj już nikt nie wyobraża sobie funkcjonowania aplikacji www
bez korzystania z JavaScriptu. Na bazie tego języka powstało wiele rozbudowanych fra-
meworków frontendowych o których więcej w rodziale 3 a nawet backendowych opisanych
w rozdziale 2.6.2 co także miało duże znaczenie na zwiększenie popularności tego Java-
Scriptu a co za tym idzie także programistów posługujących się tym językiem. Elementy
języka JavaScript zgodne ze standardem ECMAScript:
• Niektóre podstawowe typy danych i obiekty wykorzystywane w JavaScripcie.
Listing 2.6: Typy danych w języku JavaScript
1 String , Boolean , Number , Object , Math , Array
• JavaScript odziedziczył podstawowe instrukcje sterujące po językach C++ i Java.
Listing 2.7: Komentarz blokowy w języku JavaScript
31. Rozdział 2. Wprowadzenie teorytyczne 31
1 /* To jest komentarz
2 blokowy zajmujacy
3 kilka linii */
Listing 2.8: Komentarz liniowy w języku JavaScript
1 // To jest komentarz liniowy
Listing 2.9: Instrukcja if w języku JavaScript
1 if (warunki) {
2 instrukcje;
3 }
4 else {
5 instrukcje;
6 }
Listing 2.10: Pęta while w języku JavaScript
1 while (warunki) {
2 instrukcje;
3 }
Listing 2.11: Pętla do...while w języku JavaScript
1 do {
2 instrukcje
3 } while (warunki);
Listing 2.12: Pętla for w języku JavaScript
1 for ([ poczatkowe ]; [warunki ]; [krokowe ]) {
2 instrukcje;
3 }
Listing 2.13: Pętla for...in oraz for...of w języku JavaScript
1 for (wlasnosc in obiekt) {
2 instrukcje;
3 }
Listing 2.14: Instrukcja switch w języku JavaScript
1 switch (wyrazenie) {
32. Rozdział 2. Wprowadzenie teorytyczne 32
2 case wartosc1:
3 instrukcje;
4 break;
5 case wartosc2:
6 instrukcje;
7 break;
8 default:
9 instrukcje;
10 break;
11 }
• W JavaScripcie występują obiekty i typy prymitywne. Według standardu ECMA-
Script obiekty są tablicami asocjacyjnymi. Ze względu na to iż w JavaScripcie me-
toda danego obiektu jest także jego polem istnieją dwa sposoby odwołania:
Listing 2.15: Notacja kropkowa wywołania metody w języku JavaScript
1 m.metoda1 ();
Listing 2.16: Notacja z nawiasami kwadratowymi wywołania metody w języku JavaScript
1 m["metoda1"]();
• Aby utworzyć w JavaScripcie własny obiekt trzeba stworzyć funkcję konstruktora.
Listing 2.17: Funkcja konstruktora w języku JavaScript
1 function Obiekt(pole1 , pole2) {
2 this.pole1 = pole1;
3 this.pole2 = pole2;
4
5 function metoda1 () {
6 alert("Obiekt :: metoda1 ()");
7 }
8 this.metoda1 = metoda1;
9
10 function metoda2 () {
11 alert("Obiekt :: metoda2 ()");
12 }
13 this.metoda2 = metoda2;
14 }
33. Rozdział 2. Wprowadzenie teorytyczne 33
• W większości współczesnych języków obiektowych występują ”klasy” które pozwala-
ją tworzyć własne niestandardowe typy. Inaczej jest w przypadku języka JavaScript,
ponieważ według ECMA pojęcie ”klasy” nie istnieje w sensie formalnym. Mówiąc o
”klasach” w JavaScripcie mamy na myśli obiekty stworzone z wykorzystaniem tego
samego konstruktora.
Listing 2.18: Tworzenie instancji klasy Obiekt w języku JavaScript
1 var m = new Obiekt (1, 2);
• Definiowanie funkcji w JavaScripcie odbywa się za pomocją słowa kluczowego func-
tion. Zgodnie z ECMAScript funkcje są jednocześnie obiektami klasy Function.
Listing 2.19: Definicja funkcji w języku JavaScript
1 function dodajLiczby(a, b) {
2 return a+b;
3 }
• JavaScript implementuje także paradygmat dziedziczenia, ale w uproszczony sposób
wykorzystując prototypy.
Listing 2.20: Dziedziczenie w języku JavaScript
1 function KlasaBazowa () {
2 this.metoda1 = function () {
3 alert("KlasaBazowa ::1()");
4 }
5 this.metoda2 = function () {
6 alert("KlasaBazowa ::2()");
7 }
8 }
9
10 function KlasaPochodna () {
11 // metoda2 przeciaza odpowiednia metode z klasy KlasaBazowa:
12 this.metoda2 = function () {
13 alert("KlasaPochodna ::2()");
14 }
15 }
16 KlasaPochodna.prototype = new KlasaBazowa ();
17
18 x = new KlasaBazowa ();
19 y = new KlasaPochodna ();
34. Rozdział 2. Wprowadzenie teorytyczne 34
Rozdział opracowany na podstawie [15]
2.6 Środowiska programistyczne
Poprzez pojęcie środowiska programistyczne rozumiemy tu technologie, narzędzia oraz
biblioteki zorientowane wokół danego języka programowania wraz z tymi językami, które
zostały bliżej przedstawione w rozdziale 2.5. Należy pamiętać o tym, iż wybór danego
języka determinuje całe środowisko pracy danego programisty. Dlatego też w tym rozdziale
przedstawione zostaną bliżej konkretne zestawy narzędzi i bibliotek, ich geneza oraz baza
teoretyczna dla wybranych w poprzednim rozdziale języków programowania.
2.6.1 Ekosystem Rubiego
Język Ruby sam w sobie jest świetnym narzędziem, nastawionym na tworzenie czy-
stego, zrozumiałego kodu. Jest to język powszechnego zastosowania, w praktyce jednak
używany w większości przypadków w zestawieniu z Railsem. Czy popularność powyższych
można tłumaczyć tylko wysoką estetyką kodu? Ma to z pewnością wielkie znaczenie, jed-
nak nie mniej ważny jest aspekt open-source. Zarówno Ruby, jak i Rails to środowiska
otwarte. Każdy ma dostęp do kodu źródłowego, każdy może go modyfikować na własne
potrzeby. Może również tymi modyfikacjami dzielić się z twórcami, aby każdy mógł korzy-
stać z nowych funkcjonalności czy poprawek. Podobnie jest ze wszystkimi gemami (”gem”
to nazwa dodatkowej biblioteki w języku Ruby. Nawiązuje do nazewnictwa języka, ruby
- rubin. Jest to nazwa własna i dość charakterystyczna, dlatego w dalszej części pracy,
pojęcia te będą używane zamiennie). I to właśnie otwartość środowiska jest motorem
napędzającym cały ekosystem.
RubyGems
Ruby od wersji 1.9 jest dostarczany z wbudowanym menadżerem pakietów - Ruby-
Gems. Moduł ten jest domyślnie połączony z repozytorium gemów - rubygems.org. Jeśli
wskazana biblioteka znajduje się w repozytorium, zostanie automatycznie pobrana. Ist-
nieje możliwość wskazywania również innych źródeł. Instalacja nowego gema ogranicza się
do wywołania jednej komendy, np:
Listing 2.21: Przkład instalacji gema ”Devise”
1 gem install devise
Narzędzie RubyGems zostało szerzej opisane w [17] w rozdziale 21.1.
35. Rozdział 2. Wprowadzenie teorytyczne 35
RVM
Bardzo często zdarza się, że programista pracuje nad kilkoma projektami. Nie wszyst-
kie z nich musza korzystać z tej samej wersji języka Ruby. W standardowych okoliczno-
ściach każda zmiana projektu wiązałaby się z reainstalacją Rubiego wraz z potrzebnymi
gemami. Takie działanie byłoby ogromną stratą czasu. Z pomocą przychodzi RVM, czyli
menadżer wersji języka Ruby. Dzięki niemu w systemie rezydują obok siebie różne wersje
wraz z zainstalowanymy gemami. Zmiana wersji odbywa się za pomocą prostej komendy:
Listing 2.22: Zmiana wersji Rubiego na 2.2.3
1 rvm use 2.2.3
2 ruby -v
3 ruby 2.2.3 p173 (2015 -08 -18 revision 51636) [x86_64 -darwin14]
Bundler
Instalacja pakietów jest prosta dzięki RubyGems. Bardzo często zdarza się jednak, że
gemy bazują na innych gemach (dependency). Ręczne sprawdzanie zależności i instalacja
odpowiednich wersji bibliotek, byłoby bardzo czasochłonnym zajęciem. W dodaktu cały
proces należałoby powtarzać przy każdej aktualizacji gemów. Powyższe zadanie jest trudne
dla człowieka, jednak komputery radzą sobie z nim bez problemu. Gem ”bundler” auto-
matycznie negocjuje wersje pakietów, znajduje takie, które spełniają oczekiwania każdego
gema (o ile to możliwe). Programista tworzy jedynie plik Gemfile, w którym wylistowane
są potrzebne gemy. Bundler tworzy na jego podstawie plik Gemfile.lock wskazujący na
ich konkretne wersje. Poniżej przykłady wymienionych plików:
Listing 2.23: Przykładowy Gemfile
1 source ’https :// rubygems.org’
2
3 gem ’guard ’
4 gem ’guard -shell ’
5 gem ’terminal -notifier -guard ’
Listing 2.24: Przykładowy Gemfile.lock
1 GEM
2 remote: https :// rubygems.org/
3 specs:
4 coderay (1.1.0)
5 ffi (1.9.10)
37. Rozdział 2. Wprowadzenie teorytyczne 37
irb
Bardzo wygodnym narzędziem jest interaktywna konsola języka Ruby (irb - interactive
Ruby). Jako że Ruby jest językiem interpretowalnym, można dynamicznie wykonywać jego
instrukcje i prezentować ich wyniki. Irb jest przydatne w przypadku chęci przetestowania
działania napisanego kodu. Poniżej przykład użycia irb:
Listing 2.25: ”Hello World” w irb
1 $ irb
2 :001 > puts ’Hello World ’
3 Hello World
4 => nil
Jeszcze ciekawszym narzędziem jest rails console. Jest bardzo podobne do konsoli irb,
używane jest jednak tylko w obrębie aplikacji Rails. Dzięki niemu możemy korzystać
ze wszystkich klas i metod napisanych w obrębie tej aplikacji. Np. możliwe staje się
uzyskanie dostępu do bazy danych poprzez ORM. Możemy wprowadzić zmiany, operując
na modelach, a nie tabelach bazodanowych. Przykład użycia rails console:
Listing 2.26: Zmiana hasła klienta o id 1
1 $ rails console
2 przykladowa_aplikacja >> Client.find_by(id: 1). update_attributes
password: ’nowe haslo ’
3 Client Load (19.4 ms) SELECT "clients".* FROM "clients" WHERE "
clients"."id" = $1 LIMIT 1 [["id", 1]]
4 (2.2 ms) BEGIN
5 SQL (0.6 ms) UPDATE "clients" SET " encrypted_password " = $1 , "
updated_at" = $2 WHERE "clients"."id" = $3 [["
encrypted_password ", " $2a$10$fOjZciThhpJ9flUV0B5f1 .. qbsZXgEBfYb
/5 PJkQMGWxww0XKbbj ."], ["updated_at", "2015 -08 -29
15:41:19.559307 "], ["id", 1]]
6 (48.7 ms) COMMIT
7 => true
Rake
Odpowiednik narzędzia ”make” z systemów UNIX. Pozwala na konstruowanie zadań,
reguł. Umożliwia zarządzane zależnoścami. Zadania definiuje się w języku Ruby, dzię-
ki zastosowaniu specjalnego DSL. Co ciekawe umożliwia budowanie także programów w
innych językach, np. C.
38. Rozdział 2. Wprowadzenie teorytyczne 38
2.6.2 Node
Node.js jest platformą stworzoną na bazie środowiska uruchomieniowego JavaScript.
Projekt ten jest stosunkowo młody, gdyż pojawił się w 2009 roku i bardzo szybko zyskał
dużą popularność, co widać na Rys. 2.4. Społeczność wokół Node.js rośnie tak szybko, że
obecnie projekt ten jest drugi co do liczby obserwatorów w portalu GitHub i aktualnie
istnieje ok. 70 000 modułów stworzonych na licencji ”Open Source” dostępnych z poziomu
menadżera pakietów dla Node.js npm. Główne cechy platformy Node.js:
• Podstawą działania Node.js jest język JavaScript, o którym więcej w rozdziale 2.5.2.
Do wykonywania kodu po stronie serwera Node stosuje wirtualną maszynę Java-
Script V8, z której korzysta przeglądarka Google Chrome. Dzięki temu aplikacje
tworzone w Node zyskują dużą wydajność z tego względu, iż zamiast uruchamiania
kodu bajtowego z wykorzystaniem interpretera Node kompiluje program do kodu
maszynowego. Wykorzystanie języka JavaScript po stronie serwera przynosi kilka
istotnych korzyści. Po pierwsze w wyniku tego, iż aplikacje internetowe mogą być
tworzone z wykorzystaniem tylko jednego języka programowania zarówno po stronie
klienckiej, jak i serwerowej minimalizowany jest proces przełączania kontekstu. Po
drugie Node wykorzystuje JSON jako format wymiany danych zyskujący dużą popu-
larność w dziedzinie tworzenia aplikacji internetowych będący natywnym formatem
dla języka JavaScript. Dzięki temu, że Node korzysta z dokładnie jednej maszyny
wirtualnej V8, zgodnej ze standardem ECMAScript, korzystanie z nowych funk-
cji języka JavaScript nie jest ograniczone przez spóźnione aktualizacje przeglądarek
internetowych różnych producentów.
• Obsługa operacji wejścia-wyjścia na serwerze. Zgodnie z rysunkiem 2.3 obrazują-
cym wykonywanie nieblokującej operacji Node jest serwerem asynchronicznym. W
standardowym podejściu komunikacji z serwerem mamy model typu zapytanie - od-
powiedź kóre realizowane jest w głównym wątku aplikacji i kolejne zapytania do
serwera są zwyczajnie kolejkowane. Natomiast w przypadku Node.js istnieje tzw.
pętla zdarzeń, która dopiero po otrzymaniu odpowiedzi wykonuje operacje zdefinio-
wane w funkcji zwrotnej.
• Tworzenie aplikacji typu DIRT. W związku z tym, że Node dzięki nieblokującemu
przetwarzaniu akcji wejścia-wyjścia powoduje małe obciążenie podczas przetwarza-
nia operacji, często jest wykorzystywany jako pośrednik w przekazywaniu różnych
strumieni danych z różnych źródeł. Ta cecha powoduję, iż Node staję się popularny
w aplikacjach przetwarzających dużą ilość danych w czasie rzeczywistym. Przykła-
39. Rozdział 2. Wprowadzenie teorytyczne 39
Rysunek 2.3: Przykład nieblokującej operacji wejścia-wyjścia wykonywanej przez Node,
[źródło: [3]].
dem aplikacji typu DIRT napisanej w Node jest [16] która służy do testowania stron
internetowych na różnych platformach w czasie rzeczywistym.
W związku z dużym rozwojem frameworków frontendowych rosnącym ich skompli-
kowaniem oraz tendencją do upodabniania się do aplikacji backendowych pojawiły się
zapotrzebowania na dodatkowe narzędzia i biblioteki do odpowiedniego zarządzania tymi
frameworkami. I tutaj do gry wchodzi Node.js, który wprowadza między innymi mena-
dżer zarządzania pakietami npm, które można porównać do gemów w środowisku Ruby
oraz nvm, który służy do zarządzania wersjami Node.js. Nvm jest odpowiednikiem rvm,
o którym więcej w rozdziale 2.6.1. W wyniku tego, iż technologie aplikacji klienckich nie
podlegają w zasadzie żadnej standaryzacji tego, typu rozwiązań open source jest bardzo
dużo i nie sposób je wszystkie opisać a co dopiero używać.
2.7 Bezpieczeństwo
W dzisiejszym świecie Internet jest medium, w którym dochodzi do olbrzymiej wy-
miany informacji. Dane przesyłane w Internecie dotyczą każdego aspektu życia, dlatego
też kluczowym pojęciem stało się bezpieczeństwo. Według jednej z definicji komputer jest
40. Rozdział 2. Wprowadzenie teorytyczne 40
Rysunek 2.4: Porównanie ilości ofert pracy dla popularnych platform serwerowych, [źródło:
http://www.indeed.com/jobtrends].
uznawany za bezpieczny wówczas, gdy można stwierdzić, że sprzęt oraz oprogramowanie
na nim znajdujące się działają zgodnie z oczekiwaniami użytkownika. Natomiast w obliczu
bardzo szybkiego rozwoju globalnej sieci, jaką jest Internet Rys. 2.5 oraz sposobu i skali
publikacji informacji, powstała potrzeba bezpieczeństwa ukierunkowana na WWW. Defi-
niuje się je jako zbiór technologii, procedur i metod wykorzystywanych do zabezpieczania
serwerów WWW, użytkowników, a także organizacje stające za nimi. Wprowadzane środki
bezpieczeństwa mają za zadanie ustrzec użytkowników przed nieoczekiwanym zachowa-
niem komputerów. Istnieje wiele znaczących czynników, które wpływają na odmienne i
bardziej ukierunkowane podejście do bezpieczeństwa WWW:
• Internet z założenia jest siecią dwukierunkową. Publikowane za jej pomocą informa-
cje znajdują się na serwerach WWW, do których dostęp mogą mieć miliony osób
na całym świecie. Tego typu zagrożenia nie występują w przypadku innych mediów
np. gazeta, faks, telefon.
• Sieć WWW wykorzystywana jest przez duże organizacje komercyjne, ale także i rzą-
dowe do przechowywania i dostępu do wielu różnych danych także tych wrażliwych.
Skutkiem tego jest wymagane zwiększone bezpieczeństwo dostępu do tych danych
oraz większa kontrola nad tym, kto posiada dostęp do nich.
• Pomimo tego, że sieć internetowa tworzona była i standaryzowana od początku swo-
jego istnienia przez wiele lat to poziom skomplikowania zarówno z punktu widzenia
sprzętowego, jak i oprogramowania jest na tyle duży, że liczba występujących błę-
41. Rozdział 2. Wprowadzenie teorytyczne 41
dów bardzo mocno podnosi ryzyko zagrożenia bezpieczeństwa. Przykładem takiego
oprogramowania mogą być przeglądarki internetowe, którym cały czas zdarzają się
często poważne luki w zabezpieczeniach.
• Na poziom istotności zabezpieczeń serwerów WWW w znaczący sposób wpływa
fakt, iż większość użytkowników Internetu nie posiada wystarczającej wiedzy, do-
świadczenia ani świadomości zagrożeń, jakie mogą się pojawić.
• W większości przypadków naprawa skutków naruszeń bezpieczeństwa wymaga wię-
cej zasobów czasu i pieniędzy niż wprowadzenie odpowiednich zabezpieczeń.
• W dzisiejszym świecie za pośrednictwem sieci WWW codziennie dochodzi do nie-
zliczonej ilości transakcji finansowych wraz z wymianą informacji poufnych typu
numery kart kredytowych czy też dane personalne obu kontrahentów.
• Wiele firm wykorzystuje sieć WWW do komunikacji z zewnętrznymi odbiorcami,
którymi mogą być partnerskie firmy bądź kontrahenci zlokalizowani na całym świe-
cie. Zastrzeżone dane wymieniane pomiędzy tymi firmami mogą się stać celem przy-
kładowo dla wrogów danej firmy.
Problem zapewnienia bezpieczeństwa WWW należy podzielić na trzy główne elementy, z
których każdy musi spełniać warunki bezpieczeństwa, aby całość przetwarzania danych w
obrębie WWW można było nazwać bezpiecznym:
• Odpowiednie zabezpieczenie serwera WWW oraz informacji na nim przechowywa-
nych w taki sposób, aby mieć pewność, że serwer będzie działał ciągle i poprawnie.
Przechowywane dane na serwerze WWW nie mogą być modyfikowane przez nieupo-
ważnione do tej operacji osoby oraz mogą być udostępniane tylko i wyłącznie dla
osób autoryzowanych, czyli takich, które posiadają prawa dostępu do tych informa-
cji.
• Jak w każdym systemie informatycznym tak i w sieci WWW największe zagrożenie,
jeżeli chodzi o bezpieczeństwo, występuje na styku dwóch różnych systemów. Klu-
czowym elementem jest zabezpieczenie przekazywanych informacji w szczególności
tych poufnych od klienta do serwera. Głównym zagrożeniem bezpieczeństwa komu-
nikacji na linii klient-serwer są wszelkiego rodzaju podsłuchy np. atak ”man in the
middle”.
• Zabezpieczenie komputera klienta, czyli użytkownika końcowego zwykle bywa naj-
trudniejszym elementem dla zapewnienia bezpieczeństwa. Klient powinien być pew-
42. Rozdział 2. Wprowadzenie teorytyczne 42
Rysunek 2.5: Dostęp do Internetu stacjonarnego w Polsce, [źródło: http://pclab.pl/
news63802.html].
ny tego, że dane przesyłane na jego komputer są zgodne z oczekiwaniami i nie
spowodują uszkodzenia innych danych bądź urządzeń.
2.7.1 Uwierzytelnianie
Uwierzytelnianie jest procesem mającym na celu potwierdzenie tożsamości danego
użytkownika. Metoda ta jest podstawowym mechanizmem bezpieczeństwa danych. W
przypadku poprawnego uwierzytelnienia istnieje pewność, że dany użytkownik jest osobą,
za którą się podaje, czego konsekwencją z kolei jest przydzielenie mu dostępu do zasobów,
czyli uwierzytelnienie co jest opisane dokładniej w rozdziale 2.7.2.
2.7.2 Autoryzacja
Samo uwierzytelnienie klienta wobec serwera nie jest wystarczające, aby zapewnić
bezpieczeństwo. Możliwe jest w ten sposób ograniczenie dostępu do pewnych rejonów
aplikacji, aż do momentu zalogowania. Złożone aplikacje internetowe dają użytkowni-
kom dostęp do ogromu treści. Czasem aplikacja przechowuje jednak dane, które nie po-
winny być oglądane przez osoby niebędące z nimi powiązane. Np. aplikacja bankowa
umożliwia przeglądanie historii swoich transakcji pod adresem http://przykladowy-
bank/user/transactions. Użytkownik może wyświetlić szczegóły pojedynczej transak-
cji pod adresem http://przykladowy-bank/user/transactions/:id, gdzie :id to nu-
mer transakcji. Nie byłoby jednak właściwe, gdyby inna osoba mogła uzyskać dostęp
43. Rozdział 2. Wprowadzenie teorytyczne 43
do tych danych, po prostu przeszukując wszystkie możliwe numery transakcji. Kolejnym
przykładem może być blog gdzie pod adresem http://blog.com/recent posts możemy
wszystkie posty utworzone w ciągu ostatnich 5 dni. Co, jednak jeśli chcemy, aby tylko
użytkownicy ze specjalnymi uprawnieniami mogli wyświetlać posty oznaczone jak taj-
ne? Problemem do rozwiązania jest w tym przypadku filtrowanie treści w zależności od
uprawnień użytkownika. Wracając do przykładu z najnowszymi postami. Przekazując do
widoku kolekcję wszystkich postów, należy wziąć pod uwagę uprawnienia (np. czy mamy
do czynienia z administratorem) użytkownika (w tym przypadku zmienna current user).
Listing 2.27: Przykład modelu ”Post” i zabezpieczonego kontrolera
1 class Post < ActiveRecord :: Base
2 scope :recent , -> { where(’created_at BETWEEN ? AND ?’, 5. days.ago ,
Time.now) }
3 scope :visible_by , -> (client) { client.admin ? all : where(
supersecret: false) }
4 end
5
6 class PostsController < BaseController
7 def index
8 render Post.visible_by(current_user).recent
9 end
10 end
Kolejnym problemem, który trzeba rozważyć w powyższym przykładzie, jest możliwość
edycji posta. Funkcjonalność edycji utworzonych przez siebie bytów jest konieczna w więk-
szości przypadków. Nie chcemy jednak aby ktoś inny mógł edytować nasze posty. Zabez-
pieczenie kontrolera na powyższy przypadek wygląda następująco:
Listing 2.28: Przykład zabezpieczenia metody ”update” kontrolera
1 class PostsController < BaseController
2 before_action : check_ownership !, only: :update
3 expose (: post)
4
5 def update
6 post. update_attributes (body: params [: body ])
7 redirect_to post_path(@post)
8 end
9
10 private
11
12 def check_ownership !
13 fail UnauthorizedAccess unless post.author == current_user
44. Rozdział 2. Wprowadzenie teorytyczne 44
14 end
15 end
Przed wykonaniem akcji update serwer sprawdza, czy użytkownik domagający się takiej
akcji jest autorem posta. Jeśli nie jest, wzbudza wyjątek UnauthorizedAccess, a następ-
nie może wyświetlić użytkownikowi informację, że może edytować tylko swoje posty. Jeśli
test na autorstwo przejdzie pozytywnie, pozwala wykonać żądaną akcję. Powyższe za-
bezpieczenia wydają się trywialne, ale są bardzo ważne do zachowania bezpieczeństwa
całej aplikacji. Edycja czyjegoś wpisu nie jest może dużą stratą, ale wszędzie tam gdzie
system obraca jakimikolwiek środkami pieniężnymi, stawka jest dużo większa. Mimo tego
programiści bardzo często niedostatecznie zabezpieczają dostęp do usług.
Biblioteką, która podchodzi do powyższego zagadnienia kompleksowo, jest cancan-
can[22]. Jej twórcy zaproponowali deklaratywne podejście do uprawnień użytkowników.
Zamiast w każdym miejscu sprawdzać, czy dane konto ma rzeczywiście dostęp do konkret-
nego zasobu, definiuje się jego uprawnienia w jednym pliku. Podczas dostępu do zasobów
biblioteka sprawdza, czy żądany obiekt znajduje się na liście uprawnień. Przykład definicji
uprawnień:
Listing 2.29: Przykład definicji uprawnień w bibliotece ”cancancan”
1 class Ability
2 include CanCan :: Ability
3
4 def initialize(user)
5 user ||= User.new # guest user (not logged in)
6 if user.admin?
7 can :read , Post
8 else
9 can :read , supersecret: false
10 end
11 end
12 end
Klasa Ability definiuje zdolności użytkownika. Powyższy definicja jest intuicyjna i zbli-
żona do pseudokodu. Może być zrozumiana również przez kadrę menadżerską, co jest dużą
zaletą i ułatwia komunikację w zespołach. Jest to jeden z atutów języka Ruby.
45. Rozdział 3
Opis technologii
W niniejszym rozdziale zostaną wprowadzone analizowane technologie do tworzenia
aplikacji klienckich typu SPA: ReactJS oraz AngularJS (rozdziały 3.1 i 3.2). W dalszej
częsci przedstawione zostaną technologie serwerowe, mianowicie: Rails, Sinatra oraz Gra-
pe (3.3, 3.4, 3.5). Wprowadzenie zawierać będzie informacje ogólne, krótki rys historyczny,
a także czynniki motywujące autorów powyższych technologii do użycia takich czy innych
rozwiązań. Autorzy pracy będą starali się wskazać najbardziej charakterystyczne cechy
analizowanych bibliotek. Duży nacisk zostanie położony również na architekturę przesta-
wianych rozwiązań, z uwzględnieniem wzorców projektowych i modelowania przepływu
informacji. Tam gdzie to konieczne, wprowadzone zostaną szczegóły użytych algorytmów
(Diff Algorithm 3.1.6). Konkretne elementy bibliotek, zostaną podparte przykładami ko-
du, czy to z zaimplementowanych aplikacji, czy dokumentacji.
3.1 ReactJS
Biblioteka ReactJS napisana w języku JavaScript, przez programistę Facebooka Jorda-
na Walke, jest z całą pewnością najciekawszą pozycją w niniejszej pracy. Celem przyświe-
cającym jej twórcom było usprawnienie projektowania interfejsów użytkownika, poprzez
wprowadzenie deklaratywnych, modularnych i dajacych się ponownie używać komponen-
tów. Jeśli rozważalibyśmy jej umiejscowienie w modelu MVC - byłaby to litera V - widok.
Wydaje się, że odpowiedzialność widoku w powyższym wzorcu jest dość mała. React jed-
nak prezentuje rewolucyjne, a także dość kontrowersyjne podeście, wymuszając zmiany
na całym przepływie danych. Sama biblioteka jest dość minimalistyczna. Nie wymusza
na programiście konkretnej architektury projektu. Nie jest wskazany preferowany spo-
sób komunikacji z serwerem, struktura klas czy folderów. Twórcy Reacta proponują co
prawda wzorzec o nazwie ”Flux” (o którym będzie też mowa w tym rozdziale), ale jest
45
46. Rozdział 3. Opis technologii 46
to opcjonalne. Takie podejście daje dużą elastyczność, jest jednak sporym wyzwaniem.
Samodzielne dobieranie komponentów i projektowanie architektury wymaga pewnego do-
świadczenia. Jako materiał do analizy w niniejszej pracy posłużyło połączenie Reacta,
Fluxa i lekkiej biblioteki BackboneJS (tylko elementy do komunikacji z API). Więcej o
tych technologiach można znaleźć w [9] i [13].
3.1.1 Deklaratywny charakter widoków
Opisywany w rozdziale 3.2 AngularJS korzysta z rozwiązania o nazwie ”two-way data
binding”. Jest to funkcjonalność wbudowana w tę bilbiotekę (podobne rozwiązanie zasto-
sowano w EmberJS - więcej informacji o nim w [10]). Przez długi czas był to standard
wiązania danych z szablonem. Zarządzanie stanem obiektów po stronie klienta i ich syn-
chronizacja z serwerem jest zadaniem trudnym. ”Two-way data binding” zdaje się cudow-
nym rozwiązaniem. Programista nie musi ręcznie kontrolować wartości przechowywanych
w formularzach i zapisywać ich w specjalnych obiektach. Po powiązaniu elementu inter-
fejsu użytkownika z konkretnym modelem każda zmiana jednego z nich będzie skutkowała
też zmianą drugiego. Twórcy Reacta przekonują, że takie wiązanie danych, jest źródłem
wielu problemów. Ciężko stwierdzić jak głęboko może sięgać łańcuch takich zmian. Za-
miast tego proponują prostsze rozwiązanie - ”unidirectional data flow”. Każdy komponent
posiada funkcję ”render”. W niej za pomocą biblioteki JSX definiowany jest wygląd tego
elementu, poniżej przykład z zaimplementowanej aplikacji:
Listing 3.1: Przykład funkcji ”render” dla elementu listy rzeczy do zrobienia
1 render: function () {
2 var todo = this.props.todo;
3 var iClassName = ’fa fa -2x action check ’
4 var trClassName = ’row ’
5 if (todo.get(’completed ’)) {
6 iClassName += ’fa -check -square ’;
7 trClassName += ’bg -success ’;
8 } else {
9 iClassName += ’fa -square -o’;
10 };
11 if (this.props.showCompleted === false && todo.get(’completed ’) ===
true) {
12 return null
13 };
14
15 return (
16 <tr className ={ trClassName}>
47. Rozdział 3. Opis technologii 47
17 <td >
18 <i className ={ iClassName} onClick ={ this. _onToggleComplete }/>
19 </td >
20 <td >{ todo.get(’name ’)}</td >
21 <td className=’inline ’>
22 <i className=’fa fa -times -circle fa -2x delete pull -right
action close ’ onClick ={ this. _onDestroyClick }/>
23 </td >
24 </tr >
25 );
26 }
Programista definiuje wygląd komponentu tylko raz. Jeśli zajdą jakieś zmiany w przeka-
zywanych parametrach lub jego stanie, cały DOM zostanie przerenderowany.
3.1.2 Props
W powyższym przykładzie pokazane zostało użycie funkcji Reacta o nazwie ”props”,
pochodzącej od słowa ”properties”. Służy ona do przekazywania danych z widoku nad-
rzędnego do osadzenia w szablonie JSX (hierarchia widoków może być złożona, zgodnie ze
wzorcem projektowym o nazwie kompozyt, opisanym szerzej w [11]). Co ciekawe można
przekazywać w ten sposób nie tylko prymitywy, ale też złożone obiekty, np. JSON czy też
funkcje. Przekazanie funkcji w taki sposób jest również dobrą praktyką - jest to zgodne ze
wzorcem projektowym obserwator [11]. Zapobiega to zbędnemu i szkodliwemu wiązaniu
obiektów. Obserwowany obiekt nie ma dostępu do rodzica, potrafi wywyłać tylko metodę,
którą otrzymał. Poniżej przykład takiego działania:
Listing 3.2: Przekazanie funkcji do subkomponentu
1 // Przekazanie funkcji do subkomponentu
2 return (
3 <div id=’main ’>
4 <TodoTextInput onSave ={ this.createTodo }/>
5 ...
6 </div >
7 );
8
9 // Wywolanie funkcji przekazanej z komponentu glownego
10 _save: function () {
11 this.props.onSave(this.state.value);
12 this.setState ({
13 value: ’’
48. Rozdział 3. Opis technologii 48
14 });
15 }
Należy zaznaczyć, że wszystkie dane przekazane przez ”props” są niezmienne (immutable)
dla komponentu, który je otrzymał.
3.1.3 State
Bardzo często zdarza się, że elementy interfejsu użytkownika muszą reagować na inte-
rakcje. Przykładowo przycisk ”Lubię to” na Facebooku, zmienia swój wygląd po kliknię-
ciu, komunikując użytkownikowi wykonanie akcji. Jak zostało wspomniane w poprzednim
podrozdziale dane pochodzące z ”props” są niezmienne. ”State” jest miejscem przecho-
wywania danych dynamicznie zmieniających się podczas cyklu życia komponentu. Do
ustawienia początkowego stanu służy funkcja ”getInitialState”. Poniżej przykład użycia
z aplikacji ”Todo”:
Listing 3.3: Ustawienie początkowego stanu komponentu w ReactJS
1 getInitialState : function () {
2 return {
3 value: this.props.value || ’’
4 };
5 }
W trakcie działania aplikacji, komponent może zmienić swój stan, dzięki funkcji ”setSta-
te”, poniżej przykład użycia:
Listing 3.4: Zmiana stanu komponentu w ReactJS
1 // Element UI ktory jest zmieniany
2 <input className="form -control fw" autoFocus ={ true} value ={ this.state
.value} onChange ={ this._onChange} placeholder="Task to be done ..."
type="name"/>
3
4 // Funkcja wywolywana po zmianie
5 _onChange: function(/* object */ event) {
6 this.setState ({
7 value: event.target.value
8 });
9 },
49. Rozdział 3. Opis technologii 49
Rysunek 3.1: Diagram obrazujący przepływ sterowania w architekturze Flux, [źródło:
https://github.com/facebook/flux].
3.1.4 Flux
Przepływ danych narzucony przez Reacta jest bardzo przyjazny dla programisty. Lu-
dzie nie są przystosowani do pracy nad kilkoma zadaniami jednocześnie. W dwustronnym
przepływie danych analiza zachowania aplikacji często wymagania śledzenia długiego łan-
cucha zmian. W takim przypadku łatwo o pomyłkę. React proponuje zastosowanie wzorca
SSoT - jedynego źródła prawdy (ang. Single Source of Truth). Pomysł na architekturę
całej aplikacji klienckiej dostarcza właśnie Flux (szerzej opisany w [1]). Przepływ danych
we Fluxie został przedstawiony na rysunku 3.1: Flux składa się z kilku głównych elemen-
tów (rysunek 3.1). Przepływ sterowania odbywa się w pętli po każdym z nich. Poniżej ich
opis (ułożone są chronologicznie, od punktu rozpoczęcia interakcji przez użytkownika, aż
do pełnego przerysowania dokumentu HTML):
• Widok
Komponent Reacta. Wchodzi w interakcje z użytkownikiem, renderuje HTML. Opi-
sany szerzej w 3.1.1
• Action Creator
Dodatkowa warstwa abstrakcji służąca do tworzenia akcji. Została utworzona po
to, aby programista inicjował zmiany danych tylko w jednym miejscu. Jego funk-
cje mogą przyjmować argumenty, wynikiem ich działania jest przekazanie akcji do
Dispatchera
50. Rozdział 3. Opis technologii 50
• Dispatcher
Kolejna warstwa abstrakcji. W tym miejscu podejmowane są decyzje, co należy
zrobić z akcją. Zazwyczaj przekazywane są dalej, jednak możliwe są inne możliwości.
Poniżej wycinek kodu z Dispatchera zaimplementowanej aplikacji. Akcja o typie
”TODO CREATE” jest wywoływana tylko wtedy, gdy jej atrybut ”text” nie jest
pusty:
Listing 3.5: Część kodu obiektu Dispatcher
1 switch(action.actionType) {
2 case TodoConstants. TODO_LOAD_ALL_COMPLETE :
3 TodoStore.emitChange ();
4 break;
5
6 case TodoConstants.TODO_CREATE:
7 text = action.text.trim ();
8 if (text !== ’’) {
9 create(text);
10 }
11 break;
12 // ...
13 }
• Store
Centralny punkt składowania danych, również miejsce komunikacji z API. W zaim-
plementowanej aplikacji do tego celu została użytka biblioteka BackboneJS (opisana
szerzej w 3.1.8). Po wykonaniu działań na danych, obiekt Store emituje globalne zda-
rzenie, na które reagują wszystkie komponenty. Cały interfejs, wszystkie formularze,
przyciski, pola tekstowe zostają wyrenderowane ponownie. Wydaje się, że jest to nie-
efektywne działanie i przy standardowym pełnym odświeżeniu tak by było. Jednak
jedna bardzo ważna właściwość Reacta sprawia, że takie działanie ma sens.
3.1.5 Virtual DOM
DOM został stworzony do reprezentacji dokumentów HTML niezależnie od platfor-
my i języka. Ma strukturę drzewa. Przez długi czas był stosowany tylko do wyświetlenia
informacji. Nie został stworzony z myślą o złożonych operacjach dodawania, usuwania i
zmieniania węzłów. Te uwarunkowania czynią działania na nim wąskim gardłem (co po-
kazały testy przeprowadzone przez autorów pracy 4, a także przeprowadzone przez inne
osoby). React został zaprojektowany z myślą o przejrzystej architekturze i łatwo roz-
51. Rozdział 3. Opis technologii 51
wijalnym, modularnym kodzie. Rozpoznawalny jest jednak chyba głównie dzięki swojej
wydajności. Z każdą zmianą stanu, React przerenderowuje całą aplikację. Jednak robi to
w specyficzny sposób.
Virtual DOM to dodatkowa warstwa abstrakcji, lekka kopia prawdziwego DOM. Wszystkie
zmiany stanu są odzwierciedlane najpierw w Virtual DOM. Operacje na nim są szybsze,
gdyż obiekt ten jest tylko wirtualny - nie musi zostać przerysowywany po każdej opera-
cji. W przypadku zwyczajnego DOM bardzo często zdarza się, że przeplatając operacje
odczytu i zapisu, wymagany jest tzw. ”reflow” i to nawet kilkukrotny. Ilustruje to listing
3.6:
Listing 3.6: Operacje na DOM przykład
1 // Wejscie
2 <div>
3 <div>a</div>
4 <div>b</div>
5 </div>
6
7 // Wyjscie
8 <div>
9 <div>c</div>
10 <div>d</div>
11 </div>
W standardowej manipulacji na DOM, przeglądarka wykonałaby ”reflow” 4 razy. Przy
operowaniu na znaczącej liczbie węzłów liczonej w tysiącach, byłoby to już zauważalne
dla użytkownika. React oblicza minimalną liczbę zmian między drzewami (diff algorithm),
a dopiero później aplikuje je do DOM. Dlatego fakt, że cała aplikacja przerysowuje się z
każdą zmianą danych, nie niesie za sobą spadku wydajności, a jedynie wzrost przejrzystości
architektury.
3.1.6 Diff Algorithm
Operacja obliczania minimalnej liczby transformacji jednego drzewa w drugie ma zlo-
żoność rzędu O(n3
). W przypadku manipulacji tysiącem węzłów (co nie jest rzadkim
przypadkiem), koniecznie jest wykonanie jednego miliarda operacji. Taka złożoność jest
jednak nie do przyjęcia. Autorzy Reacta oparli swój algorytm na dwóch dodatkowych
założeniach:
• Dwa komponenty tej samej klasy wygenerują podobne drzewa, a dwa komponenty
różnych klas wygenerują różne drzewa.
52. Rozdział 3. Opis technologii 52
• Możliwym jest dostarczenie unikalnych kluczy, dla elementów niezmiennych przy
ponownym rysowaniu.
Algorytm porównywania drzew ma rekurencyjny charakter. Zatem chcąc operować na
całym drzewie, należy najpierw zdefiniować operację na jednym węźle. React rozróżnia 3
typy takiej operacji:
• Różne typy węzła
Listing 3.7: React transformacja węzłów różnych typów
1 renderA: <div />
2 renderB: <span />
3 => [removeNode <div />], [insertNode <span />]
W takim przypadku React nie podejmuje próby negoncjacji atrybutów, czy węzłów
potomnych. Cała gałąź drzewa zostaje usunięta i zastąpiona nową.
• Zgodne typy węzła
Listing 3.8: React transformacja węzłów o takich samych typach
1 renderA: <div style ={{ color: ’red’}} />
2 renderB: <div style ={{ fontWeight: ’bold ’}} />
3 => [removeStyle color], [addStyle font -weight ’bold ’]
Tutaj bardziej opłacalnym działaniem staje się zmiana atrybutu. Należy dodać, że
style elementu (które w HTML są po prostu zmienną typu String), traktowane są
jako tablica asocjacyjna.
• Inteligentne operowanie na liście
Najbardziej problematyczne jest obliczanie różnic w liście węzłów. Przypadek ze
stałą liczbą elementów jest prosty - React porównuje po prostu obie listy, element
po elemencie. Złożoność operacji to O(n). Dodanie elementu na końcu listy, również
nie jest skomplikowane (złożoność nie wzrasta). Co jednak w przypadku dodawania
elementów na początku lub w środku listy? Rozwiązaniem tego problemu mogłoby
być obliczenie odległośi Levenshteina (złożoność O(n2
)). Jednak ten algorytm mimo
wyższej złożoności, nie pozwala na wykrycie zmiany kolejności elementów. Algoryt-
my, które na to pozwalają, mają jeszcze większą złożoność.
53. Rozdział 3. Opis technologii 53
Twórcy Reacta zaproponowali prostsze rozwiązanie. Zamiast skomplikowaych ob-
liczeń na elementach listy, nadali każdemu z nich unikalny (w lokalnym zakresie)
klucz, niezmienialny przy kolejnych przerysowaniach. Dzięki temu iterowanie po li-
ście sprowadza się do iteracji po wszystkich kluczach tablicy asocjacyjnej (złożoność
O(n)).
3.1.7 JSX
Kolejną rzeczą wyróżniającą Reacta jest biblioteka JSX do pisania szablonów. Należy
zaznaczyć, że jest całkiem opcjonalna. Dzięki niej programista może osadzać elementy o
bardzo podobnej składni do HTMLa, w kodzie JavaScript. Różnice obu podejść na listingu
3.9
Listing 3.9: Porównanie budowy szablonów z użyciem JSX i bez
1 // Komponent HelloMessage zbudowany z pomoca JSX
2 var HelloMessage = React.createClass ({
3 render: function () {
4 return <div >Hello {this.props.name}</div >;
5 }
6 });
7 React.render(<HelloMessage name="John" />, mountNode);
8
9 // Komponent HelloMessage zbudowany w czystym JS
10 var HelloMessage = React.createClass ({ displayName: "HelloMessage",
11 render: function () {
12 return React.createElement("div", null , "Hello ", this.props.name
);
13 }
14 });
15 React.render(React.createElement(HelloMessage , {name: "John"}),
mountNode);
3.1.8 Backbone
React, czy też architektura Flux, nie dostarcza gotowych narzędzi do komunikacji z
API. Programista ma więc wolny wybór. Istnieje możliwość używania czystego JavaScrip-
tu, jQuery lub dodatkowej biblioteki. Bardzo często spotyka się implementacje Fluxa wraz
z minimalistyczną biblioteką BackboneJS, która dostarcza dodatkową warstwę abstrakcji,
w postaci modeli i kolekcji. Dzięki nim nie ma potrzeby konstruowania skomplikowanych
54. Rozdział 3. Opis technologii 54
zapytań XHR. Wszystko to zostało opakowane w wygodne funkcje, np. fetch() - do po-
bierania listy obiektów, save() - zapisywania stanu obiektu na serwerze, czy destroy() -
do usuwania obiektu.
3.2 AngularJS
Angular z języka angielskiego znaczy w tłumaczeniu dosłownym kanciasty, kątowy,
narożny. Czy nazwa tego frameworka mówi coś więcej na temat jego funkcjonalności, ten
rozdział postara się na to i inne pytania odpowiedzieć. AngularJS jest to zestaw narzę-
dzi JavaScript do tworzenia aplikacji frontendowych typu SPA. Koncepcja SPA została
omówiona w rozdziale 2.3. Historia Angulara sięga roku 2009. W początkowym etapie
projekt ten był zaledwie małym prywatnym pomysłem realizowanym przez pracowników
firmy Google Adama Abronsa i Misko Hevery’ego. Zarząd koncernu Google uznał projekt
AngularJS na tyle ciekawy, że otrzymał oficjalne wsparcie wraz z zespołem programi-
stów który miał za zadanie go rozwijać. W 2012 roku po raz pierwszy framework został
upubliczniony. Wiele zalet Angulara zaczerpnięto ze sprawdzonych metod, co pozwoliło
na stworzenie wydajnego i efektywnego frameworka, który zapewnia nieskomplikowaną
strukturę, szerokie spektrum możliwości oraz wygodne metody testowania. Bardzo duży
udział w rozwoju Angulara ma także społeczność internetowa. Dzięki ciągłej wymianie do-
świadczeń pomiędzy programistami AngularJS framework jest poddawany ciągłym ulep-
szeniom. Programiści chcący poznać wady i zalety Angulara w praktyce powinni odwiedzić
stronę [8] na której znajdują się dokumentacje, kursy, poradniki, opisy API i inne rzeczy
przydatne dla deweloperów. Przykłady aplikacji napisanych w Angularze znajdziemy na
stronie https://builtwith.angularjs.org/. Bardzo istotnym aspektem dotyczącym Angula-
ra jest fakt, iż jest on publikowany na licencji MIT, co oznacza, że jest w pełni darmowy.
AngularJS pozwala w teorii w szybki i łatwy sposób budować warstwę kliencką aplikacji
internetowych. Koncepcja omawianego frameworka zakłada tzw. MVW, czyli organizację
aplikacji w obrębie model-widok-cokolwiek dzięki, czemu można pogodzić idee JavaScript
z modelem MVC. W ostatnim czasie omawiany framwework stał się bardzo popularny,
co zaobserwować możemy na wykresie 3.2 oraz zaczął być wykorzystywany w aplikacjach
typu enterprise. Za pomocą Angulara stworzone zostały między innymi YouTube na Play
Station 3 oraz platforma muzyczna VEVO.
AngularJS posiada kilka interesujących rozwiązań, których próżno szukać w innych
frameworkach. Oto niektóre z nich:
55. Rozdział 3. Opis technologii 55
Rysunek 3.2: Porównanie ilości ofert pracy dla popularnych platform klienckich typu
MVC, [źródło: http://www.indeed.com/jobtrends].
3.2.1 Kompilator HTML
Główną cechą odróżniającą Angulara od reszty tego typu narzędzi to fakt posiadania
własnego kompilatora HTML. Dzięki temu można w prosty sposób rozszerzyć HTML o
nowe zdeiniowne wcześniej tagi, które mogą realizować nowe funkcje.
3.2.2 Dwustronne wiązanie danych
W przeszłości, zanim technologia AJAX opisana w rozdziale 2.2, była szerzej wykorzy-
stywana, do konstruowania interfejsu użytkownika wykorzystywane były narzędzia typu
PHP, Rails, ASP.NET lub inne. Serwer odpowiadał za generowanie widoku HTML przed
prezentacją go dla użytkownika. Dzięki wykorzystaniu biblioteki jQuery dla języka Ja-
vaScript można odświerzać wybrane elementy DOM bez konieczności przeładowywania
całej strony. W HTML wstrzykiwane są dane, a następnie wynik jest dodawany do do-
wolnej części DOM z wykorzystaniem atrybutu ”inneHtml” dla właściwego elementu. W
koncepcji one way binding informacje są pobierane z modelu, który służy za swego ro-
dzaju kontener do przechowywania danych i wysyłane do widoku, który ma za zadanie je
wyświetlić. Natomiast nie istnieje możliwość wpływania na model z poziomu widoku przy
tym podejściu. Zgodnie z diagramem 3.3 dane synchronizowane są tylko z widokiem, to
znaczy, że programista musi zaimplementować mechanizm synchronizacji widoku z mo-
delem, gdy przykładowo użytkownik wprowadzi dane do aplikacji. Przykładem takiego
frameworka jest BackboneJS. Problem ten został rozwiązany w Angularze. Omawiany
56. Rozdział 3. Opis technologii 56
Rysunek 3.3: Jednostronne wiązanie danych w AngularJS, [źródło: https://docs.
angularjs.org/guide/databinding].
framework posiada tak zwane dwustronne wiązanie danych, które pozwala na synchroni-
zację stanu widoku i modelu po stronie JavaScript. Wystarczy dodać prostą deklarację,
która zdefiniuje, jakie obiekty po stronie kontrolera lub widoku będą ze sobą powiązane.
W tej deklaracji wykorzystujemy obiekt $scope oraz dyrektywę ”ng-model”. Na rysun-
ku 3.4 możemy zobaczyć, że gdy dajmy na to, zostanie wprowadzona zmiana w widoku,
przykładowo wprowadzimy tekst w polu formularza, to dane te automatycznie zostaną
zsynchronizowane z modelem. Podobnie działa to w drugą stronę. To znaczy, gdy przy-
kładowo dane w modelu zmienią się w wyniku odpowiedzi na zapytanie do API serwera,
to mechanizm podwójnego wiązania uaktualni te dane w widoku, czyli w warstwie pre-
zentacji dla użytkownika. Dzieki temu rozwiązaniu programista jest odciążony z dbania o
aktualny stan danych w każdym miejscu w aplikacji.
3.2.3 Obiekt $scope
Charakterystyczną cechą Angulara jest obiekt $scope. Służy on do transportowania
modelu pomiędzy widokiem a kontrolerem. Odpowiada też za nasłuchiwanie zdarzeń lub
zmian zachodzących w modelu, a także za propagację tych zmian. Pomimo że $scope jest
traktowany przez twórców omawianego frameworka, w sposób wyjątkowy to wpsomniany
obiekt jest tak naprawdę zwykłym obiektem typu POJO, którego atrybutami możemy
57. Rozdział 3. Opis technologii 57
Rysunek 3.4: Dwustronne wiązanie danych w AngularJS, [źródło: https://docs.
angularjs.org/guide/databinding].
dowolnie manipulować. Warty uwagi jest fakt, iż generalnie $scope jest tworzony i wstrzy-
kiwany w sposób automatyczny bez udziału programisty. AngularJS w początkowej fazie
ładowania aplikacji tworzy powiązanie między tagiem zawierającym dyrektywę ”ng-app”
a wszystkimi elementami znajdującymi się poniżej obiektu $scope. Najwyżej w hierarchii
obiektów znajduje się $rootScope, który jest rodzicem wszystkich obiektów $scope. Każ-
da aplikacja posiada tylko jeden obiekt typu $rootScope, po którym dziedziczą wszystkie
inne obiekty $scope. W fazie początkowej ładowania aplikacji tworzona jest nadrzędna
instancja $rootScope. Dobrą praktyką jest relatywnie mała liczba atrybutów przypiasna
do niego, gdyż pełni on rolę obiektu golobalnego, który powinien zawierać tylko rzeczy
najistotniejsze. Przy korzystaniu z dużej ilości bibliotek zewnętrznych pojawia się ryzyko,
iż wystąpi zbieżność nazw medod lub atrybutów przypisanych do obiektów typu $rootSco-
pe dlatego też unikanie tego rodzaju sytuacji może zaoczędzić programiście wiele czasu
i nerwów. W związku z tym, że zmienna $scope jest inicjalizowana w procesie począt-
kowego ładowania aplikacji, czyli tzw. bootstrap elementy przypisane tej zmiennej są od
początku dostępne w widoku. Na listingu 3.10 zaczerpniętego z książki [7] mamy przy-
kład przypisywania funkcji i atrybutów do modelu po stronie kontrolera. W omawianym
przykładzie został zdefiniowany atrybut dateOriginal w globalnym obiekcie $rootScope a