Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicit. No middleware ? #8

Closed
Pana opened this issue Feb 2, 2016 · 7 comments
Closed

Explicit. No middleware ? #8

Pana opened this issue Feb 2, 2016 · 7 comments

Comments

@Pana
Copy link

Pana commented Feb 2, 2016

Explicit. No middleware. Modules declare all dependencies
What does it mean by no middleware? why you design it like this.

@rauchg
Copy link
Member

rauchg commented Feb 2, 2016

Middleware accomplish two things:

  • Define global behavior for routes
  • Make asynchronous code easier to write

Both are problematic. To elaborate on the first, consider a typical express application:

app.use(logger());
app.use(needsAuth());
app.get('/my-route', require('./my-route'));

Then, imagine you want to add a route /log for which you want to turn off request logging and authentication. You end up with awkward code that introduces a global whitelist of exceptions.

app.use(function(req, res, next){
  if ('/log' === req.url) return next();
  logger(req, res, next);
});

Assuming that through experience and discipline you avoid this pitfall, you probably then rely on defining middleware for each route through "sub-apps".

// boot.js
app.use(require('./my-route');

// my-route.js
const bodyParser = require('body-parser');
module.exports = express().get('/', bodyParser, function(req, res, next){
  // req.body
});

This pattern, however, still has significant problems:

  • req.body comes out of nowhere. It's unclear to someone who's never seen the codebase before that the presence of bodyParser is what made req.body exist.
  • Error handling is done by the framework in a similarly obscure way (app.error).
  • It's not clear whether bodyParser will hold up the request or not.

await + async is the solution to all of these problems.

First, the assignment operator brings clarity:

async function (req, res) {
  const body = await json(req); // explicit
}

function (req, res, next) {
  req.body                      // where did this come from? (╯°□°)╯︵ ┻━┻
}

Second, errors are handled like everywhere else:

async function (req, res) {
  try {
    await json(req);
  } catch (e) {
    // buffering failed
    // size limit exceeded
    // parsing failed
  }
}

If you don't specify try/catch and instead the error is caught by micro, it goes with a suggested code (err.statusCode), or 500 if no information can be derived from the Error.

This flexibility in error handling is very difficult to achieve with middleware. In general, middleware skip over subsequent routes and go to the error handler (also not very obvious).

Third, the presence of the await keyword will tell you if the request is actually being defered or not.

async function (req, res) {
  await rateLimit(req);    // this holds up the request
  log(body);               // this doesn't hold up the request
  send(res, 200, 'woot!'); // no `res` monkey-patching
}

Finally, you might still favor global middleware to avoid incessant repetition. You could successfully argue that if every route in your app requires logging, this pattern falls into "pre-mature optimization".

Fortunately for us, simple functions solve this without sacrificing explicitness:

import decorate from './my-middleware';
export default decorate(async (req, res) => {
  // the introduction of re-usable logic is explicit and 
  // the programmer knows to look at `my-middleware.js`
});

As you can see, micro goes beyond "syntax sugar" or "making async code look sync". It's about code that's easier to understand, debug and collaborate on.

@rauchg rauchg closed this as completed Feb 2, 2016
@tj
Copy link

tj commented Feb 2, 2016

FWIW decorators are middleware. I totally agree though, excited to see that node core is maybe going with promises finally! Hopefully then npm modules can finally converge on the same goal, async/await should have been added 4 year ago haha – we could all be using vanilla Node with promises, no frameworks, no koa, no micro, no express, no babel, utopia <3.

I'd also argue though that HTTP is a bad abstraction to begin with, I kind of like Lambda's approach there, it makes more sense for the general case, but there's always going to be weird points where you have to integrate with HTTP. You're not finding the granularity annoying? I like Lambda for data processing but for HTTP I find so many distinct functions is kind of just a pain in the ass haha

Another approach that I think would be interesting, potentially even nicer is a more functional approach like Ruby's Rack and the now super popular Redux. The "middleware" just become opaque objects all the way down with promises wrapped at will for whatever behaviour, but that wouldn't have the normalizing effect that promises does/will.

@pemrouz
Copy link

pemrouz commented May 2, 2016

+1 to being lean/explicit and just using decorators for middleware

I'd also argue though that HTTP is a bad abstraction to begin with

Couldn't agree more with this 👍, HTTP always tends towards lots of adhoc plumbing. I think what makes things like Lambda, Kafka et al conceptually nicer is that a microservice is something that simply consumes one log and produces another (as opposed to just "small service"). Then you have a fundamental way to stack component parts together. If you consider decoupling the request-response (i.e. that you could receive a response without a request, or make a request with multiple responses, or make a request that does not have a response, etc) then HTTP (responding directly to a request) just becomes one specific instance of the stream processing model. Would love to see a version of micro based on this, or otherwise, for more realtime communication (push by default rather than pull)..

@tim-phillips
Copy link

For those new to higher order functions, the decorate function could look like this:

const decorate = fn => (req, res) => {
  // do some stuff here
  fn(req, res)
}

@sibelius
Copy link

sibelius commented Nov 2, 2018

what about having React hooks concept to solve this?

@iamstarkov
Copy link
Contributor

yes, algebraic effects ftw

@thelinuxlich
Copy link

Please provide an example where React Hooks are better than async-await

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants