Sails.js

Teil 1: Sails.js Einführung in das Backend MVC Framework

Von am 23.01.2014

Serverseitiges JavaScript ist seit einer Weile in aller Munde und genau das hat sich Mike McNeill zunutze gemacht um ein full-stack Framework zu erschaffen, welches nun ja, eben “mehr” kann und “weniger” verlangt. Es basiert auf dem MVC Muster und ist an Ruby on Rails angelehnt. Warum Sails.js jedoch wirklich glänzt und wieso entwickeln von skalierbaren, service orientierten APIs noch nie zuvor einfacher war, soll dieser Artikel beschreiben.

Sails.js baut auf dem Express.js Web Framework auf und erweitert dieses um genau die Funktionalitäten wofür es für eine komplexe Backend Applikation zu schlank ist.

Waterline ORM

Mike McNeill hat nicht nur Sails entworfen, sondern auch Waterline.js, einen schlanken Datenbank agnostischen O/R Mapper der von Sails direkt verwendet wird. Waterline ist deshalb so praktisch weil es funktioniert, egal welche Datenbank man verwendet. Waterline’s Data Access Layer funktioniert über sogenannte Adapter, die man pro Datenbanksystem erstellen und konfigurieren kann. Genau genommen gibt es sogar einen vorintegrierten Filesystem Adapter der Models auf dem Filesystem abspeichert. Über GitHub findet man bereits Adapter für die verschiedensten relationalen und objektorientierten Datenbanksysteme wie MySQL oder MongoDB und schlimmstenfalls schreibt man den Adapter einfach selbst.

// config/adapters.js
module.exports.adapters = {
  'default': 'mysql',

  disk: {
    module: 'sails-disk'
  },

  mysql: {
    module: 'sails-mysql',
    host: 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS',
    user: 'YOUR_MYSQL_USER',
    password: 'YOUR_MYSQL_PASSWORD',
    database: 'YOUR_MYSQL_DB'
  }
}

Models and Lifecycle Callbacks

Models funktionieren in Sails.js genauso wie in jedem anderem MVC Framework. Es wird ein Entity erstellt, welches die Attribute beschreibt. Einziger Unterschied: Das Model ist zwar ein Objekt, die Attributswerte sind jedoch keine nativen Datentypen aus der Scriptsprache sondern Strings und werden dann pro Adapter anders für das Datenbanksystem gemappt. Dadurch ist auch keine zusätzliche Annotation für das ORM notwendig.

// models/person.js
module.exports = {
  attributes: {
    firstName: 'STRING',
    lastName: 'STRING',
    age: {
      type: 'INTEGER',
      max: 150,
      required: true
    },
    emailAddress: {
      type: 'email', // Email type will get validated by the ORM
      required: true
    }
  },
  beforeCreate: function () {
     // ...
  }
};

Models in Sails.js werden außerdem von Waterline validiert, dazu gibt es eine Liste an Typen die man sich in der Sails Dokumentation ansehen kann. Weiters ist es möglich Lifecycle Callbacks zu implementieren, das sind Funktionen die an bestimmten Zeitpunkten automatisch aufgerufen werden und auf die man wie bei Delegates reagieren kann. Typische Callback Funktionen wären:

- beforeCreate / *fn(values, cb)*
- afterCreate / *fn(newlyInsertedRecord, cb)*
- beforeUpdate / *fn(valuesToUpdate, cb)*
- afterUpdate / *fn(updatedRecord, cb)*
- beforeDestroy / *fn(criteria, cb)*
- afterDestroy / *fn(cb)*

RESTfull JSON API in 5 seconds

Sails erstellt zu jedem Model eine bereits fertig funktionierende json-basierte REST Api. Das bedeutet dass man sich überhaupt nicht mehr um eine REST Library kümmern muss, oder mühsam alle CRUD Funktionen mit den passenden HTTP Methoden implementieren muss. Die Magie dahinter steckt in den sogenannten Controller Blueprints – sprich Blaupausen – die man in der controller.js Konfiguration sehen kann.

Eine nette Einführung in die REST Features von Sails.js und eine Demo der Blueprints findet man hier:
https://www.youtube.com/watch?v=GK-tFvpIR7c

Blueprints for Actions

Schaltet man die `action` blueprints ein so erstellt Sails automatisch alle Routen für die entsprechenden Controller actions. Sprich die `sayHi` action vom Controller `Person` würde über `http://localhost:1337/person/sayHi` erreichbar sein. Das ist soweit noch nichts neues, das bieten sogar die meisten Frameworks bereits out-of-the-box an. Zusätzlich erstellen die `action` blueprints aber auch Routen für alle 4 HTTP Methoden (GET, POST, PUT, DELETE) und binden sie an jede Action Methode. Dadurch kann man in der Action bequem auf den Methodentyp abfragen und zusätzlich differenzieren. Zusätzlich bekommt jede Action einen optionalen `id` Parameter.

GET     /email/send/:id?
POST    /email/send/:id?
PUT     /email/send/:id?
DELETE  /email/send/:id?

Blueprints for REST

Etwas interessanteres sind beispielsweise die `rest` blueprints. Diese erstellen wie bereits oben angedeutet unter der Haube alle 4 CRUD Methoden mit den dazugehörigen Routen. Das ist in vielen Fällen sehr praktisch und erleichtert einem viel mühsame Arbeit, zumal diese Actions und Routen für jedes Model automatisch generiert werden. Das bedeutet also, dass Sails hier bereits folgende Bindings unternimmt:

GET      /person           -> PersonController.findAll
GET      /person/:id?      -> PersonController.find
POST     /person           -> PersonController.create
PUT      /person/:id       -> PersonController.update
DELETE   /person/:id       -> PersonController.destroy

Blueprints for Shortcuts

Die dritte Möglichkeit ist mehr oder weniger eine weitere Beschleunigung und ermöglicht es, falls aktiviert, dass Sails für alle CRUD Methoden GET Routen anlegt und an die Actions bindet. Das bedeutet also, dass man alleinig über die Browserleiste alle 4 CRUD Methoden aufrufen kann. Das ist zwar sehr praktisch fürs Testing, da man keine Forms, Javascript oder sonstige REST Client Plugins braucht um die Methoden aufzurufen, aber Sails unterstreicht hier stark dass es aus Sicherheitsgründen für den Production Bereich deaktiviert werden sollte. Shortcuts würden folgendes ausschließlich über GET möglich machen:

GET      /person/create?name=&age=      -> PersonController.create
GET      /person/:id/update?name=&age=  -> PersonController.update
GET      /person/delete/:id             -> PersonController.destroy

Socket.io, Boom!

Sails.js verwendet für alle asynchronen Requests Socket.io und somit eine flexible Library mit vielen Fallbacks. Socket.io wird dabei von Sails.js gewrappt und ist am Client sowie am Server bereits fertig implementiert. Keine Handshakes, keine manuellen Verbindungen vom Client aus, alles out-of-the-box. Es ist also nicht mehr notwendig AJAX calls z.B. per jQuery zu machen, man nimmt einfach die globale `socket` Variable am Client und ruft einfach die Route am Server auf, die die Ressource liefern soll.

// json von allen personen abrufen
socket.get('/person', function (err, data) {
    // ...
});

Assets love Grunt

Sails.js nutzt Grunt um einige Tätigkeiten zu automatisieren. Beispielsweise werden über Grunt alle neuen bzw. veränderten Assets vollautomatisch vom internen Assets Ordner in den öffentlich zugänglichen kopiert. Dabei kann man Grunt weiterkonfigurieren und CSS Präprozessoren einbetten, welche sobald sich beispielsweise eine SASS Datei verändert hat, diese direkt parst und in den public Assets Ordner verschiebt. Eine weitere Funktionalität ist die automatische Minifizierung sobald man den Modus auf Production wechselt. Sails.js nimmt dabei alle Assets her, minifiziert sie und linkt sie automatisch zurück ins Layout.

Multiple Template Engines

Ein sehr praktisches Feature ist die einfache Änderung von Templating Engines in Sails.js. Man kann entweder bei der Erstellung des Projekts über den `sails new projectname –template=engine` Befehl oder nachträglich über die Konfiguration die Template Engine definieren. Sails unterstützt dabei eine riesige Anzahl an Engines wie z.B.: underscore, hogan, haml, dust, swig, ejs, toffee, walrus, & whiskers, wobei ejs die standards templating engine ist. Es ist allerdings wichtig zu wissen, dass nach einer Änderung eventuell das Grunt File upzudaten ist, da sonst die Assets nicht richtig kopiert werden und der Server bei jeder Client Datei Änderung neugestartet werden müsste.

Integrated Async.js

Um der Callback Hell zu entkommen, welche bei stark event gesteuerten Segmenten in einer Applikation enstehen können, implementiert Sails.js bereits Async.js out-of-the-box. Async.js verfügt über ein paar sehr praktische Methoden um Callbacks parallel oder seriell ausführen zu können. Im Sonderfall kann man sogar Async.js selbst entscheiden lassen, welches Callback zuerst aufgerufen wurde. Ein einfaches Beispiel für eine serielle, statt üblicherweise verschachtelte Abwicklung:

var gameId = 7;

// normal
Game.find(gameId).done(function (err, game) {
   Reviews.findAllByGameId(game.id).done(function (err, reviews) {
      // [object] game
      // [object] reviews
   });
});

// with async.js
async.seriel({
  game: Game.find(gameId).done, // callback 1
  reviews: Review.findAllByGameId(gameId).done // callback 2
}, function (err, results) {
  // [object] results.game
  // [object] results.reviews
});

Fazit

Sails.js ist meiner Meinung `das` Beispiel schlechthin wie viel Potential in Node.js steckt. Zudem ist Sails.js gerade noch in Kinderschuhen und soll mit Version 0.10 nun auch Datenbank Relationen mit HAS_ONE, BELONGS_TO etc. mappen können. Die integrierten Blueprints für eine API sind unvorstellbar mächtig und die Integration von Socket.io macht das ganze zum Kinderspiel. Ich lege jedem Web Developer Sails.js ans Herz, der sich nach jahrelangen Krämpfen von PHP trennen möchte und ein Projekt sucht um sich mit Node anzufreunden. Das Entwickeln mit JavaScript auf sowohl Client als auch Serverseite mit den teilweise gleichen globalen Objekten ist sehr bequem. Wer PHP für seine Applikation weiterverwenden will und Sails.js als einfache API für seine Projekte nutzen möchte ist auf jeden Fall auch im richtigen Boot, denn einfacher geht es kaum.

Stay tuned für Teil 2 – die Kombination von Client und Serverseite mit Backbone und Sails von Desireé Zottl.

The comments are closed.