Idź do:
Czym jest grafowa baza danych?
Neo4j (https://neo4j.com/) jest jedną z najpopularniejszych, o ile nie najpopularniejszą grafową bazą danych. Dla przypomnienia i uporządkowania wiedzy: graf jest kompozycją dwóch typów elementów, jakimi są węzły i relacje. Węzeł może reprezentować określony typ lub kilka typów i posiada swoje właściwości (ang. properties). Relacje zaś poza nazwą i własnymi właściwościami posiadają – co najistotniejsze – kierunek oddziaływania. Wspomniane właściwości są kolekcjami par klucz – wartość. Służą do przechowywania istotnych informacji. Dla przykładu: jeżeli węzłem będzie osoba, to jej właściwościami mogą być: imię, nazwisko, wiek albo lista ulubionych książek.
Relacje między węzłami w Neo4j – jak i w grafowych bazach danych w ogóle – są tak samo ważnymi danymi jak węzły. Traktujemy je jak obiekty, których istnienie jest determinowane przez obecność danych węzłów. Występowanie relacji samodzielne nie ma żadnego uzasadnienia.
Relacje znane z baz danych typu RDBMS (Relational Database Management System) zazwyczaj będą nam się kojarzyć z oznaczaniem danych w jakimś wierszu z jednej tabeli jako mających swoje odzwierciedlenie w konkretnym wierszu innej tabeli. Pozwoli nam to na operacje kaskadowe podczas edycji lub usuwania danych. Podczas normalizowania modelu danych w RDBMS możemy się zetknąć z koniecznością wprowadzenia specjalnych tabel pośredniczących między dwoma tabelami służącymi do wiązania ze sobą całych grup wierszy.
Czytaj także: Poznaj korzyści Test-Driven Development na co dzień
Wybór bazy danych
Wciąż jeszcze w wielu środowiskach panować może przeświadczenie, że RDBMS są najlepsze do wszelkiego rodzaju zadań. Osoby decyzyjne w zakresie doboru baz danych mogą nieprzychylnie spoglądać na alternatywne rozwiązania do przechowywania danych, a za taką zwykło się dotąd uważać właśnie Neo4j. Jak rozsądnie dokonać wyboru bazy lub baz danych do wykorzystania w projekcie? Przede wszystkim wybór powinien być zdeterminowany przez stojące przed aplikacją zadania.
Już na etapie projektowania aplikacji, jeszcze na etapie wyboru bazy danych, warto zastanowić się nad kilkoma kwestiami:
- jakie operacje będą w przyszłości wykonywane na gromadzonych danych?
- czy zadaniem aplikacji będzie zapisywanie i odczytywanie danych bez żadnych bardziej skomplikowanych operacji?
- może najistotniejsze będą wzajemne relacje między danymi, a program powinien pomagać w ich analizie?
Wybór bazy uzależniony jest więc od tego, jaka jest podstawowa funkcja projektowanej aplikacji i charakter danych. Czy będą to dane osób zatrudnionych w konkretnej organizacji, współtworzących jej strukturę organizacyjną, a aplikacja, którą tworzysz, będzie służyć do zapewnienia sprawnego obiegu dokumentów między pracownikami? Może masz do opracowania wyszukiwarkę połączeń lotniczych? Albo aplikację, która zapewni wsparcie logistyczne dla firmy transportowej? A może dostałeś supertajne zlecenie dla rządowych służb specjalnych polegające na zaimplementowaniu systemu wspierającego zarządzanie siatką agentów i informatorów? W ostatnim przykładzie rzeczywiście fantazja nieco mnie poniosła, ale głównie dlatego, że możliwości, jakich dostarcza nam grafowa baza danych, są naprawdę ogromne.
Jednak nie zawsze przestrzega się zasady dopasowania bazy danych do celu aplikacji, a zwolennicy nowych i często bardziej odpowiednich rozwiązań muszą zmagać się ze sceptycyzmem managerów („Przecież nikt nie korzysta z tych rozwiązań”).
Zalety Neo4j
Jakie wielkie musi być zaskoczenie i zdumienie oponentów, gdy po wejściu na stronę główną projektu Neo4j ich oczom ukazują się liczne logotypy światowych marek czy instytucji rządowych albo badawczych. Od firm medycznych, przez instytuty naukowo-badawcze, po firmy finansowe, transportowe, telekomunikacyjne i wojskowe. Pełna obszerna lista oraz case studies dostępne są na stronie projektu. To smakowity kąsek dla wszystkich zajmujących się analizą i modelowaniem danych oraz architektów aplikacji.
Jak widać, popularność Neo4j i grafowych baz danych ciągle rośnie. W wielu rankingach, w tym w moim prywatnym, baza danych Neo4j jest jednym z liderów takich rozwiązań. Warto wspomnieć o tym, że twórcy Neo4j zadbali o przyjazne zorganizowanie Clusteringu i pracy w chmurze, a najczęściej stosowanym rozwiązaniem jest Neo4j pracujący na AWS – co również przemawia na jej korzyść.
Warto pamiętać, że jako narzędzie Neo4j jest mocno wspierane zarówno przez współczesne narzędzia do pisania kodu, jak i popularne frameworki, na przykład Spring Framework w projekcie „Spring Data Neo4j”. To, że grafowa baza danych jest tak intensywnie rozwijana, dobrze wróży na przyszłość.
Co to znaczy, że Neo4j jest bazą NoSQLową?
Tak jak bazy danych z grupy RDBMS korzystają z języka zapytań SQL, tak Neo4j korzysta z języka Cypher. W obu przypadkach są to języki deklaratywne. O ile składniowo Cypher jest w wielu aspektach podobny do SQL, o tyle jedną z najczęściej wskazywanych różnic jest użycie słowa kluczowego MATCH w miejsce SELECT. Inną jest wykorzystanie – w znaczeniu dosłownym – strzałek relacji.
Cypher to język bardzo elastyczny pod względem możliwości budowania zapytań. Widać to dobrze choćby w takich przykładach jak ten, gdzie przeprowadzamy dopasowanie warunkowe. W SQL zawsze warunek umieścimy w klauzuli WHERE, zaś w Cypher dodatkowo może zostać zawarty już podczas deklaracji węzła. Możemy tworzyć interesujące i przydatne zapytania złożone z etapów przy wykorzystaniu klauzuli WITH.
Bardzo ważna, a często nawet kluczowa, jest czytelność zapytań, którą tu odnajdujemy. Godna uwagi jest również łatwość pisania zapytań dla osób, które mają zrozumienie pojęcia grafu i zetknęły się z pisownią zapytań w SQL.
Wyszukiwarka połączeń autobusowych – case study
Zaprezentowany przeze mnie niżej przykład aplikacji do rezerwacji biletów zaczerpnięty został z życia. Problem, z którym się mierzyłem, zaistniał kilka lat temu, głównie przez przywiązanie osób decyzyjnych do rozwiązań uznanych za sprawdzone i niechęci do rozeznania w gronie developerskim w poszukiwaniu nowych możliwości.
Projekt: aplikacja, która służy do rezerwacji biletów autobusowych.
Produkt końcowy: rezerwacja miejsca i zakup biletu. Z racji zbyt dużej złożoności tematu ograniczmy się jednak wyłącznie do wyszukiwarki połączeń.
Działanie aplikacji: zanim użytkownik zarezerwuje bilet, powinien najpierw wskazać przystanek, z którego zechce odjechać, oraz przystanek docelowy z listy dostępnych.
Modelowanie: w podejściu zgodnym z RDBMS do zamodelowania tego obszaru będziemy potrzebować przynajmniej trzech tabel. Pierwszą będzie rejestr przystanków, drugą – rejestr tras, na których zlokalizowane są przystanki. W trzeciej tabeli do konkretnych tras przypiszemy poszczególne przystanki.
Kłopotliwy RDBMS
Warto zwrócić uwagę na pewien typowy problem tabeli z odwzorowanymi relacjami pomiędzy poszczególnymi wierszami spajanych tabel. Występujące w niej wiersze zawierają komórki wypełnione nieczytelnymi liczbami należącymi zwykle do indeksowanych kluczy głównych tabel w relacji. Rozszyfrowywanie pochodzenia i znaczenia tych numerów może niekiedy wymagać sporego wysiłku. Tym większego, im bardziej złożona jest tabela. W naszym przykładzie taka spajająca tabela mogłaby wyglądać na przykład tak:
Takie „twory” znajdziemy w relacjach typu „Many-to-Many”. Mogą być one szczególnie uciążliwe, gdy w projekcie jest sporo tabel, zdefiniowanych kluczy obcych i relacji. Są też problematyczne, gdy istnieje cała masa danych w skryptach potrzebnych do zasilania testowych instancji baz danych w celu integracyjnej weryfikacji poprawności implementacji. Sytuacja często kluczowa w developmencie. Nierzadko trzeba się solidnie napracować, żeby przygotować nowe rekordy danych z zachowaniem relacji do tabel pośredniczących. Dane w wierszach muszą być unikalne albo dopasowane do wzorca, zgodne z innymi danymi „nienullowymi” z co najmniej dziesięciu innych tabel. Może się zdarzyć tak, że w modelu danych zaczynają – np. przez nieuwagę – pojawiać się odwołania cykliczne. Wówczas bez wykonania operacji wyłączenia wszystkich ograniczeń na bazie, nie jest możliwe ani dalsze dodawanie nowych danych, ani nawet zaimportowanie do niej prawidłowych danych.
RDBMS pełne Joinów
Przejdźmy do pierwszego kroku przy użyciu wyszukiwarki połączeń. Będzie to wyszukiwanie przez pasażera wszystkich możliwych tras przypisanych do wybranego przystanku.
Co interesującego właśnie się stało? Dane z trzech osobnych tabel na tym etapie scaliły się w zapytaniu w jedną i możemy wybrać z niej te wiersze, które zawierają szukany przystanek. Jeżeli jedną trasę określa nam N składających się na nią przystanków połączonych z nią nazwami tras, to odrzucamy wszystkie wiersze, w których przypisany przystanek jest inny niż wskazany przez nas.
Poniżej przykład tabeli wynikowej przed okrojeniem jej z wierszy o innej nazwie przystanku niż zadany:
Po odrzuceniu wierszy, w których nazwa przystanku nie odpowiada wskazanej przez nas, otrzymujemy taki oto zbiór wierszy, który następnie należy odpowiednio przyciąć poprzez odrzucenie ewentualnych powtórzeń.
RDBMS pełne podzapytań
Wyobraźmy sobie, że teraz nasz pasażer potrzebuje listy wszystkich przystanków, do których będzie mógł dojechać, wsiadając do autobusu na wskazanym przystanku. Poniżej przykładowe zapytanie będące odpowiedzią na oczekiwanie użytkownika aplikacji.
W powyższym zapytaniu mamy do czynienia z koniecznością użycia dwóch podzapytań na rzecz jednej operacji projekcji. Występuje łącznie sześć operacji łączenia tabel przy użyciu polecenia JOIN. Złożoność tego zapytania jest jego niewątpliwą wadą. Inną wadą jest brak intuicyjności w konstrukcji powstałych w języku SQL w przypadku, gdy chcemy uzyskać wycinek zbioru danych badanej rzeczywistości. Wynikiem zapytania w naszym przykładzie będzie zbiór danych zawartych w tabeli poniżej.
Zapytania z obu poprzednich listingów to dopiero wstęp do pozostałych operacji prowadzących do kupna biletu. Kolejne zapytania będą niekiedy jeszcze bardziej złożone. Szczególnie gdy utworzymy dodatkowe tabele do przechowywania informacji o cenach biletów uzależnionych od wybranego przystanku odjazdu oraz przystanku docelowego, rodzaju trasy, kursu, pory nocnej albo np. okresowych zniżek na danych odcinkach przejazdu.
Case study: użycie grafu Neo4j
Spójrzmy na analizowany model danych przez pryzmat obiektów grafu. Co to oznacza, że coś da się opisać przy pomocy grafu? Jak pisałem na początku: graf to zbiór węzłów i ich wzajemnych skierowanych relacji. W kontekście analizowanej wyszukiwarki połączeń autobusowych nasze przykładowe węzły i relacje możemy z wykorzystaniem Neo4j przedstawić w taki sposób:
Przystanki są węzłami, a droga, która prowadzi od jednego do drugiego przystanku, w sposób naturalny określa istniejącą między nimi relację. Droga pomiędzy przystankami spowoduje wystąpienie odpowiednich tras przejazdu, więc potraktujemy trasę jako właściwość relacji przez nią wyznaczoną.
Przykładowy rezultat powyższego zapytania:
Zapytanie z listingu 3 już na pierwszy rzut oka jest o wiele bardziej intuicyjne, niż miało to miejsce w przypadku SQL. Na pewno jest też mniej skomplikowane. W powyższym zapytaniu staramy się wybrać wszystkie relacje typu LEADS TO do innych przystanków, a wychodzące z takiego, którego nazwa odpowiada przystankowi wskazanemu w zapytaniu. Po czym zwracamy ich trasy przy użyciu słowa kluczowego RETURN.
Przejdźmy do kroku drugiego, który powinien wykonać użytkownik, by uzyskać listę możliwych przystanków, przez które będzie przejeżdżał autobus. W Neo4j uzyskamy ją, wykonując na przykład takie zapytanie jak na listingu 4.
Powyższe zapytanie możemy odczytać następująco: „Skoro między wskazanym przystankiem a kolejnymi istnieje relacja łączącej je drogi, to zwróć mi wszystkie kolejne aż do ostatniego”
Poniżej przykładowy rezultat w postaci grafu oraz w postaci tabeli nazw:
Jak widzimy w powyższych przykładach, zapytania w Cypher są dużo krótsze niż ich odpowiedniki w SQL i zaprezentowane w odpowiednich listingach SQL. Jednocześnie pozwalają uzyskać identyczne efekty. Dodatkową zaletą jest to, że w Neo4j poza widokiem tabelarycznym dostępny jest również widok węzłów i ich relacji.
Podsumowanie
Jeżeli wycinek rzeczywistości, nad którym pracujemy, stanowi zbiór obiektów i ich wzajemnych relacji – bardzo prawdopodobne, że odnajdziemy tam strukturę grafową i użycie grafowej bazy danych będzie miało sens. W niniejszym tekście chciałem zaprezentować przede wszystkim intuicyjność i łatwość w konstruowaniu zapytań w Neo4j. Podałem przykład aplikacji, w której mierzyłem się z problemem niedopasowania narzędzia do potrzeb projektu. Gdybym wówczas posiadał obecne doświadczenie i wiedzę na temat grafowych baz danych, starałbym się przekonać decyzyjne osoby w projekcie do zastosowania Neo4j. Uchroniłoby to klienta przed wieloma niepotrzebnymi problemami i wydatkami.