Skip to content

Migration Guide (0.25.x 1.0.0 rc.x)

Michael Laccetti edited this page Jun 22, 2018 · 5 revisions

Migration Guide (0.25.x -> 1.0.0-rc.x)

Core Components

Core Ravel components are now defined using decorators instead of via class extension.

Modules

const Ravel = require('ravel');
const Module = Ravel.Module;
class MyModule extends Module {
 // ...
}

becomes

const Ravel = require('ravel');
const Module = Ravel.Module;
@Module('name') // the injection name for your module. Brackets and argument can be omitted for name inference as before.
class MyModule {
 // ...
}

Routes

const Ravel = require('ravel');
const Routes = Ravel.Routes;
class MyRoutes extends Routes {
 constructor () {
   super('/my/base/path');
 }
 // ...
}

becomes

const Ravel = require('ravel');
const Routes = Ravel.Routes;
@Routes('/my/base/path')
class MyRoutes {
 // ...
}

Resources

Same as Routes, now appearing as:

const Ravel = require('ravel');
const Resource = Ravel.Resource;
@Resource('/my/base/path')
class MyResource {
 // ...
}

New Feature: Autoinjection

In Ravel, it was common to inject several things into a class and then perform assignments in the constructor. This has been streamlined via the addition of the @autoinject decorator. This decorator can be mixed with the @inject decorator for those injectables which require some instantiation in your constructor before assignment to this.

const Ravel = require('ravel');
const Module = Ravel.Module;
const inject = Ravel.inject;
@inject('moment', 'fs')
class MyModule extends Module {
 constructor(moment, fs) {
   this.moment = moment;
   this.fs = fs;
 }
}

becomes

const Ravel = require('ravel');
const Module = Ravel.Module;
const autoinject = Ravel.autoinject;
@Module('name')
@autoinject('moment', 'fs')
class MyModule {
 // this.moment and this.fs are
 // now automatically available in
 // any method, after construction
}

Core Services

Since class extension has been eliminated, core services such as errors this.ApplicationError or logging this.log have become "core services" which are available for injection into any application component.

  • this.app can be injected via @autoinject('$app') and then used as this.$app
  • this.ApplicationError -> @autoinject('$err') -> this.$err
  • this.log -> @autoinject('$log') -> this.$log
  • this.kvstore -> @autoinject('$kvstore') -> this.$kvstore
  • this.params -> @autoinject('$params') -> this.$params
  • this.db -> @autoinject('$db') -> this.$db

Fields of app have been renamed to be consistent with these new injection names (i.e. app.log is now app.$log).

Loading Components

Directory scanning is now consolidated into app.scan. Classes can also be loaded directly via app.load, which makes testing much easier.

  • app.modules() -> app.scan()
  • app.resources() -> app.scan()
  • app.routes() -> app.scan()

Note that calling app.scan() on your entire tree of Modules, Resources and Routes is completely viable, though it may change the inferred injection names of your Modules (since you'll have changed the directory you are scanning "from" to be one level higher than before).

app.load is useful for testing:

const Ravel = require('ravel');
const Module = Ravel.Module;
@Module('name')
class MyModule {
}
const app = new Ravel();
// ...
app.load(MyModule);
// ...

Registering Providers

The original approach to provider registration is not very eslint-friendly, since it relies on instantiation side-effects. The new approach is better:

new MySQLProvider(app, 'mysql');

becomes

app.registerProvider(MySQLProvider, 'mysql');

New Feature: @middleware

Creating middleware in Ravel used to look something like this:

const MyResource extends Resource {
  constructor() {
    super('/base/path');
    this.myMiddleware = async (ctx, next) => { /* ... */ }
  }

  @before('myMiddleware')
  async getAll(ctx) {
    // ...
  }
}

While this worked, it often led to bloated constructors for Routes and Resource classes, and forced business logic to live there when it would have been better suited for definition in a Module. Ravel now includes the @middleware decorator to permit the definition of middleware in Modules:

@Module('middleware')
const MyMiddleware {
  @middleware('my-middleware') // give it a name
  async handler(ctx, next) {
    // ...
  }
}

// then

@Resource('/base/path')
const MyResource {
  @before('my-middleware') // use it directly, by name, without injection!
  async getAll(ctx) {
    // ...
  }
}

New Feature: Environment Variable Support in .ravelrc.json

You may now reference environment variables in .ravelrc.json and they will automatically be loaded from the environment during app.init():

{
  'redis host': '$REDIS_HOST',
  // ... 
}

Async Lifecycle

Ravel lifecycle methods are now async, and require an IIFE until node adds top-level async support:

(async () => {
  await app.init();
  await app.listen();
  await app.close();
})();

This has the added benefit of ensuring that lifecycle-decorated methods in Modules will finish before the next lifecycle event occurs. Declare your lifecycle-decorated methods async and be sure to return and/or await on Promises whenever possible:

@prelisten
async handler () {
  // do something async
}

Reduction of References to Koa

  • app.set('koa public directory') is now app.set('public directory')
  • app.set('koa favicon path') now app.set('favicon path')`
  • Session cookies for Ravel are now ravel.sid and ravel.sid.sig instead of koa.sid and koa.sid.sig.

Deprecation of Integrated Templating

app.set('koa view directory') and app.set('koa view engine') are now deprecated. The underlying koa-views library was not particularly functional, and using pug etc. directly is easy, well-documented, and eliminates needless wrapper libraries. In general, wrapper libraries will be avoided whenever feasible.

New Feature: Accessing Components

Useful for testing. Ravel application components can now be accessed via app.module('injectionName'), app.routes('/base/path') and app.resource('/base/path') after app.init(). This means that a Ravel app can be bootstrapped and unit tested without having to provide mocks for $log, $kvstore, etc. See the new docs for more information.

New Feature: WebSockets

Ravel now includes WebSocket integration. See the docs for more information.

New Feature: KeyGrip Key Rotation

Using signed session cookies is an important concept in Ravel. Productionizing this means exposing a mechanism for clients to rotate signing keys periodically. This can now be accomplished via app.rotateKeygripKey, or in a Module via $app.rotateKeygripKey. It is encouraged to store and retrieve keys in an external store, such as redis and then synchronize rotations between Ravel application instances. Automated synchronization will be considered before the 1.0.0 release (i.e. calling rotateKeygripKey on an instance of your app causes all nodes in the cluster to rotate).