KeystoneJS – za co kocham, za co hejtuję, a może Django lepsze ?

Keystone jest technologią, z którą mam styczność od ponad 1,5 roku. Obcowanie z nią wywołało we mnie wiele uczuć, tych pozytywnych, jak i negatywnych 😉 . Z tego faktu chciałbym wyrazić opinię o tym narzędziu . Czy jest sens poznać ten framework ? W jakich projektach jest sens użyć Keystone’a ? Czy może jest jakaś alternatywa ? Mam nadzieję, że zostaniecie ze mną do końca wpisu ;).

Co oni chcę nam sprzedać ?

Spróbujmy na początku wejść na stronę domową Keystone’a. Aktualnie wygląda ona następująco:

Na górze mamy tytuł Node.js CMS & Web Application Platform. Na dole mamy bardziej szczegółowe rozwinięcie. Opis sugeruje, że Keystone jest frameworkiem, który ułatwia tworzenie aplikacji bazodanowych. Na pierwszy rzut oka jest to narzędzie podobne w założeniu do frameworków jak Django w Pythonie albo RoR w Ruby.

Co dostajemy po domyślnej instalacji  ?

Jak większość nowoczesnych frameworków Keystone oferuje instalację za pomocą kilku linijek. Do instalacji potrzebujemy jedynie działającego NodeJS oraz MongoDB na naszym komputerze ( mongoDB nie musi być koniecznie zainstalowane na naszym komputerze, możemy użyć działającej instancji np. na jakimś serwerze, Keystone potrzebuje tylko connection string’a 🙂 ). MongoDB jest jedyną bazą danych, która jest wspierana przez ten framework. Keystone w oficjalnym tutorialu zachęca do użycia template’a działającego na Yeoman’ie. W zależności od tego, jak przejdziemy kreator instalacji dostajemy trochę odmienną wersję szkieletu. Tutaj możecie zobaczyć szczegółową listę moich kroków, jest to najbardziej podstawowa instalacja:

Projekt odpalamy jakże prostą komendą, ➜ node keystone.js. W tym momencie na porcie 3000 zaczyna działać Keystone. Na domyślnej ścieżce wita nas oto taka strona domowa:

Nic wielkiego. Po przejściu na ścieżkę /keystone możemy zalogować się wykorzystując email i hasło ustawione w kreatorze.  W tym momencie dostajemy dostęp do panelu admina. Po zalogowaniu wygląda on następująco:

I teraz co niektórym osobą mogę włączyć się skojarzenia. Keystone na ten moment przypomina dość mocno Django !! W Django mamy podobnie, framework instalujemy kilkoma komendami, po tym mamy od razu dostęp do panelu admina gdzie istnieje domyślna kolekcja z użytkownikami. Czas teraz zajrzeć w strukturę wygenerowanego projektu Keystone’a 😀

Jaką architekturę oferuje domyślny generator ?

Oto struktura projektu która jest wygenerowana przez Yeomen’a:

P.S Prosiłbym was tylko o chwilowe spojrzenie bez głębszej analizy 🙂

Struktura ta jest niczym innym jak klasycznym MVC. Mamy foldery templates/ oraz routes/views/ które zawierają odpowiednie widoki ( w tym wypadku templatki w pug’u ) oraz kontrolery. Po za tym mamy dodatkowe foldery oraz pliki, które mają następujące role:

  • models/ – znajdują się tu definicje kolekcji
  • public/ – nazwa mówi sama za siebie, statyczne pliki
  • updates/ – folder z migracjami
  • keystone.js – startowy plik w którym mamy parę ustawień
  • inne pliki – generator dodaje różne inne pliki jak .editorconfig, .eslintrc albo .gitignore, które de facto nie są związane z Keystone’m, taki mało znaczący bonus 😉

Nie będę wyjaśniał tutaj dokładnie jak działa MVC, pokaże tutaj jedynie jak wygląda renderowanie strony domowej. Poniżej możecie zobaczyć kod kontrolera tej strony :

var keystone = require('keystone');

exports = module.exports = function (req, res) {

  var view = new keystone.View(req, res);
  var locals = res.locals;

  // locals.section is used to set the currently selected
  // item in the header navigation.
  locals.section = 'home';

  // Render the view
  view.render('index');
};

Zmienna res.locals jest obiektem który pełni rolę modelu. To co zostanie wpisane do tego modelu jest dostępne w templatce pug’owej ( jeżeli używacie ASP.NET myślę, że kojarzycie ViewBag, res.locals jest odpowiednikiem tego ). Czyli w tym wypadku section będzie dostępne jako zmienna w templatce. W ostatniej linijce funkcji kontrolera określamy nazwę templatki, którą chcemy użyć. W tym przypadku będzie to ostatecznie plik index.pug.

Kolekcje Keystone – czyli kwintesencja tego freamwork’a

Przejdźmy teraz do sedna, moim zdaniem najważniejszego feature’a który daje nam Keystone – do kolekcji. Po wykorzystaniu generatora domyślnie jest dodana kolekcja o nazwie User.js, jak nazwa sugeruje zawiera ona schemę modelu użytkownika, spójrzmy w nią:

var keystone = require('keystone');
var Types = keystone.Field.Types;

/**
 * User Model
 * ==========
 */
var User = new keystone.List('User');

User.add({
  name: { type: Types.Name, required: true, index: true },
  email: { type: Types.Email, initial: true, required: true, unique: true, index: true },
  password: { type: Types.Password, initial: true, required: true },
}, 'Permissions', {
  isAdmin: { type: Boolean, label: 'Can access Keystone', index: true },
});

// Provide access to Keystone
User.schema.virtual('canAccessKeystone').get(function () {
  return this.isAdmin;
});


/**
 * Registration
 */
User.defaultColumns = 'name, email, isAdmin';
User.register();

Model użytkownika składa się z 4 pól: nazwy, emaila, hasła oraz informacji czy użytkownik jest adminem ( string który widzicie w kodzie, „Permissions” odgrywa tylko rolę jako przedziałka w UI, widać to na poniższym screenie ). Zastanówmy się teraz jakby wyglądała schema gdyby użyć mongoose’a albo MySQL’a. Imię, email oraz hasło powinny być ciągiem znaków, tudzież stringiem, natomiast informacje o tym czy user jest adminem, zmienną typu boolean. Schemy w Keystone’ie wyglądają trochę inaczej, tutaj każde pole w kolekcji musi mieć jakiś typ który jest związany z UI. Czyli w tym przypadku nazwa jest typu Name (de facto zwykłe pole tekstowe), email jest typu Email ( pole tekstowe typu email <input id="emailAddress" type="email">  ), hasło typu Password ( pole które jest zakropkowane ), info o tym czy user jest adminem jest typu Boolean ( pole powinno być checkboxem ). Spójrzmy teraz w UI które zostało wygenerowane na podstawie tej schemy, w tym celu wchodzimy do panelu admina, przechodzimy do kolekcji Users i wchodzimy w jedyny istniejący rekord :

Zgodnie z oczekiwaniami został wygenerowany UI dla tej schemy, przyznajcie, że całkiem spoko to działa :). Spróbujmy teraz w celach demonstracyjnych rozszerzyć użytkownika o pole ze zdjęciem. W tym celu w schemie dodajemy nowe pole image :

P.S Na zielono dodany kod

var keystone = require('keystone');
var Types = keystone.Field.Types;

var storage = new keystone.Storage({
  adapter: keystone.Storage.Adapters.FS,
  fs: {
    path: 'public/user-uploaded',
    publicPath: '/user-uploaded',
  },
});

/**
 * User Model
 * ==========
 */
var User = new keystone.List('User');

User.add({
  name: { type: Types.Name, required: true, index: true },
  email: { type: Types.Email, initial: true, required: true, unique: true, index: true },
  password: { type: Types.Password, initial: true, required: true },
  image: {
    type: Types.File,
    storage: storage,
  },
}, 'Permissions', {
  isAdmin: { type: Boolean, label: 'Can access Keystone', index: true },
});

// Provide access to Keystone
User.schema.virtual('canAccessKeystone').get(function () {
  return this.isAdmin;
});


/**
 * Registration
 */
User.defaultColumns = 'name, email, isAdmin';
User.register();

Użyłem tutaj storage’u lokalnego, lepszym rozwiązaniem byłby typ CloudinaryImage ( Cloudinary to specjalny serwis do trzymania zdjęć, Keystone oferuje dla pola tego typu dodatkowo podgląd z miniaturkami po uploadzie ), ale wymagałoby to dodatkowych ustawień credentiali dla Cloudinary. Po restarcie projektu powinniśmy móc w panelu admina dodać zdjęcie dla pojedynczego użytkownika:

Jeżeli się zastanowimy co jest takiego wyjątkowego w schemie kolekcji Keystona to jest fakt, że nie używamy typów które powinny się finalnie znaleźć w bazie. Keystone pod spodem mapuje typy UI’owe na faktyczne typy w bazie. Jest to podejście które jest stosowane także w innych frameworkach, międzyinnymi w Django ( tutaj możecie znaleźć domyślne dostępne typy ).

Znowu robię porównanie do Django ;). Pewnie się zastanawiasz teraz czy Keystone to kopia Django :D. Nie odpowiem jeszcze :p. Na razie mogę powiedzieć, że Keystone na początku daje podobne uczucie które towarzyszą pracy przy Django, i nie są to tylko moje słowa, tutaj dowód 😉 :

Kiedy domyślny MVC ma sens ?

Podejście które oferuje domyślny generator projektu Keystone’a jest już dość przestarzałym rozwiązaniem. W dzisiejszych czasach frontend jest oddzielną aplikacja która komunikuje się z backendem poprzez API. Aczkolwiek jeżeli tworzymy aplikację której frontend ograniczy się na paru prostych widokach możliwe, że nie ma sensu robić sztywnego podziału. Prawdopodobnie to co daje nam MVC będzie wystarczającym setem.

Myślę też, że nie ma sensu by Keystone robił nowe template’y do generowania aplikacji ze sztywnym podziałem na front i backend. Mamy na froncie tyle technologii, że ciężko byłoby zrobić na tyle elastyczny generator by wszystkim pasował.

Koszt ustawienia sobie Keystone’a tak by działał bez problemów z Twoim ulubionym stackiem frontowym jest naprawdę nieduży i po stronie backendu ogranicza się do zrobienia API.

Problemy Keystone’a

Jak na razie nie mówiliśmy nic o problemach Keystone’a . Mam okazję uczestniczyć w projekcie który został zbudowany na Keystone’a, ale nie powinien. Jednym z problemów który wystąpił to użycie MVC który oferuje domyślny generator, ale o tym nie będę pisał bo to nie jest stricte związane z freamworkiem. Oto lista głównych grzechów Keystone:

Pierwszy

Nie jest możliwe używanie zagnieżdżonych schem. Wszystkie definicje kolekcji mogą mieć najwyżej max jeden level zagnieżdżenie.

To jest na szczęście problem który wyglada na to, że za niedługo zostanie rozwiązany. Tutaj możecie znaleźć PR. Jak na razie jest możliwe używanie brancha który ma zmergowany ten feature, jest to jednak mały trick, dlatego uważam, że nested scheme jest nadal problem.

Drugi

Nie możemy nadpisać domyślnego widoku panelu admina. Co implikuje także to, że nie możemy dodać w panelu stron nie związanych z edycją kolekcji (CRUDa), np strony z dashboardem.

Jak na razie nie zapowiada się na to by była możliwość nadpisania wyglądu.

Trzeci

Nie możemy dodać nowych typów pól bez modyfikacji node_modules/keystone/fields/types.

Jest to moim zdaniem największy problem. Przez to jesteśmy skazani na używanie pól które stworzą ludzie od Keystone. Gdyby twórcy umożliwi dodawanie customowych pól myślę, że sprawiłoby to wysyp wielu nowych pól tworzonych przez społeczność.

Czwarty

Keystone jest zafixowany na MongoDB. Zafixowanie się na jedną bazę danych nie jest dobrym pomysłem.

Tutaj moim zdaniem twórcy pozostaną przy MongoDB i zostaje nam akceptacja tego faktu.

Piąty

Brak ról. Wiążę się to z tym, że jest tylko jeden typ admina który może edytować wszystko co w panelu.

Tutaj jest PR który ma dodać role.

 

Spróbujmy teraz zastanowić się kiedy, którego narzędzia użyć.

Co kiedy wybrać ?

Kiedy WordPress ?

Jeżeli potrzebujesz postawić standardowego bloga albo prostą statyczną stronę wybierz WordPressa. Stawianie bloga na Keystone jest moim zdaniem bezcelowe( Keystone promuje się na głównej stronie, że jest CMSem, ale moim zdaniem powinni to usunąć :p )

Kiedy DJango lepsze ?

Jest wiele frameworków bazodanowych. Możecie zadać pytanie dlaczego porównuje Keystone do Django ? Myślę, że Django ma najbliższą filozofię do Keystone. Lecz Django robi to na tą chwilę po prostu lepiej, możemy bez problemu nadpisać panel admina, możemy dodać nowy customowy typ pola, Django supportuje wiele baz danych ( w tym MySQL i mongoDB ), dodatkowo ma za sobą potężne community. Jeżeli więc znasz dobrze Pythona i chcesz tworzyć aplikację bardziej niezależną od bazy, wybierz Django, unikniesz wiele problemów które spotkasz w Kesytone’ie.

Kiedy Keystone  Lepszy ?

Jeżeli nie przeszkadza Ci to że model danych będzie oparty o bazę dokumentową i nie potrzebujesz bardziej nietypowych pól oraz customowego wyglądu panelu admina wybierz Keystone’a. Moim odczucie Keystone oferują jeszcze lepszy feeling niż Django, wybierając Keystone’a musisz być świadomy problemów które on posiada. W wielu przypadkach Keystone prawdopodobnie Ci wystarczy.

Jakie plany na przyszłość ma zespół Keystone’a ?

W pierwszej połówce 2017 rozwój framework’u był znikomy. Wiele osób zamartwiało się co dalej z projektem :p. Na szczęście Jed Watson ( główny programista ) zapewnił ostatnio, że prace mają wkrótce ruszyć ponownie. Trzymam go za słowo ;). Tutaj pełny topic: https://github.com/keystonejs/keystone/issues/4434.

Podsumowanie

Na podsumowanie chciałbym przytoczyć tekst z głównej strony Kesytone’a:

… framework for developing database-driven websites …

Według mnie jest to kwintesencja tego czym powinien być Keystone. Na dzień dzisiejszy brakuje mu wiele do ideału ale filozofia którą kroczy jest dobra. Moim zdaniem pomysł by uzależnić schemę bazy od tego jak wygląda UI jest bardzo dobry, dzięki temu nie musimy spędzać czasu na pisaniu panelu admina, możemy się skupić na prawdziwych problemach biznesowych. Mam nadzieję, że za jakiś czas Keystone naprawi swoje błędy albo jakiś inny framework JSowy wejdzie na rynek i uzupełni lukę 😉