Comments (17)
What is actually implemented by this story? Does it need a breakdown, as in, a checklist?
from loopback-boot.
Let's borrow some ideas from JAX-RS:
https://jersey.java.net/documentation/latest/filters-and-interceptors.html
In particular:
- Types of phases (middleware, remoting, model, connector)
- Predefined phases
- Execution order
- Binding of handlers (global/name binding/dynamic binding)
from loopback-boot.
@raymondfeng here is the code scratch pad we used to pair program some of the basic implementation ideas.
var loopback = require('loopback');
var boot = require('loopback-boot');
var app = module.exports = loopback();
// Set up the /favicon.ico
app.use(loopback.favicon());
// request pre-processing middleware
app.use(loopback.compress());
// -- Add your pre-processing middleware here --
// boot scripts mount components like REST API
boot(app, __dirname);
var root = app.createPhase('root');
foreach(phase in config.phases) {
}
foreach(phase in app.phases()) {
foreach(handler in handlersForGivenPhase) {
app.register(phase.name, hander)
}
root.add(phase);
}
// juggler
User.create = function(data, cb) {
User.SOMETHING.execPhase('validate', function() {
// the validate phase!
// ... and so on...
});
}
// hook into your method
Phase.wrapSync('yourMethod', ...);
MyModel.register('beforeYourMethod', function(ctx) {
ctx.arguments
})
MyModel.yourMethod = function proxy() {
// executes concrete yourMethod
// before phase
// if sync
// executes the after phase
// async
// wrap the callback
// exec the after phase
}
Model.wrap = function(methodName, options) {
var methodPhase = Model.createPhase(key);
model[methodName] = function() {
var args = arguments;
if(isSync) {
methodPhase.add('before', methodName, function() {
originalMethod(args);
methodPhase.add('after', methodName);
});
} else {
var cb = getCb(args);
methodPhase.add('before', ..., function() {
methodPhase.add('after', methodName);
});
}
}
}
app.register('foo:*', function(cb) {
console.log(ctx.phase.name);
cb();
});
MyModel.register('afterYourMethod', function(ctx) {
ctx.arguments
})
app.models.User.register('validate', function(ctx, done) {
done(new Error('nothing is valid!'));
});
// new imp for enable auth
app.register('auth', function(ctx) {
// ...
loopback.Context
ctx.remotingContext.req
});
var explorer;
try {
explorer = require('loopback-explorer');
} catch(err) {
console.log(
'Run `npm install loopback-explorer` to enable the LoopBack explorer'
);
return;
}
var restApiRoot = server.get('restApiRoot');
var explorerApp = explorer(server, { basePath: restApiRoot });
server.use('/explorer', explorerApp);
server.once('started', function() {
var baseUrl = server.get('url').replace(/\/$/, '');
// express 4.x (loopback 2.x) uses `mountpath`
// express 3.x (loopback 1.x) uses `route`
var explorerPath = explorerApp.mountpath || explorerApp.route;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
});
var restApiRoot = server.get('restApiRoot');
server.use(restApiRoot, server.loopback.rest());
// -- Mount static files here--
// All static middleware should be registered at the end, as all requests
// passing the static middleware are hitting the file system
// Example:
// app.use(loopback.static(path.resolve(__dirname', '../client')));
var websitePath = require('path').resolve(__dirname, '../client');
app.use(loopback.static(websitePath));
// Requests that get this far won't be handled
// by any middleware. Convert them into a 404 error
// that will be handled later down the chain.
app.use(loopback.urlNotFound());
// The ultimate error handler.
app.use(loopback.errorHandler());
app.start = function() {
// start the web server
return app.listen(function() {
app.emit('started');
console.log('Web server listening at: %s', app.get('url'));
});
};
// start the server if `$ node server.js`
if (require.main === module) {
app.start();
}
from loopback-boot.
I have reviewed the code from the scratchpad. Few comments:
- I find the names of the new methods little bit confusing, we should come up with a more descriptive name (e.g.
onPhase(name, fn)
instead ofregister(name, fn)
) app.createPhase
androot.add(phase)
seem rather arbitrary to me, I don't understand what is the purpose. It's not a problem, I am just saying that I cannot asses this part.- The proposal does not handle phase ordering, nor Binding of handlers (global/name binding/dynamic binding)
Other than that, this looks like a sensible approach to me.
It would be great if we can build this incrementally in small steps. Start with something small yet complete, then add more stuff building on top of that. Perhaps start by replacing beforeValidate
and afterValidate
hooks with the new implementation?
I don't understand what is the relation of this feature with the middleware registration and ordering, if there is any relation. In particular, how will be phase.toMiddleware()
mounted on the app?
from loopback-boot.
@ritch could you please update the spec in wiki to match the final implementation?
The check-list in the issue description contains items that were not implemented AFAICT. Are you sure this task is done? ;-)
from loopback-boot.
@bajtos good catch... I though this was the PR. This should stay open until it is implemented in boot.
from loopback-boot.
reestimate, rescope
from loopback-boot.
IMO the middleware (request-processing) phases should be defined directly in loopback. That way we preserve the current design where loopback-boot is just a convention-based wrapper around features provided by loopback core.
from loopback-boot.
The more I think about integrating phases & middleware components into loopback and loopback-boot, the more confused I am about what is the actual plan.
As I see it, there are many places where we can use phases:
-
Bootstrapping phases:
applyAppConfig
,setupDataSources
,setupModels
,runBootScripts
, etc. However, using phases is not very practical here, as the boot scripts that can register phase callbacks are run towards the end, when most of the phases were already executed. -
Bootstrapping phases for middleware registration. After
runBootScripts
is done, the bootstrapper can run phases to executeapp.use
in the correct order.// boot/token.js module.exports = function(app) { app.boot.middleware.find('auth').use(function() { app.use(app.loopback.token()); }); }; // loopback-boot async.series([ function(done) { runBootScripts(app, instructions, done); }, function(done) { app.boot.middleware.run(app, done); }, function(done) { enableAnonymousSwagger(app, instructions); done(); }], callback)
-
LoopBack runtime can provide a phase list that is executed as part of request handling. A simplified implementation:
// boot/token.js module.exports = function(app) { app.middleware.find('auth').use(app.loopback.token()); }; // lib/application.js app.handlers = function(type) { // todo: memoize (cache) the final handler var remotingHandler = this.remotes().handler; // we need to inject strong-remoting handler into `route` phase // to avoid duplicate calls of the handler, we create a new clone // of the original phase list var phases = app.middleware.clone(); phases.find('route').use(function(ctx, cb) { remotingHandler(ctx.req, ctx.res, cb); }); return function(req, res, next) { phases.run({ req: req, res: res }, next); } };
-
Replace remoting hooks with phases
-
Replace Model hooks like
beforeCreate
orafterSave
with phases. Possible merge remoting hooks with these new phases.
Only the items 1 & 2 require changes in loopback-boot, items 3-5 affect loopback core.
@ritch @raymondfeng What's your opinion? What did I missed? Which items do we want to implement?
This is blocking my work on #44, which in turns blocks other stories in the sprint. Unfortunately I won't have much time to discuss this in person today. Could you please leave a comment here so that I can make a better decision on what to work tomorrow?
from loopback-boot.
Bootstrapping phases: applyAppConfig, setupDataSources, setupModels, runBootScripts, etc. However, using phases is not very practical here, as the boot scripts that can register phase callbacks are run towards the end, when most of the phases were already executed.
You are assuming the only way you can add a boot phase handler is in a boot script... Why? I think we should make it clear where you can add them that makes sense (eg. server.js).
from loopback-boot.
I think 3 is the priority and the only real blocker for other stories.
from loopback-boot.
I'd like to implement most of these. Here is the priority IMO:
-
- a phase list that is executed as part of request handling (for mounting middleware in order)
-
- a boot phase list executed during the boot process (for components to add boot tasks)
-
- hooks for methods built on phases (for apps and components)
from loopback-boot.
The more I think about integrating phases & middleware components into loopback and loopback-boot, the more confused I am about what is the actual plan.
Do you have specific questions about 3?
from loopback-boot.
I think the other important perspective for phases is to allow additional logic to plug in. For the boot process, if we anticipate custom code to be executed when an app is starting, we should phasify the steps. When a phase is run, we could emit before/use/after events too. This way, some logic can be added as event listeners too.
from loopback-boot.
Bootstrapping phases: applyAppConfig, setupDataSources, setupModels, runBootScripts, etc. However, using phases is not very practical here, as the boot scripts that can register phase callbacks are run towards the end, when most of the phases were already executed.
You are assuming the only way you can add a boot phase handler is in a boot script... Why? I think we should make it clear where you can add them that makes sense (eg. server.js).
server.js
should be as small as possible and preferably users should not touch it. There are many reasons for that, one of them is to allow generator-loopback and Studio to add/remove stuff without having to parse arbitrary javascript code in server.js
.
We can add a concept of "preboot" scripts that are run before anything else, but that adds even more complexity to the already complicated boot process.
I think 3 is the priority and the only real blocker for other stories.
Do you have specific questions about 3?
I'd say the specific question will arise when we get down to implementation. I have few suspicions on what may not work great, but let's wait until we have a code to discuss.
The question is - who is going to implement it? I am happy to volunteer. We should create a new story in loopback to track this task.
What is the scope of this issue in loopback-boot? Is it "1. a boot phase list executed during the boot process (for components to add boot tasks)"?
When a phase is run, we could emit before/use/after events too. This way, some logic can be added as event listeners too.
Phases already provide before/use/after semantics and as opposed to event emitters, it support asynchronous listener functions. I'd rather keep the matter simple and let users install before/use/after callback instead of adding event emitter into the mix.
from loopback-boot.
The question is - who is going to implement it? I am happy to volunteer. We should create a new story in loopback to track this task.
from loopback-boot.
Since the last comment was almost 4 years ago, closing due to inactivity. Please create a new issue if you are still running into problems.
from loopback-boot.
Related Issues (20)
- Subfolders in boot folder HOT 7
- [forward-port] Fix resolution of middleware modules
- Boot scripts are loaded, but not actually run HOT 9
- organizing configuration files leads to problems HOT 3
- test failing on browserify due to form-data HOT 1
- Document refactored design of loopback-boot HOT 1
- datasources duplicated when booting using a config HOT 1
- models not loaded when using config on boot HOT 1
- 3.0.0
- Async boot scripts - silent failure with security risk HOT 6
- resolveAppScriptPath() is not pass scriptExtensions to fixFileExtensions() HOT 2
- [BUG] Middleware is loading .js.map of .js HOT 1
- The middleware "loopback#static" in phase "files"is not defined in the main config
- Async operations in component initialization HOT 3
- isomorphic and uglify causing "AssertionError: must provide a valid {{SharedClass}}" HOT 1
- custom middleware not found when boot options are just __dirname HOT 2
- Why next() requires to trigger process.nextTick in bootscript. HOT 2
- export-api-def doesn't work with loopback-boot@3 HOT 1
- Address usage of config/application on boot LBOptions HOT 2
- Loopback-boot updating to 3.3.1 producting "LoopBack server listening @ https://undefined:undefined/" HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from loopback-boot.