#Euruko2017 - Things I learned the Hard Way Building a Search Engine

Rozmowa i opinie po prezentacji “Things I learned the Hard Way Building a Search Engine”



Dima Boyko (Software Engineer, inFakt.pl): Hello. Byłem na jedzeniu, na lunchu i niestety nie zdążyłem na ostatnią prezentację. Powiedz, co tam było ciekawego. O czym w ogóle była ta prezentacja?

Alicja Cyganiewicz (Ruby developer, inFakt.pl): Wiesz co? To była prezentacja o wyszukiwaniu i wyszukiwaniu jak chodzi o teksty. Tzn. w jaki sposób możemy zwrócić użytkownikowi tekst, który najlepiej będzie odpowiadał temu, czego szuka. I najśmieszniejsze jest to, że właściwie robimy to codziennie, bo każdy z nas czegoś wyszukuje w Google i nigdy chyba się nie zastanawia nad tym jakim cudem faktycznie dostajemy to czego chcemy. A z wyszukiwaniem tekstu jest naprawdę ciężka sprawa, bo masz semantykę, której nie da się zmierzyć liczbowo. Jeżeli np. w zwykłej aplikacji wyszukujesz, nie wiem, fakturę, to jesteś w stanie podać jakieś konkretne jej dane, natomiast przy przeszukiwaniu tekstu wchodzą słowa, wchodzą jakieś takie znaczeniowe rzeczy i ciężko jest to zmierzyć w taki sposób, żebyśmy byli w stanie podać jakąś taką liczbową wartość. I okazało się, o czym ja np. wcześniej nie wiedziałam, nie zdawałam sobie sprawy, że jesteśmy w stanie w jakiś sposób przedstawiać liczbowo podobieństwo pomiędzy tym czego ktoś wyszukuje a tym co mamy w tekście. Okazało się… wiesz, gdyby się tak nad tym samemu zastanowić, to myślę, że też byłbyś w stanie wskazać taki jeden przykładowy licznik, co może być ważne. I np. ja wpisuję 3 słowa do wyszukiwania, mam masę tekstów, to zastanów się, co mógłbyś liczyć.

Dima: Ilość wystąpień tych słów w tekstach.

Alicja: OK. Czyli chodzi ci o to, jak często występowały w tekstach, tak?

Dima: To jest najprostsze.

Alicja: Widzisz i to jest trochę złudne. Dlatego, bo okazuje się, że tak naprawdę, jeżeli masz tekst, to każde następne wystąpienie tego samego słowa w tekście jest coraz mniej znaczące. Tzn., jakby największą wartość ma pierwsze wystąpienie danego słowa, bo to oznacza, że jakby ten tekst, faktycznie mówi na ten temat, ale każde kolejne wystąpienie ma coraz mniejsze znaczenie, bo możemy się też spodziewać, że skoro tekst jest na jakiś dany temat, powiedzmy szukasz tekstu o programowaniu w Ruby, to słowo kluczowe: Ruby, będzie się powtarzało w tym tekście, no i każde jakby kolejne wystąpienie będzie mniej znaczące. Ale faktycznie masz rację, że jakby częstotliwość jest tam jakimś jednym z mierników. Ale to co jest w gruncie rzeczy najważniejsze, to czy w ogóle dane słowo występuje i na prezentacji był np. pokazany sposób przełożenia występowania słów w tekście na wektory. To się wydaje zupełnie abstrakcyjne, bo z jednej strony masz tekst, czyli coś zupełnie takiego jakby odciętego od nauk ścisłych, a z drugiej strony masz przedstawienie tego wektorowe. Dzięki temu możemy np. wyliczyć taką pierwszą podstawową tzw. relevance, czyli to czy tekst odpowiada temu czego ktoś wyszukuje. I to jest ten taki podstawowy miernik. Później tak jak mówisz wchodzi częstotliwość występowania…

Dima: Ale pewnie jest zależna też od długości tekstu.

Alicja: Dokładnie tak.

Dima: Bo im dłuższy mamy tekst tym statystycznie prawdopodobieństwo wystąpienia tego słowa jest większe.

Alicja: Dokładnie tak. I dlatego jest też algorytm, który jakby coraz bardziej uszczegóławia to podobieństwo wyszukiwanej frazy do tekstów i tam właśnie tak jak mówisz, teksty, które są dłuższe, będą w cudzysłowie karane, czyli ilość wystąpień tych słów, będzie coraz mniej brana pod uwagę, no bo wiadomo, tak jak powiedziałeś, dłuższy tekst, większe prawdopodobieństwo. Natomiast te krótkie teksty… jeżeli w krótkim tekście wystąpi ci to chociaż raz, to i tak jest to dosyć ważne wystąpienie. Prawda? Można w ten sposób powiedzieć. Tylko że, tak jak powiedziałam, z tekstami najgorsze jest to… to jest semantyka, to jest coś nad czym do końca nie panujemy, dlatego trzeba też walczyć z synonimami. Warto zastanowić się właśnie nad tym jakie podobne słowa mogły tam wystąpić, jakie słowa z rodziny mogły tam wystąpić i tutaj wchodzi dosyć dużo takiej powiedzmy manualnej pracy. W sensie nie wszystko jesteśmy w stanie mierzyć algorytmami, bo ktoś musi wprowadzić, nie wiem, tzw. derywaty, czyli powiedzmy końcówki słów, w jaki sposób tam dane słowo może się odmieniać, żebyśmy później byli w stanie znaleźć to w tekście. I to jest coś moim zdaniem najtrudniejszego. To nie było powiedziane może na prezentacji, ale tak mi się wydaje, że to jest chyba najtrudniejsza część, jak chodzi o wyszukiwanie i przeszukiwanie tekstów, że w dużej mierze opiera się na danych, które my musimy wprowadzić, szczególnie, że każdy język będzie rządzi się swoimi prawami. Prawda? Np. język angielski nie będzie miał takiej odmiany przez przypadki jak ma język polski, więc w tym momencie jakby ta baza się troszeczkę zawęża.

Dima: A czy zdarzyło się w twojej praktyce implementować, albo używać jakiś gotowych rozwiązań do wyszukiwania tekstu? Czy znasz jakieś gotowe rozwiązania i czy mogłabyś coś polecić?

Alicja: Znaczy wydaje mi się, że takim najbardziej znanym rozwiązaniem jest Elasticsearch. On też był wspomniany na prezentacji. Natomiast przyznaję, że ja akurat z tego nigdy nie korzystałam, więc ciężko jest mi powiedzieć. Ale z tego co słyszałam to jest to, jak mówię, jedno z najlepszych narzędzi, jakie można używać w tym kontekście.

Dima: OK. Czyli generalnie message prezentacji jest taki, że nie musisz używać natural language processing dlatego żeby wyszukiwać tekst, tylko można przełożyć to na standardową matematykę.

Alicja: W pewnym zakresie tak, tylko, że dochodzi do tego masa kolejnych powiedzmy faktorów takich, które na to wpływają. One nawet nie były wszystkie wymienione, bo nie da się tak na prawdę wszystkich wymienić. Chociażby to, jak dostajesz frazę do wyszukiwania, to teraz możemy się zastanawiać, czy wystąpiła dokładnie w tej samej kolejności, czy wystąpiło dokładnie to złożenie tych słów koło siebie. Czasami to nawet jest gorsze, dlatego bo jeżeli wpisujesz tylko 3 słowa klucze, a nie wpisujesz zdania jakby takiego ludzkiego, no to jeżeli to wystąpi koło siebie, to ten tekst znaczy jakby ma niewielką wartość, więc jakby pojawiają się kolejne takie elementy, o których trzeba by było pamiętać pisząc takie wzory pozwalające na przeliczenie podobieństwa tekstu do wyszukiwanej frazy.

Dima: Czyli takie algorytmy muszą być bardzo trudne do zaimplementowania.

Alicja: Moim zdaniem tak i z prezentacji też to wynikało.

Dima: A jak uważasz, takie podejście wektoryzacji matematycznie pasuje tylko dla wyszukiwania tekstów, czy innych typów informacji też, pikseli np.

Alicja: Podejrzewam, że tym bardziej dla innych typów informacji. Że jeżeli coś jesteśmy… moim zdaniem tekst i w ogóle ludzka mowa, jako coś naturalnego jest tak trudne do ujęcia w ścisłe ramy, że jeżeli jesteśmy w stanie to przełożyć na matematykę, to tym bardziej jesteśmy w stanie wyszukiwać, tak jak mówisz, piksele.

Dima: A odnośnie jeszcze wyszukiwania tekstów. Kiedyś zauważyłem, że różne systemy, różne wyszukiwarki np. Google i DuckDuckGo, bardzo różnie interpretują zapytanie, które wprowadzam w pole tekstowe. Google np. rozumie to bardziej… bardziej sprawdza sens tego co ja piszę, natomiast DuckDuckGo wydaje wyniki, które po prostu bardziej psują językowo. Które z tych podejść wg ciebie jest bardziej…

Alicja: Wiesz co? Moim zdaniem lepsze jest wychwytywanie sensu szczerze mówiąc. Dlatego zwłaszcza że nie wszyscy użytkownicy potrafią dobrze formułować zapytania. Takie jest też moje zdanie, że jakby jedną myśl jesteś w stanie przedstawić na różne sposoby i w tym momencie jeżeli algorytm, który wyszukuje podobieństwo w tekście jest w stanie zrozumieć o co ci chodziło, to jest to dużo, moim zdaniem, bardziej wartościowe, niż tak jak mówisz względy językowe takie, strukturalne.

Dima: Czyli może zdarzyć się tak, że użytkownik wpisał jakieś zapytania i dostał w pierwszym rankingu odpowiedź, która nie zawiera w ogóle tego tekstu…

Alicja: Może się tak zdarzyć, jasne.

Dima: …bo algorytm stwierdził, że ma to większy sens.

Alicja: Podejrzewam, że tak, chociaż chyba nie zdarzyła mi się nigdy taka sytuacja, więc jest to taka trochę teoretyczna sytuacja.

Dima: OK. I na koniec jaki jeden najważniejszy punkt z tej prezentacji byś wspomniała.

Alicja: Przede wszystkim to, że jesteśmy w ogóle w stanie mierzyć to relevance czyli tą taką odpowiedniość wyszukiwanego tekstu do frazy. Moim zdaniem to jest taka główna myśl i że przede wszystkim, że to nie jest jakby konkretna wartość, ale, to co cały czas było podkreślane na prezentacji, że to jest… jakby to relevance zawsze będzie spektrum wartości.

Dima: OK. No to teraz mogę powiedzieć, że byłem na tej prezentacji.

Alicja: Cieszę się, że pomogłam.

#Euruko2017 - Data Driven Production Apps

Rozmowa i opinie po prezentacji “Data Driven Production Apps”



Dima Boyko (Software Engineer, inFakt.pl): Sebastian, opowiedz o czym była ostatnia prezentacja. Jak ci się podobała?

Sebastian Bobrowski (CTO inFakt.pl): Bardzo mi się podobała. Był człowiek z Shopify’a. Dla mnie Shopify to jest taka pierwsza poważna aplikacja Railsowa. Jak ja się zacząłem interesować Rubim i Railsami to Shopify już działał tak produkcyjnie na dużą skalę. To jest tam firma z Kanady dostarczająca oprogramowanie dla sklepów internetowych. To był dla mnie taki, przy podejmowaniu decyzji technologicznej, pierwszy duży punkt, że da radę na Ruby od Rails i Rubim zbudować działającą produkcyjną aplikację. Na tej prezentacji zrozumiałem taką historię trochę Shopify’a, jak to się zaczęło. No zaczęło się od tego, że budowali po prostu aplikację dla sklepów internetowych i ona działała dobrze, ale w pewnym momencie wyszedł temat danych i dlaczego warto je zbierać. Na początku wiedzieli, że są biblioteki machine learningowe, że sztuczną inteligencją można je analizować, natomiast na podstawie danych, które mieli, było dosyć trudno było to robić. Na początku próbowali to robić jakby wewnątrz aplikacji, dobudowywać modele, ale okazało się, że to jest bardzo trudne, jeżeli mamy aplikację, która robi jedną rzecz, czyli dostarcza coś dla sklepów internetowych, to bardzo trudno jest tam dobudować jakiś moduł, który coś tam analizuje. No i ważnym wtedy elementem było to, że zrozumieli, że potrzebują taki magazyn danych, taki data warehouse, po to, żeby tam trzymać te informacje. To jest dla mnie taka pierwsza, najważniejsza rzecz z tej prezentacji.

Dima: Myślisz, że chciałbyś wprowadzić w swoim produkcie, taki data warehause? Czy ma to sens dla takiej skali?

Sebastian: Wydaje mi się… Na pierwszy rzut oka, to jest takie powtarzanie danych, jak gdyby że trzymamy nagle te same dane w dwóch miejscach i na pierwszy rzut oka wydaje się to niezbyt dobrym pomysłem, ale jak się nad tym dobrze zastanowić, co można przy okazji zrobić i jakby jak ta logika mocno jest oddzielona, to wydaje mi się, że ma to duży sens. Wyciągnięcie tych informacji poza, oczyszczenie ich, on opowiadał o tym, że na pewno warto w tym momencie, gdy wyciągamy je z produkcyjnej bazy do tego magazynu, na pewno warto je wtedy oczyścić, tak, żeby mieć je mocno przygotowane i je wręcz włożyć do płaskiej tabeli, czyli żebyśmy nie mieli zależności, które są pomiędzy różnymi tabelami. Mamy kilkadziesiąt tabeli np. w Postgresie i po to, żeby wyciągnąć jedną informację, musimy zjoinować kilkadziesiąt tabeli, to wtedy przy tym takim preprocesingu danych w momencie ich wyciągania możemy je od razu umieścić w jednej tabeli i wtedy dla takiej osoby, która później pracuje z tym magazynem danych, dużo łatwiej jest to zrozumieć i wyciągać. Także jak najbardziej, ale na pewno nie jest to takie super proste zadanie.

Adrian Zięba (Software Engineer, inFakt.pl): Zwróć też uwagę, że on opowiadał o tym, że mieli problemy z główną aplikacją i nie mogli zapewnić użytkownikom podstawowych funkcjonalności, jeśli to wszystko się działo, to analizowanie danych, w tej głównej aplikacji, bo obciążyło to serwer głównej aplikacji i jednym słowem zabrakło mocy obliczeniowej dla podstawowych funkcjonalności. Więc pod tym względem też jest to dobrze dzielić, żeby dywersyfikować obciążenie głównych serwerów.

Sebastian: Jakby się nad tym zastanowić, to można spróbować zrobić tak, że wiesz, ż wydzielasz jakąś tam aplikację bazy danych pod to, żeby z niej tylko czytać te dane, natomiast pytanie, czy to wtedy już nie robisz tego magazynu tak powoli.

Adrian: No właśnie.

Dima: Osobna instancja aplikacji z bazą to jest taki trochę data warehause. Adriano wspominał, że bardzo lubi MongoDB. Uważasz, że na taki data warehouse nadaje się bardziej tradycyjna baza danych czy coś w stylu MongoDB?

Sebastian: Wydaje mi się, że tutaj taka NoSQL’owa baza ma duży sens, właśnie z tego powodu, że wyciągamy dużą ilość danych z takiej produkcyjnej aplikacji, możemy je wcześniej oczyścić, możemy je wcześniej przygotować i możemy je właśnie użytkując zapakować w takie gotowe rekordy, dzięki czemu mamy już je gotowe, możemy coś na nich działać, a nie musimy zawsze trzymać tego schematu. Jeżeli mamy jakieś obiekty, które mają dzisiaj składają się z pięciu wartości, a jutro dochodzi szósta, no to nie musimy jakby całości przebudowywać, plus dostęp do jednego obiektu jest bardzo szybki.

Dima: Ten spiker z Shopify też wspominał, że dzielenie takich danych pozwala użyć zupełnie innych technologii do obróbki tych danych. Uważasz, że warto na tym data warehause używać takiej samej technologii czy można pójść w zupełnie innym kierunku i w innej technologii inne paradygmaty stosować?

Sebastian: Wydaje mi się, że można tutaj… jakby jest bardzo wtedy łatwo użyć innej technologii, ponieważ dużo technologii szczególnie maszyn learningowych ma duże wsparcie dla tego, że mamy dane bezpośrednio w JSON-ie, a dużo mniejsze wsparcie np. dane, które są w SQL-u. Też się da, ale jeżeli mamy dane w JSON-ie, to mamy od razu wsparcie, możemy je bezpośrednio załadować np. do Scikit-Learn’a Pytonowego i „z pudełka” mamy od razu te dane dostępne, możemy je analizować, wyciągać z nich jakieś informacje, budować na nich widoki itd.

Adrian: Zwróć też uwagę, że oni też dokonali na końcu czegoś takiego jak komunikacji zwrotnej. Dlatego że z tego magazynu danych potrzebowali danych na temat, aktualnych danych i danych przewidywanych dla użytkowników odnośnie sprzedaży i później z powrotem, te dane wrzucili do aplikacji łącząc, komunikując je przez API. Więc to też jest tak, że niby te dane są osobno, ale masz też do nich dostęp, do tych rezultatów.

Sebastian: No, bo dzięki temu, że one są w stanie jakby wrócić. Bo tak naprawdę robimy to dla tego użytkownika końcowego. No i ten użytkownik końcowy później ma z tego wartość, bo jesteśmy w stanie te dane wstrzyknąć.

#Euruko2017 - Zapowiedź serii

Zapowiedź serii wideo z Euruko 2017 w Budapeszcie



Warsztaty Ruby on Rails z inFakt

Zapraszamy na Warsztaty z programowania Ruby on Rails, które rozpoczną się 8 listopada w Krakowie.



Druga edycja warsztatów

To już druga edycja serii spotkań zespołu IT inFaktu z developerami, którzy chcą rozwijać swoje umiejętności w budowaniu aplikacji webowych.

Zachęceni bardzo pozytywnymi opiniami po warsztatach dla początkujących programistów, przygotowaliśmy kolejny zestaw praktycznych ćwiczeń, które pozwolą zrobić kolejny krok w drodze do zostania railsowym wymiataczem.
Jeżeli masz już podstawowe doświadczenie w technologii Ruby on Rails i kilka prostych projektów za sobą, to jesteś idealnym kandydatem do tego, żeby wspólnie z nami rozszerzać swoje developerskie horyzonty!

Warsztaty Ruby on Rails z inFakt to trzy spotkania po ok. 3 godziny, w trakcie których uczestnicy będą budować i ulepszać prostą aplikację, wykorzystując świeżo zdobyte umiejętności. Obecni na sali mentorzy na każdym kroku będą oferować wsparcie merytoryczne, wyjaśniać ewentualne wątpliwości i udzielać dobrych rad, bazując na swoim doświadczeniu.

Czego się nauczysz?

W trakcie warsztatów, w praktyce poznasz i przećwiczysz tematy:

  • Kontrolery i widoki
  • Routing, modele, konfiguracja aplikacji
  • Testy i clean up aplikacji
  • Zadania w tle
  • API
  • Odciążanie bazy danych
  • Omniauth 2
  • Integracja z zewnętrzną usługą


Zapisy na warsztaty

Jeżeli czujesz, że takie spotkania mogą być dla Ciebie wartościowe - zachęcamy do zgłoszenia swojego udziału!

Formularz rejestracyjny dostępny jest na stronie www dedykowanej warsztatom: www.infakt.pl/warsztaty-rails.

Uwaga: Zgłoszenia uczestników przyjmujemy tylko do 29 października 2017r.

Zapraszamy!

Czym może zaskoczyć Cię case statement

Przesadziłabym mówiąc, że nie ma dnia, żebym pisząc w Rubym nie skorzystała z case’a. Wydawało mi się jednak, że znamy się już całkiem nieźle… aż pewnego razu okazało się, że właściwie „znamy sie tylko z widzenia”, bo nigdy nie zastanawiałam się nad tym jak działa pod maską.

Gdyby ktoś spytał mnie kiedyś jak należy rozumieć

case grade
when "A", "B"
  puts 'You’ve passed! Great!'
when "C", "D"
  puts 'You’ve passed… but...'
else
  puts 'You haven’t passed, sorry'
end

Odpowiedziałabym:

Jeżeli grade równa się A lub B - wpisz 'You’ve passed! Great!'. W przeciwnym razie, jeżeli jest równa C lub D - wypisz 'You’ve passed… but...'. W każdym innym przypadku wypisz 'You haven’t passed, sorry'.

Czy odpowiedziałabym poprawnie? Tak i nie.
Tak - ponieważ w tym przypadku do tego sprowadza się zapisana składnia i na tzw. „chłopski rozum” - tak właśnie zadziała.
Nie - ponieważ znaczyłoby to, że należałoby to przełożyć na zapis

if grade == "A" || grade == "B"
  puts 'You’ve passed! Great!'
elsif grade == "C" || grade == "D"
  puts 'You’ve passed… but...'
else
  puts 'You haven’t passed, sorry'
end

…a nie tak działa case!

Case equality, threequals

Mechanizm case opiera się o tzw. (zaskakująca nazwa) case-equality, czyli operator “===”, zwany również threequals Poprawny, przepisany zapis powyższego wywołania to

if "A" === grade || "B" === grade
  puts 'You’ve passed! Great!'
elsif "C" === grade || "D" === grade
  puts 'You’ve passed… but...'
else
  puts 'You haven’t passed, sorry'
end

Już na pierwszy rzut oka widać, że “wartości” znajdują się po przeciwnej stronie porównania niż zakladakałam… I kolejność ta, przy porównaniu za pomocą “===” ma znaczenie!
Jaka więc jest różnica pomiędzy “==” a “===” ?

== VS ===

W wielu przypadkach, threequals zachowa się tak jak “==”, porównując obiekty. Przy niektórych wywołaniach możemy jednak odczytać left_x === right_y jako pytanie _czy right_y można umieścić w pudełku left_x ?

Dlatego też, prawdę zwrócą wszystkie następujące wywołania:

Integer === 5             # zachowa się jak is_a?
(1..5) === 3              # include?
/test/ === 'myteststring' # =~, zwracając boolean

Fałszywe okażą się natomiast wszystkie te wywołania z zamienioną kolejnością argumentów. (Czy Integer można umieścić w pudełku 5?)

WARNING: instance class

Ciekawym przykładem, na którym łatwo jest się potknąć, jest sprawdzanie klasy danego obiektu.

obj = 'hello'
String === obj.class
#=> false
String === obj
#=> true

Dlaczego tak się dzieje? Ponieważ obj.class jest obiektem klasy Class nie String! Warto o tym pamiętać, gdyż jest to bardzo łatwy do popełnienia błąd, jeżeli używając case’a mamy z tyłu głowy „tradycyjne” porównanie - aż prosi się o użycie

def evaluate(obj)
  case obj.class
  when String
    obj.capitalize
  when Array
    obj.join.capitalize
  else
    'error'
  end
end

evaluate("my string")
#=> "error"
evaluate(["array", "of", "strings"])
#=> "error"

Poprawnym natomiast jest pominięcie wywołania metody .class na obiekcie

def evaluate(obj)
  case obj
  when String
    obj.capitalize
  when Array
    obj.join.capitalize
  else
    'error'
  end
end

evaluate("my string")
#=> "My string"
evaluate(["array", "of", "strings"])
#=> "Array of strings"

Podsumowanie

O czym warto w takim razie pomyśleć, pisząc kolejnego case'a?
Co w życiu zmieni nam === ?

Przede wszystkim należy pamiętać, że często, ale nie zawsze case equality zadziała jak ==.
Z drugiej strony, dzięki threequals mamy więcej możliwości niż przy tradycyjnym porównaniu wartości - możemy np. skorzystać z wyrażeń regularnych.

  case obj
  when /\d/
    "Object includes integers"
  when /^[a-zA-Z]*$/
    "Object contains only letters"
  else
    "Other"
  end
end

2017: Aplikacja webowa

Praca ksiegowych zbyt często polega na żmudnym przepisywaniu kwot, numerków, dziesiątek tabelek z liczbami. Mamy zamiar to zmienić. Tworzymy arbitrue — wirtualnego asystenta księgowego, nowy produkt skierowany na rynek w Wielkiej Brytanii.

Jako zespół infakt.pl — od lat pomagamy małym przedsiębiorcom z całej Polski przy fakturowaniu i księgowości. Na nasz sukces składa się wiele czynników, ale wiemy, że jednym z nich jest dobrze zaprojektowana, intuicyjna i przejrzysta aplikacja webowa.

Stojąc przed nowym projektem, postawiliśmy sobie poprzeczkę jeszcze wyżej. W 2007 roku, gdy startowaliśmy z inFakt, postawiliśmy na Ruby on Rails. Dziś wiemy, że podjęliśmy dobrą decyzję. 10 lat później, w 2017 tworzymy nową aplikację i stajemy przed kolejnym wyborem. Chcemy móc za 10 lat powiedzieć to samo.

alt text

Nowa aplikacja webowa w 2017,

Nie trzeba być bacznym obserwatorem, aby stwierdzić, że przez ostatnie lata wiele się dzieje w świecie frontendu. Kiedyś wystarczyło generowanie widoków stron na serwerze. Potem, zaczęto modyfikować stronę (DOM) po stronie przeglądarki co spowodowało pojawienie się jQuery, następnie wprowadziliśmy się asynchroniczność za sprawą AJAX… pewnie znacie tą historię. W dalszej kolejności, zapragnęliśmy informować użytkownika na żywo o nowych zdarzeniach, np. wiadomościach, aktualizacjach, czy też zaksięgowanych plikach, np przy użyciu Web Socketów. Naturalnie wykształciła się koncepcja Single Page Application — SPA.

a więc Progressive Web Application,

W związku z rozwojem urządzeń mobilnych, sieci społecznościowych, chęci ujednolicenia wyglądu na różnych platformach, ukuto nową listę zasad: Progressive Web Application. Dziś, czy nam się to podoba czy nie, najlepsi tworzą aplikację które są:

  • Progresywne — Działają dla każdego, niezależnie od przeglądarki, ciągle dodając wsparcie dla nowych technologii, ponieważ jest tworzona z myślą o ciągłym rozwoju.
  • Responsywne — Działają na wszystkim. Komputerach, telefonach, tabletach, a nawet lodówkach, jeśli tylko jest dla nich zastosowanie.
  • Niezależne od jakości łącza — Dzięki serwisom działającym w tle, które potrafią wstrzymać wysyłanie danych aż do przywrócenia łączności, pozwalają na pracę offline i użytkownik nie odczuje, że pracuje na wolnym łączu.
  • ‘Aplikacyjne/natywne’ — Wyglądają i działają jak aplikacja, dzięki separacji funkcjonalności, od zawartości.
  • Aktualne — Zawsze zaktualizowane do najnowszej wersji, np. podczas cichych aktualizacji wykonywanych przez ‘service worker’.
  • Bezpieczne — Zawsze dostępne przez https.
  • Wykrywalne — Rozpoznawalne jako ‘aplikacja’, dzięki obecności W3C manifest i rejestracji w tle, przez co są obecne w wynikach wyszukiwania.
  • Ciągle zachęcające — Wysyłają np. wiadomości PUSH czy maile.
  • Instalowalne — Pozwalają na dodawawanie do pulpitu, bez ściągnięcia ze sklepu czy strony.
  • Pozwalające na linkowanie — Są dostępne pod linkiem nie wymagającym instalacji, który można łatwo udostępnić.

Tworzenie takich aplikacji przenosi dużą ilość logiki, ciężar tworzenia intefrejsu użytkownika z serwera na przeglądarkę, która z silnika do interpretacji wyglądu stron staje się pełną platformą uruchomieniową. Taka zmiana stwarza wiele wyzwań, dla projektantów, developerów jak i samych przeglądarek. Aby ułatwić tworzenie takich aplikacji, jak grzyby po deszczu wyrosło kilka rozwiązań w postaci frameworków. Doświadczenie nas nauczyło, że jeżeli chcemy być konkurencyjni, musimy wybierać zawsze najnowszą technologię. Biorąc pod uwagę trendy oraz to, że z punktu widzenia użytkownika aplikacje typu SPA mają niezaprzeczalnie lepszą użyteczność oraz szybkość — a to wartości, do których zawsze dążymy. Chcemy aby arbiture powtórzył sukces inFakt. Wiemy, że design i wybór technologii frontowej są kluczowe, aby zaskarbić sobie serca użytkowników. Ale jak ją wybrać?

działająca z REST API

W inFakt mamy doświadczenie w tworzeniu backendu oraz REST API w Ruby on Rails. Jesteśmy zadowoleni z tej technologii, pracują z nami eksperci i jesteśmy zaangażowani w działanie lokalnej społeczności Ruby. Postanowiliśmy pozostać przy niej, dlatego podstawowym warunkiem, który musi spełniać technologia w której stworzymy aplikację frontową jest wygodna wpółpraca z REST API i Ruby on Rails.

i WebSocketami

Aby zapewnić użytkownikowi ciągłe aktualizacje o nowych wiadomościach, powiadomieniach zdecydowaliśmy, że arbitrue będzie korzystać z WebSocketów. Ruby wspiera tą technologię, w oparciu o gem ActionCable. Znamy tą technologię i chcielibyśmy mieć możliwość jej użycia w naszej aplikacji frontowej.

oparta o bibliotekę…

Samobójstwem dla małego zespołu wydaje się być pisanie takiej aplikacji od zera, bez wsparcia żadnej biblioteki. Odpowiednie dobranie i wykorzystane frameworka, umożliwia szybkie tworzenie nowoczesnych aplikacji i daje możliwość efektywnej realizacji projektu. Mechanizmy, komponenty, pluginy, które można wykorzystać w aplikacji poprawiają przezjrzystość kodu, umożliwiają jego łatwe wielokrotne wykorzystanie. Działając zgodnie z konwencją proponowaną przez bibliotekę, piszemy kod który jest bardziej uporządkowany, przyjazny. Z drugiej strony, często frameworki mocno narzucają architekturę i sposób pisania aplikacji, która nie zawsze może wpisywać się w naszą wizję produktu. Wybierając bibliotekę przywiązujemy się nie tylko do technologii, ale także do ekosystemu, składającego się z wtyczek, tutoriali, przykładów i wsparcia. Aby ten wybór był możliwie świadomy, postanowiliśmy porównać trzy najpopularniejsze obecnie frameworki frontowe. Pierwszym działaniem było zawężenie listy kandydatów. W tym celu skorzystaliśmy ze strony stateofjs.com:

alt text

Niebieskia część słupka odpowiada za zainteresowanie technologią, czerwona doświadczeniami. Mniej nasycone kolory oznaczają brak zainteresowania, bądź złe doświadczenia, a nasycone żywe zainteresowanie bądź pozytywne doświadczenia. Wg danych, największe zainteresowanie wzbudzał Angular 2, Vue oraz React. Najwięcej satysfakcji przyniósł React (53/5) oraz Vue (10/1). Jako, że Angularjs(1) został zastąpiony Angularem 2, którego rozwinięciem jest Angular 4, pozostaniemy przy porównaniu z najnowszą wersją. Takie wyniki pozwoliły nam na szybkie zawężenie kandydatów do: React.js, Vue.js oraz Angular 2 (4)

Już niedługo opublikujemy bardziej techniczny artykuł porównujący te technologie.

Przekazywanie zmiennych w playbookach Ansible

Czasami problemem w playbookach Ansible staje się przekazywanie zmiennych między częściami tego playbooka wykonywanymi na różnych hostach. Rozważmy taki przypadek, w którym wykonamy następujące kroki:

  • Na lokalnej maszynie wykonamy przykładowe polecenie i zarejestrujemy jego wynik jako zmienną testvar
  • Połączymy się z innym hostem i spróbujemy odwołać się na nim do zmiennej testvar za pomocą modułu debug
  • Spróbujemy zmodyfikować przykładowy playbook tak, żeby zawartość zmiennej prawidłowo uzyskać.

Prosty playbook, który pozwoli nam na przetestowanie dostępności zmiennych wygląda np. tak:

---
- hosts: localhost
  become: no
  connection: local

  tasks:
  - shell: echo "Foo bar"
    register: testvar

  - debug: var=testvar

- hosts: vagrant.local
  become: no

  tasks:
  - debug: var=testvar

W efekcie wywołania takiego playbooka przekonamy się, że zmienna testvar, prawidłowo zarejestrowana na maszynie lokalnej, na hoście vagrant.local nie jest dostępna:

$ ansible-playbook -i 'vagrant.local,' test.local

PLAY [localhost] ***************************************************************

TASK [setup] *******************************************************************
ok: [localhost]

TASK [command] *****************************************************************
changed: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
    "testvar": {
        "changed": true,
        "cmd": "echo \"Foo bar\"",
        "delta": "0:00:00.001634",
        "end": "2017-03-17 16:58:20.821949",
        "rc": 0,
        "start": "2017-03-17 16:58:20.820315",
        "stderr": "",
        "stdout": "Foo bar",
        "stdout_lines": [
            "Foo bar"
        ],
        "warnings": []
    }
}

PLAY [vagrant.local] ***********************************************************

TASK [setup] *******************************************************************
ok: [vagrant.local]

TASK [debug] *******************************************************************
ok: [vagrant.local] => {
    "testvar": "VARIABLE IS NOT DEFINED!"
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0
vagrant.local              : ok=2    changed=0    unreachable=0    failed=0

Okazuje się jednak, że wystarczy drobna zmiana, żeby “dobrać się” do naszej zmiennej. Dla czytelności prezentuję tylko drugą część playbooka:

- hosts: vagrant.local
  become: no

  tasks:
  - debug: var=hostvars['localhost']['testvar']

Po wywołaniu zmodyfikowanego playbooka otrzymujemy w tym zadaniu wartość naszej zmiennej:

TASK [debug] *******************************************************************
ok: [vagrant.local] => {
    "hostvars['localhost']['testvar']": {
        "changed": true,
        "cmd": "echo \"Foo bar\"",
        "delta": "0:00:00.001697",
        "end": "2017-03-17 17:03:01.279967",
        "rc": 0,
        "start": "2017-03-17 17:03:01.278270",
        "stderr": "",
        "stdout": "Foo bar",
        "stdout_lines": [
            "Foo bar"
        ],
        "warnings": []
    }
}

Migracja z rvm do rbenv na mac OS

Na początek usuwamy wszystko co dotyczy RVM:

rvm implode

# ręcznie usuwamy (jeśli istnieje) plik:
rm ~/.rvmrc 

# oraz wpisy dotyczące RVM z:
.profile
.bash_profile
.bashrc
.zshrc

Następnie instalujemy rbenv przy pomocy brew:

brew install rbenv ruby-build

Lub ściągając z git hub’a

git clone https://github.com/rbenv/rbenv.git ~/.rbenv
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

Dodajemy rbenv do PATH:

# do .bash_profile lub .zshrc dodajemy:
export PATH="$HOME/.rbenv/bin:$PATH"

Oraz uruchamiamy:

rbenv init 

I uzupełniamy wskazany plik odpowiednim wpisem.

Aby zainstalować odpowiednią wersję ruby:

rbenv install 2.4.0

Możemy nadal używać plików .ruby-version wpisując w nie wersję ruby której chcemy używać w danym projekcie.

Synchroniczne uruchamianie workerów Sidekiq

Sidekiq pozwala na wykonywnie określonych zadań asynchronicznie, “w tle”. Najczęściej realizowane jest to za pomocą specjalnie stworzonych klas (workerów), które dzięki użyciu modułu Sidekiq::Worker zyskują metody z których możemy skorzystać aby zlecić wykonanie kodu w tle.

class FooWorker
  include Sidekiq::Worker

  def perform(foo_id)
    "Wartość foo_id wynosi: #{foo_id}"
  end
end

# wykonanie w tle
FooWorker.perform_async(1)
=> "d26cca4c2786f52e48da9520"

# wykonanie po wskazanym czasie
FooWorker.perform_in(10.seconds, 1)
=> "369aee857301ef89d7669821"

Czasem przydatne może być wykonanie kodu workera w ramach aktualnego wątku, bez planowania zadania w tle. Wbrew pozorom jest to bardzo łatwe - wystarczy pamiętać, że worker jest przecież regularną klasą Ruby, w związku z tym możemy po prostu powołać jego instancję i wykonać metodę perform:

FooWorker.new.perform(1)
=> "Wartość foo_id wynosi: 1"

Oczywiście, przy takim wywołaniu całkowicie pomijamy strukturę Sidekiq, tracąc tym samym kolejkowanie, ponawianie zadań itd. Może to być jednak bardzo przydatne w momencie, kiedy chcemy łatwo sprawdzić działanie kodu zamkniętego w workerze, np. w trakcie debugowania aplikacji.

Sortowanie NULL w PostgreSQL

W przypadku kiedy kolumna według której sortujemy dane pobierane z bazy PostgreSQL przybiera wartość NULL, kolejność wyników jest następująca:

  • dla sortowania w porządku rosnącym (ASC), wartości NULL zostaną umieszczone jako ostatnie,
  • dla sortowania w porządku malejącym (DESC), wartości NULL zostaną umieszczone jako pierwsze.

Nie zawsze jednak jest to preferowane zachowanie - łatwo wyobrazić sobie sytuację, kiedy puste dane chcemy zawsze prezentować jako ostatnie (lub jako pierwsze) - niezależnie od przyjętego porządku sortowania.

Można to łatwo wymusić jawnie wskazując w momencie sortowania w jakim porządku mają być zwracane wartości NULL. Aby to osiągnąć, należy do warunku sortowania - ORDER BY column_name ASC | DESC - dodać:

  • NULLS LAST - jeżeli wartość NULL ma być umieszczona na końcu wyników,
  • NULLS FIRST - jeżeli wartość NULL ma być umieszczona na początku wyników.

Przykładowo, dla danych:

-------------------
| id | name       |
-------------------
| 1  | Zofia      |
| 2  | NULL       |
| 3  | Alicja     |
-------------------

możemy sortować w następujący sposób:

# NULLe zawsze ostatnie
SELECT id, name FROM table_name ORDER BY name ASC NULLS LAST;
=> [3, Alicja], [1, Zofia], [2, NULL]

SELECT id, name FROM table_name ORDER BY name DESC NULLS LAST;
=> [1, Zofia], [3, Alicja], [2, NULL]

# NULLe zawsze pierwsze
SELECT id, name FROM table_name ORDER BY name ASC NULLS LAST;
=> [2, NULL], [3, Alicja], [1, Zofia]

SELECT id, name FROM table_name ORDER BY name DESC NULLS LAST;
=> [2, NULL], [1, Zofia], [3, Alicja]

Więcej informacji dostępne jest w dokumentacji PostgreSQL.