Feathers: Asynchronous initialization of Feathers services

Created on 5 Feb 2017  Â·  64Comments  Â·  Source: feathersjs/feathers

This is real issue that I've recently had, but I guess this concerns the framework in whole. I think that it is natural for Feathers services to be asynchronously initialized. They can pick their configuration from asynchronous sources, or it can be something else.

In most cases synchronous application initialization is presumed, but I've encountered design problems when started using Sequelize.

While Mongoose does some good job on accumulating promises inside for initialization actions (connection, index creation), so queries are automatically chained to initial promises, Sequelize does nothing like that, e.g. when sync() is called.

I would expect that SQL database is ready at the moment when web server has been started, but when the application is defined in synchronous manner, it will not. And this is what happens with typical Feathers setup. In some cases async initialization may take several ms, in other cases it may take seconds, while the server will pollute logs with errors that should have been never happen normally.

Another problem is error handling. If there are uncaught errors during async service initialization, the reasonable move would be to halt app initialization and catch errors in a single place.

Here's a small example that explains the idea and is based on the structure proposed by Feathers generator template.

app.js:

const app = require('feathers')();

// THIS!
app.set('feathersServicePromises', []);

app
.use(require('body-parser').json())
.configure(require('feathers-hooks'))
.configure(require('feathers-rest'))
.configure(require('./middleware'))
.configure(require('./services'));

// THIS!
Promise.all(app.get('feathersServicePromises'))
.then(() => app.listen(3000), console.error);

services/index.js:

module.exports = () => {
    const app = this;

    app
    .configure(require('./foo-async-service')
    .configure(require('./bar-async-service')
    .configure(require('./baz-sync-service')
};

services/foo-async-service.js:

const sequelize = require('./common/sequelize');

module.exports = () => {
    const app = this;
    const fooModel = sequelize.model('foo');

    // AND THIS!
    const initPromise = fooModel.sync();
    app.get('feathersServicePromises').push(initPromise);

    app.use('/foo', require('feathers-sequelize')({ Model: fooModel  }));
    app.service('/foo');
};

The places of interest here are initPromise and Promise.all(...). It would be convenient to just return initPromise from service function, like it is done in hooks. And Promise.all(...) could probably be lazily executed with app object toPromise() method or promise getter property. So it could be

module.exports = () => {
    ...
    return fooModel.sync();
};

in service function, and

app
...
.configure(require('./services'))
.promise
.then(() => app.listen(3000), console.error);

in entry point.

I suppose that the pattern I've used for async initialization is already simple and clean, but it would be great if the framework would take this job.

My suggestion is to place this feature under consideration.

I would appreciate any thoughts on the subject.

Breaking Change Core Feature

Most helpful comment

This has become a fairly long discussion but here is the proposal to solve the issue of asynchronous application setup. As it turns out, Feathers already has a pretty established way of handling asynchronous method workflows, namely Hooks. Based on #924 by @bertho-zero to allow adding hook functionality to any asynchronous method, we now have a way to make setting up an application or specific service much easier.

Asynchronous service.setup, app.setup and app.listen

This will be the breaking change making it Feathers core (@feathersjs/feathers) version 4. service.setup, app.setup and app.listen will return a Promise and run asynchronously.

__Before:__

const logger = require('./logger');
const app = require('./app');
const port = app.get('port');
const server = app.listen(port);

server.on('listening', () =>
  logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
);

__Now:__

const logger = require('./logger');
const app = require('./app');
const port = app.get('port');

app.listen(port).then(server => {
  logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
});

Service setup hooks

Also tracked in https://github.com/feathersjs/feathers/issues/853, service.setup will now be asynchronous (return a promise) allowing to add hooks to it:

app.service('myservice').hooks({
  before: {
    async setup(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  }
});

Application setup hooks

Application setup hooks will be part of the already existing app.hooks. The difference for setup hooks will be that they only run __once__ when app.setup() or app.listen() is called instead of for every service call.

app.hooks({
  before: {
    async setup(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  },

  error: {
    async setup(context) {
      if(isSomeRecoverableError(context.error)) {
        restartInTenMinutes();
      }

      return context;
    }
  }
});

Differences for setup hooks

setup is slightly different than other service methods in that

  • It can not be called externally
  • It will only be called once during the application lifecycle
  • It will never have a specific user context
  • It has no params, data or id

This means that:

  • As already mentioned, application setup hooks will only run once instead of for every service like other application level hooks
  • all hooks will __not__ be applied to setup to avoid breaking existing hooks

All 64 comments

I've been thinking about that a couple of times as well. The problem was that it will be a breaking change but we can probably get in with https://github.com/feathersjs/feathers/issues/258.

@daffl #258 is huge. I've got my share of Hapi before, and it is totally different beast under the hood, the work on unification looks really tough. And Passport+Hapi initiative was abandoned years ago, so it is a fresh start.

As for configure alone, the thing described above is quite easy to implement and doesn't introduce breaking changes. Just not sure how many cases it will cover in larger perspective.

Well, after suggesting that service creation should return a function the other day, so as to signal to user that it's a dynamic thing, the next thought was to return a Promise. So no matter the order of service creation when you use .then() you can be sure the service is already initialized. However, you do the Promise scheme it's gonna be better than how it is at present :) A very minor nitpick, but has big consequence for DX imo.... Circular references between services are common place.

@idibidiart I'm not sure if I understood correctly, does the code above fully cover the scenario you've described?

Probably service setup may return a promise, too?

@bisubus if I understand your proposal correctly (and I am knew to the Feathers and Express way of things), my concern is that A) it is a breaking change since the app.service function doesn't return a promise and B) slow startup time as you have to wait on all services to launch as opposed to starting the app and then yielding/awaiting until promise for a given service resolves before proceeding with service invocations.... if that makes any sense.

@idibidiart In implementation above it was done for configure functions only (this is where services are usually defined), not for service itself. Currently returned value from configure function is ignored, this allows to return something (a promise) from there without breaking anything. I guess that a similar thing can be done in service setup functions, currently they don't return values, this can be used too.

This proposal in its current form doesn't slow or break anything. It just provides a promise of initialization, it can be either chained to make sure that everything is ready or ignored. In my case I chained app.listen(...) because it made perfect sense there.

i see what you mean! Thanks for the clarification. I think it makes sense to do it the way you've described including returning a promise from setup. What remains vague to me is where you say that app.service returns an instance of the service. I've gotten undefined when invoking it while the service is not ready. You're suggesting to wait on the setup promise to resolve prior to invoking app.service?

@idibidiart Do you have a real-world example where app.service(..) can face a race condition?

I didn't mean that it will wait for setup promise in app.service(..) invocation, this would make app.service(..) to return a promise of a service, doesn't look good.

Yes I have an example I can describe and point you to.

I did not mean to wait for setup promise in app.service but to 'await' it from an async function (or use generator equivalent) before invoking the service methods. Won't require app.service to return a promise.

The example is where graphql service is configured before the services it depends on so when invoking app.service when graphql service is launched those services don't exist yet.

the thing is the dependency graph for services may have cycles (service A depends on service B and service B depends on service A) so we can't guarantee a linear dependency order, which is why I was thinking of waiting on setup promise resolving before proceeding with app.service invocation (again, if that makes any sense!)

@idibidiart By 'graphql service is launched' you mean setup method in graphql service, right? Because other methods (CRUD) won't be called until the server starts (and this can be guaranteed with app.toPromise().then(() => app.listen(...))).

From my understanding of how Feathers service registration works, services will become available in the order in which they are defined, so the solution is to have app.configure(...) with dependency services definitions to be executed earlier than app.configure(...) with graphql service definition.

Yes, the thing you're describing is usually addressed with DI container. I like DI a lot, but here it looks like overkill to me. With Node modules, the proper order of service definition can be relatively easily maintained.

But you've touched unon interesting subject. There may be scenarios where it may be beneficial to have a promise of some particular service (returned from service setup), not just a resulting promise from Promise.all. I guess that this can be solved by storing initialization promises as a map, not as an array. This way the promises for separate services can be retrieved any moment, like app.service(...).toPromise().then(initializedService => ...). But I don't think that this can be used as a replacement for DI to solve the problem you've mentioned, returning a promise for yet undefined service will silently cause pending promise if there are circular dependencies.

The problem was in this project: https://github.com/advanwinkel/feathers-apollo-SQLite

Note how graphql is configured before the services that it uses in the resolvers. Moving app.service calls to the dynamic parts of the resolver fixes it. Correcting the configuration order fixes it too.

DI is too much agreed.

So if you look at src/services/resolvers.js here is what I mean:

`
// export async function
export default async function Resolvers(){

let app = this;

// used to have: let Posts = app.service('posts') but that returned undefined 
// because posts service was configured after the graphql service 
// So instead of that, I was thinking:

// let Posts = await app.service('posts')
// which in this version does imply app.service returns a promise

return {

  // Type Resolvers (type, args, context)
  User: {
    posts(user, args, context){
     // Posts will be defined whereas it would be undefined without the promise/await
      return Posts.find({
        query: {
          authorId: user.id
        }
      });
    }
  },`

So elsewhere we'd have Resolvers().then(...)

Not sure what setup() does. In this case, the graphql "service" (aka graphqlExpress) does not have the Feathers service interface.

And what I meant by invocation-time circular dependency is like when Post resolver uses Users service and User resolver uses the Posts service. It wouldn't be a problem in this case because we're awaiting on Users service to resolve before calling it from Post resolver and awaiting on Posts service to resolve before calling it from User resolver. So I guess it's not a circular dependency between the Posts and Users service but a reciprocal relationship between the resolvers that depend on those services. Sorry to confuse.

Updated comments accordingly

I hope some of this makes some sense generally speaking despite my not fully grasping the way things work in Express and Feathers.

@idibidiart Yes, I've understood you correctly then. This is solved by maintaining proper users-posts-graphql configure blocks order. This is a case exactly for DI, so it's not too much - just not for everyone. Most devs (myself included) would prefer to keep it simple if possible.

In the example you've shown graphql is not really Feathers service (they conventionally have setup method that contains all initialization code), so it doesn't fit the pattern I've described for setup. It is just a middleware that is defined in configure block (it could return a promise from there if needed).

The problem with promises of services that haven't been defined yet is that if Graphql depends on User, and User depends on Graphql (possibly unintentionally, and there may be more tiers), we will have classic circular dependency. This will result in pending promise with no error (while DI would throw CD error). Looks like antipattern to me.

This is why I think that this is a job for DI, not for promises alone. It would be nice to have DI container as an optional module, not necessarily a core part. If it could leverage existing initialization promises to perform async DI (don't remember if I've seen a thing like this somewhere else, but it is doable), this would be even better.

@bisubus

I believe I managed to confuse you in part. In the Feathers-GraphQL approach, Users service will never depend on GraphQL, as GraphQL is the only API end point and is the main entry.

What I was trying to solve for in the example code I shared is to ignore the configuration order because that is the anti-pattern: you cannot depend on order being linear, i.e. that their may be circular dependencies (DI is obvious solution there) but not in this case. But that is the reason I did not wish to resort to a fix that depends on configuration order.

By having a service return a promise I'm not solving the circular dependency case but I am solving another problem: wrong configuration order can lead to service being undefined and that presents a mystery to new comers who are not experienced enough with the way things work i Express/Feathers, so having to wait for the promise then proceeding with the remainder of the Resolver function (non-blocking thanks to async stack) we can assure that the Users or whatever service graphQL depends on is not undefined and that requests that may come to GraphQL before those services are defined will probably cause the server to throw an error because Resolvers.then(...) will not have setup the GraphQL service by then. But at least I can then easily tell what the issue is w/o having to understand the thing about configuration order.

It was a thought experiment. The learning outcome from it and this discussion (which forced me to think more about it) was that the simplest and easiest way is what @daffl had proposed and what i discovered while trying to fix it, which is to call the app.service(...) from within the resolvers, so that we have dynamic state. If services did have a circular invocation-time dependency, i.e. or more clearly stated: if they had 'reciprocal relation' in terms of service A calling service B and vice versa, that is not a problem. If the services were actually dependent on each other in terms of their instantiation then DI would be needed.

@idibidiart I see your point. Thanks for explaining the example with Graphql, it adds some details.

Circular dependency was the most obvious pitfall in this situation, that's why I mentioned it. Also, there will be pending promise if there is no User or Post at all. If a thing like this should be done on framework level, these concerns cannot be ignored, because unexpected pending promise will be a PITA to debug.

The telling sign that the promise is pending is that the graphql service won't be there when we try to send it a request before the Feathers services it depends on are resolved (due to exported async Resolvers function returning a promise and being a dependency of GraphQL service, so we won't create the GraphQL service till the Resolvers promise resolves (unfortunate mixing of semantics here) We can find out which Feathers service is pending resolution with simple logging following each 'await' statement.

But I agree that is not a very formal way of dealing with the issue.

@idibidiart The situation with pending promises can be always handled with Bluebird .timeout(), but I would consider this a hack.

Yeah like I said the telling sign in the example I shared is that the GraphQL service will not have been instantiated so Express/Feathers will return a 404. Easy to conclude that a promise fora service it depends on didn't resolve.

But I'm not high on that approach.

A declarative, event-driven state machine could be setup to handle the most complex startup/boot-up sequence. If there is interest I can propose an imperative version based on ES6 generators, which I believe node supports. I've done this sort of complex startup orchestration before in the UI.

@idibidiart If you have something particular in mind, it would be nice to see it as a repo branch. There is possibly something that we could come up with.

Just to let you know that I had a similar problem and fixed it with I think a more simple approach, I overrode the configure() method like this:

app.configure = async function (fn) {
  await fn.call(this)
  return this
}

I also simply call the function given as parameter but await for it, so that you can have async operations in your callback. This means that if the lifecycle of your app is sequential you await app.configure() then app.listen(), otherwise you continue as before.

I hope this helps.

@claustres Yes, this works as temporary solution but unfortunately, breaks API, app becomes unchainable. Hope we'll come up with something in v3.

Yes for sure it does not solve the backward compatibility issue...

@claustres @bisubus Would there be something against it to make it a separate method configureAsync()? Then the backward compatibility is still guaranteed, and enables us to use those async patterns.

Yes it could be. I was also thinking of a way to make async functions chaining. Strictly speaking you can await in an expression so we could write something like this await (await app.configure()).configure() althought not really easy to read with many calls. Maybe a good chainable API would be something like this:

.configure()
.await() // This configure is async and we wait until it finishes
.configure() // This configure is sync or async but we don't wait for it
.configure()
...

The question is how to implement it ? Maybe by analysing the return the fn.call(this), if it is a promise (async) we store it and the next call to .await() in the chain will wait for it, otherwise it works as usual.

@claustres Yes, that's the problem. configureAsync is unchainable and thus inconsistent with current API ( await (await ... ) ... doesn't count). The important problem that configureAsync could address is full control over awaited promises, so they could be chained manually in series or in parallel. E.g. when configure(bar) depends on configure(foo) promise but not on configure(baz). Same applies to service. I expect that the whole thing can be addressed by extending current API with no breaking changes. I'll give it a try soon.

I have not added this in v3 (maybe in 3.1) but this should be solvable with a fairly simple plugin:

module.exports = function() {
  const app = this;
  const oldConfigure = app.configure;

  app.ready = Promise.resolve();

  app.configure = function(fn) {
    app.ready = app.ready.then(() => Promise.resolve({
      callback.call(this, this);

      return this;
    }));

    return this;
  }
}

This would allow to return a promise (or use an async function) from am .configure callback and before starting your app you just await app.ready (the slightly finicky part is that you will have to wrap this in a self-executing async function since you can't use async/await at the module level).

I think I get your point although there is some typo in your code that might mess me up, it is similar to what I was thinking about with my await() except you force each configure to be async, store the current one in app.ready and update it with the next one when done. In this case await app.ready will launch the configure sequence.

Ah, fixed the mistake. In this case configuration will happen right away when you call .configure but in sequence with each waiting for the previous to resolve.

@daffl Great. Yes, that's how I initially approached the problem. There are several considerations that appeared in a lengthy discussion above, I'm working on the implementation.

Init promises are internally stored, for debugging purposes and enhanced control flow. It could be app.resolvers Map or so.

Having them in series is safe yet very inefficient. This is the case I've encountered with multiple DB connections and other init routines. It looks like we need a dependency graph to manage this efficiently. It may need to be aware of circular deps, but otherwise it's quite simple. Resolver promises could be be identified in Map by their configuration functions, i.e app.configure(foo).configure(bar, { deps: [foo] }).configure(baz, { deps: [foo, bar] }). It will translate to something like

Promise.all([
  resolvers.get(foo),
  resolvers.get(foo).then(() => resolvers.get(bar))
  Promise.all([resolvers.get(foo), resolvers.get(bar)]).then(() => resolvers.get(baz))
]);

We may need same functionality in service as well, since that's where business logic usually belongs. And thus possibly service setup, and thus likely app setup and nested apps, so we end up with a single boot promise or something.

I expect it to cover potential use cases with no breaking changes. What do you think about it?

If you chain the promise like I suggested all you would need to do is put things in the right order (e.g. configure your db connections before anything that requires it). I'm not sure if there is a need for building up dependency trees (and all the complexity that comes with it).

If there is things you need to do in a certain order you can always create the promise you need internally before returning it.

@daffl Chaining promises in series affects server init time, especially if some of init routines are time-consuming, e.g. Mongoose ensure indexes. Indexes are ensured in different services (models) that are loosely coupled and aren't aware of each other, yet they should be coupled and aware in order to run in parallel, and this may become very messy.

It may be even worse for other DBs and scenarios; as for Mongoose, it chains connection promise internally at least, so dependency thing can be skipped for connection object before making other Mongoose async operations.

I ended up replicating same pattern I've described above by managing and chaining promises manually in app.resolvers, and I believe this is something that the framework could take care of in declarative manner.

Well I agree with @daffl in that the life cycle of an app might be so complex (eg in a distributed micro service environment) so that the cost of engineering a generic life cycle framework is not worth it. Feathers provide a built-in way to configure an app but it is only a guideline, if it can manage async functions it will be great because in node.js most functions are, but IMHO it is sufficient.

Indeed, if it does not fit your needs you can build your own initialization life cycle. I think we could take a point of view where we do not expect that Feathers provide the high-level life cycle of the app but instead the high-level life cycle is app-specific and it calls the low-level configure() when everything is ready under the hood. Moreover if you use others frameworks than Feathers it will be hard to make the different life cycles integrate in the Feathers' one.

For reference, here is - unlike my last one which totally didn't work 😉 - a complete code example:

const feathers = require('@feathersjs/feathers');

function asyncConfigure() {
  const app = this;
  const oldConfigure = app.configure;

  app.ready = Promise.resolve();

  app.configure = function(fn) {
    app.ready = app.ready.then(() => fn.call(this, this));

    return this;
  }
}

const app = feathers().configure(asyncConfigure);

app.configure(function() {
    console.log('First');
    return new Promise(resolve => setTimeout(resolve, 2000));
});

app.configure(async function() {
    console.log('Second');
});

app.ready.then(() => {
    console.log('Feathers app can be started here');
});

You can run it here.

I think I'm running into the same thing with feathers-knex and PostgreSQL.

I have a bunch of services whose models have foreign key relations to each other that I created using the feathers generator. Since db.schema.createTable is async, the order that the tables get created in can be pretty unreliable, leading to a table being created before the table that it has an FK relation to.

Using the asyncConfigure plugin, I can get the order to be correct if I always return promises from the service and the model. That seems to break the REST endpoints though; they result in 404's now.

Are the models not able to handle FK creation?

Is your service setup also correctly awaited in your config functions in case it is async ? And is also the setup of the Not Found middlware done last after everything else has been setup ?

I ran into a similar issue once, my services were initialized after the Not Found middleware causing 404...

Thanks, my services were indeed getting initialized after the Not Found middleware.

Also, my app.js file from the generator was calling app.configure(services), and that services function called app.configure on each service. Calling services() directly instead seems to allow asyncConfigure to work as I can wait for app.ready.then to start the Not Found middleware.

Hey @all! You have worked around async instantiation of services "until app bootstrap" if I understand correctly? What if I want to create services completely sporadically during runtime? Anytime after bootstrap in a "proxy service"?

Any chance of getting a workaround for this scenario?

I posted on Slack: If I disable the NotFound middleware would that make the "runtime created services" work (with all drawbacks in mind) ?

Yes without the middleware you will be able to add new services dynamically at run time.

Some people reported here https://github.com/kalisio/feathers-distributed/issues/3 that using the NotFound middleware while adding dynamic services after app startup works in Feathers v3, could anybody confirm ? Thanks

For socket it does anyway but you are asking with regards to REST I guess? I actually have a scenario I need to cover in the next days involving this... Will get back.

Yes, thanks for your feedback.

Defintely NOT working. Take app.use(express.notFound()); off and it works...

My solution. See the gist for async-chain.

Don't call app.get('configurationConstant') in app.js while inside the chain.
Return a promise from your configurations to make them async.
Remember to wait for the app to be ready.

const path = require('path');
const favicon = require('serve-favicon');
const compress = require('compression');
const cors = require('cors');
const helmet = require('helmet');
const chain = require('./lib/async-chain'); // https://gist.github.com/arleighdickerson/df30fd9d0fa6873983785e244f9d3b59

const feathers = require('@feathersjs/feathers');
const configuration = require('@feathersjs/configuration');
const express = require('@feathersjs/express');
const rest = require('@feathersjs/express/rest');
const socketio = require('@feathersjs/socketio');

const handler = require('@feathersjs/errors/handler');
const notFound = require('@feathersjs/errors/not-found');

const middleware = require('./middleware');
const services = require('./services');
const appHooks = require('./app.hooks');
const channels = require('./channels');

const authentication = require('./authentication');

const sequelize = require('./sequelize');
const redis = require('./redis');
const mongoose = require('./mongoose');

const config = configuration().bind(global)(); // need to bypass the chain here

// Load app configuration

const app = chain();

app.configure(configuration());

// Enable CORS, security, compression, favicon and body parsing
app.use(cors());
app.use(helmet());
app.use(compress());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(favicon(path.join(config.public, 'favicon.ico')));
// Host the public folder
app.use('/', express.static(config.public));

// Set up Plugins and providers
app.configure(rest());
app.configure(socketio());

app.configure(redis);
app.configure(sequelize);
app.configure(mongoose);

// Configure other middleware (see `middleware/index.js`)
app.configure(middleware);
app.configure(authentication);
// Set up our services (see `services/index.js`)
app.configure(services);
// Set up event channels (see channels.js)
app.configure(channels);
// Configure a middleware for 404s and the error handler
app.use(notFound());
app.use(handler());

app.hooks(appHooks);

const ready = app.chain(express(feathers()));

module.exports = new Proxy(app, {
  get(target, name) {
    return name === 'ready' ? ready : target[name];
  },
});

...

app.ready.then( app => {
  // $$$ profit $$$
})

@arleighdickerson how did you handle authentication? I'm trying your way but authentication (local, jwt) isn't working. It throws Error: options.service does not exist. Make sure you are passing a valid service path or service instance and it is initialized before @feathersjs/authentication-jwt..

@daffl I'm also getting errors on the authentication with your modified configure. In this case it throws Unhandled Rejection at: Promise Promise .... Cannot read property 'hooks' of undefined, because app.services('authentication') is undefined.
How are you making it work with authentication?

In case it matters I'm using feathers-knex, and the project/services/authentication were created with the generator.

My situation is the same as @mwojtul describes, db.schema.createTable is async and if the models aren't created in a specific order references are created before the tables they depend on, etc.

Is it there any prevision to provide an implementation for this in the core? Also, some information about this could be added in the documentation, I could create a PR when I get it working.

hello - i have a working example of authentication. please let me know if
you are interested. it was not that easy to get working, imho. i have
both google & github very simple examples.

Thank you,

Mark Edwards

On Sun, Apr 15, 2018 at 6:16 AM, Albert Casanovas notifications@github.com
wrote:

@arleighdickerson https://github.com/arleighdickerson how did you
handle authentication? I'm trying your way but authentication (local, jwt)
isn't working. It shouts Error: options.service does not exist. Make sure
you are passing a valid service path or service instance and it is
initialized before @feathersjs/authentication-jwt..

@daffl https://github.com/daffl I'm also getting errors on the
authentication with your modified configure. In this case it shouts Unhandled
Rejection at: Promise Promise .... Cannot read property 'hooks' of undefined,
because app.services('authentication') is undefined.
How are you making it work with authentication?

In case it matters I'm using feathers-knex, and the project/services/authentication
were created with the generator.

My situation is the same as @mwojtul https://github.com/mwojtul
describes, db.schema.createTable is async and if the models aren't
created in a specific order references are created before the tables they
depend on, etc.

Is it there any prevision to provide an implementation for this in the
core? Also, some information about this could be added in the
documentation, I could create a PR when I get it working.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/feathersjs/feathers/issues/509#issuecomment-381405820,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACyd4oxRps-7QXxBPSs5fDHTupIgIqkiks5to0g9gaJpZM4L3ppM
.

@edwardsmarkf that would be really helpful!

be careful what you wish for:

https://github.com/edwardsmarkf/fastfeathers

github-login and gmail-login are the shortest ones. my goal is to have
bash-scripts that can be ran (or cut/pasted) that make full use of the
feathers-CLI to minimize efforts.

jsgrid-sequelize uses the totally awesome js-grid as a front-end really for
illustration only. i have tested it against mariaDB, cockroachDB,
postgresql, and timescaleDB, and hopefully google-spanner soon.

i will start working on https://fastfeathers.site soon and have all this
available.

please let me know what you think.

Thank you,

Mark Edwards

On Sun, Apr 15, 2018 at 5:07 PM, Albert Casanovas notifications@github.com
wrote:

@edwardsmarkf https://github.com/edwardsmarkf that would be really
helpful!

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/feathersjs/feathers/issues/509#issuecomment-381449095,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACyd4o245q0lc1Uzg63eroDDEJFTAMEmks5to-CtgaJpZM4L3ppM
.

@edwardsmarkf thanks for sharing your project, you clearly put some hours in there :wink:. I checked the *-login files but I don't see how do you solve the authentication problem I'm having.

thanks for sharing your project, you clearly put some hours in there

ahhh love compliments! love 'em!

I don't see how do you solve the authentication problem I'm having.

sorry about that. please let me know what you find out.

i am hoping that we will see some sort of "feathers cookbook" for all the
common issues. feathers (like all frameworks) makes it look pretty easy to
get started, but one quickly runs into issues. and of course feathers is
still pretty new.

when i first started with feathers, they were in a transition between auk
and buzzard, and none of the examples were working. i wanted to help
people who came after me to get into feathers, and that is why i started
fast-feathers. its always much easier to study and explore something that
is already working, imho.

thanks for writing back.

Thank you,

Mark Edwards

On Tue, Apr 17, 2018 at 10:57 PM, Albert Casanovas <[email protected]

wrote:

@edwardsmarkf https://github.com/edwardsmarkf thanks for sharing your
project, you clearly put some hours in there 😉. I checked the *-login
files but I don't see how do you solve the authentication problem I'm
having.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/feathersjs/feathers/issues/509#issuecomment-382271180,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACyd4nD9E0OPUickI7KOS-423Rfl8oYRks5tptW2gaJpZM4L3ppM
.

This has become a fairly long discussion but here is the proposal to solve the issue of asynchronous application setup. As it turns out, Feathers already has a pretty established way of handling asynchronous method workflows, namely Hooks. Based on #924 by @bertho-zero to allow adding hook functionality to any asynchronous method, we now have a way to make setting up an application or specific service much easier.

Asynchronous service.setup, app.setup and app.listen

This will be the breaking change making it Feathers core (@feathersjs/feathers) version 4. service.setup, app.setup and app.listen will return a Promise and run asynchronously.

__Before:__

const logger = require('./logger');
const app = require('./app');
const port = app.get('port');
const server = app.listen(port);

server.on('listening', () =>
  logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
);

__Now:__

const logger = require('./logger');
const app = require('./app');
const port = app.get('port');

app.listen(port).then(server => {
  logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
});

Service setup hooks

Also tracked in https://github.com/feathersjs/feathers/issues/853, service.setup will now be asynchronous (return a promise) allowing to add hooks to it:

app.service('myservice').hooks({
  before: {
    async setup(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  }
});

Application setup hooks

Application setup hooks will be part of the already existing app.hooks. The difference for setup hooks will be that they only run __once__ when app.setup() or app.listen() is called instead of for every service call.

app.hooks({
  before: {
    async setup(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  },

  error: {
    async setup(context) {
      if(isSomeRecoverableError(context.error)) {
        restartInTenMinutes();
      }

      return context;
    }
  }
});

Differences for setup hooks

setup is slightly different than other service methods in that

  • It can not be called externally
  • It will only be called once during the application lifecycle
  • It will never have a specific user context
  • It has no params, data or id

This means that:

  • As already mentioned, application setup hooks will only run once instead of for every service like other application level hooks
  • all hooks will __not__ be applied to setup to avoid breaking existing hooks

Hi, great news and thanks for the detailed response.
Would this mean that we don't need the "disable NotFound middleware" workaround anymore?

Handle it using hooks seems great although I would add a new hook type to handle this use case (eg setup hooks). Indeed using regular hooks raises confusion IMHO because they seem to work like any other hook but actually they don't...

Hook initiative looks interesting. Seems like there's no support of promises for middlewares. Is a middleware supposed to be wrapped with dummy service to enable promises?

This functionality will not affect how middleware runs. It is possible to register middleware in a setup hook after other asynchronous things have happened though:

setup: [ doAsyncStuff, context => context.app.use(errorHandler) ]

@claustres I was debating that as well, something looking like:

app.service('myservice').hooks({
  before: {},
  after: {},
  error: {},
  setup: {
    before(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  }
});

app.hooks({
  before: {},
  after: {},
  error: {},
  setup: {
    before: createDatabaseTableIfNotExists(),
    after: startMonitoring(),
    error: restartInTenMinutes()
  }
});

It adds some inconsistencies when it comes to retrieving and registering hooks and doing codemods but it is more explicit than having some internal exceptions you have to know about.

@daffl I have a question about this new way of handling the setup: what should go in the setup method and what should go in the hooks?

I think the same as with service methods. Anything that the service needs to work by itself should be implemented in setup. Anything that the application needs to set up the service should be added via hooks. I also just realized that there should be a service mixin that adds a setup method if there isn't one otherwise the hooks will blow up.

Just to be sure: Would this officially support creating services at runtime anytime after start of the app (in my case user triggered) ?

This already works for websockets and is more of an Express than a Feathers limitation. You could create an updated notFound handler to be registered _after_ all custom middleware but _before_ your services and have it check the URL against the paths in app.services and only throw the NotFound error if nothing matches.

Is this working? The documentation of setup indicates that it returns a promise. Will the app wait for the promise or should we use the hooks as indicated here. Because the documentation of hooks does not indicate any hook for setup.

What is, currently, the right way to make the setup wait?

I'm think is missing returning a promise in app.configure(), that was the original cause for this issue. There is no way of aborting in case of fail during configuration (eg. database not available)

What's the status on this issue?
I am using version 4.3.0-pre.2 and service.setup methods don't seem to be asynchronous yet.

This will be part of v5 using the new hook system (https://github.com/feathersjs/feathers/issues/932).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

codeus-de picture codeus-de  Â·  4Comments

stephane303 picture stephane303  Â·  3Comments

corymsmith picture corymsmith  Â·  4Comments

NetOperatorWibby picture NetOperatorWibby  Â·  4Comments

arve0 picture arve0  Â·  4Comments