質問

Is it a bad design pattern / anti-pattern to create a whole bunch of specific middleware to replace functions in-route. So instead of doing this

router.post('/myRoute', (req, res, next) => {
    checkParams(req.body).then(paramsResult => {
        if(paramsResult.status === 'failed') return res.send(paramsResult);
        return checkUserEmail(req.body.email);
    }).then(emailResult => {
        if(emailResult.status === 'failed') return res.send(emailResult);
        return checkPasswordLength(req.body.password);
    }).then(passResult => {
        if(passResult.status === 'failed') return res.send(passResult);
        return ...
    }).then(...).catch(err => {
        return res.send({ status:'failed', message:'An error occurred.' });
    });
});

I do this

router.post('/myRoute', [checkParams, checkUserEmail, checkPasswordLength, ...], (req, res, next) => {
   // I'd set req.user = {...} in the last middleware before this route.
   createUser(req.user).then(createResult => {
        if(createResult.status === 'failed') return res.send(createResult);
        return res.send({ status:'success', message:'Your account has been created.' });
   }).catch(err => {
        return res.send({ status:'failed', message:'An error occurred.' });
   });
});

From the middleware I call in myRoute I'll be able to return res.send(errMsgObj) if I determine that the operation should fail (e.g. if the user's password is too short), and in so doing, the request won't proceed to the next middleware function (thus "failing gracefully"). Similarly, if it meets my criteria, I can call next() at the end of the middleware function.

What I'd like to know:

  1. Would it be considered bad practice to use the multiple-middleware method over the normal promise-chain-inside-the-route way?
  2. If not, is it better than or on par with the promise-chain-inside-the-route way?

TL;DR: How this question came to me
Seriously, feel free to skip the following paragraphs, I only add it for context and any answers shouldn't have to depend on it.

I've been using Node and Express to develop my personal projects for a while now, but because I come from a Groovy/Grails background, Javascript's asynchronous nature still trips me up quite a bit. So much, I think, that it'd be fair to say that I'm probably still a beginner.

I've been working on a new project, trying to write "the perfect" User register route. Because I've gotten the hang of JS promises and promise chains a while back, implementing my route as a series of promises seemed the natural way to go. The problem I found with this approach, is that my route requires 13 steps (most of which are broken up into separate functions containing their own up to 3-long promise chains), that all have to execute successfully, one after another, for the route to return a success response. That's a lot of promises to chain together, but I set out to do it anyway. However, when I was nearly done with this monstrosity, I realised that there's no way (that I've found) to gracefully exit a promise chain without throwing an error, so if execution fails in a predictable way (e.g. the email address a user wants to register with is already taken), I'd have to throw an error and handle it in the chain's catch block. But the catch block is where I prefer to handle unpredictable errors (e.g. the database server went unexpectedly offline), by logging the request params and error info, and return a generic "An error has occurred" message to the user. So obviously I don't want it clogging up my logs with "your password has to be at least 276 characters long" messages.

I then set out to learn async/await and I love it. I feel like I'm right at home, like I'm using Groovy/Grails, because I can now assign the return value of a function to a variable and if a function returns a known-error error message ("your password's already been taken"), I can immediately return res.send() said message to the client and it won't continue executing the code for all the following steps in the route. The next problem I ran into was that I couldn't figure out how to implement a post-route middleware that writes the request and response to the database (for debugging purposes) by without the following steps in my route attempting execution, after next()ing to the middleware.

I don't know how I came up with it, but I finally had the idea of breaking all the steps involved in the route into small chunks and assigning them to their own middleware functions that would all run before the route and if they fail, they create an Event database record, containing the info that I want stored and return res.send() with an appropriate message to the client. I realise that I could also do this with a promise chain in-route, and that multiple middleware won't solve this problem, but it's just a different way of doing it that I at first thought could solve the problem.

Finally, I realise that the code example I gave in above also doesn't really demonstrate my problem that well. I gave an oversimplified example, simply to demonstrate my question, not the actual real world problem I'm facing. If you'd like me to elaborate the code example to fully understand my real world problem, let me know and I'll make an update.

役に立ちましたか?

解決

I don't see any problems in doing things this way as it keeps the code pretty readable. However, unless you are the only person that will ever edit this code, I might recommend documenting your technique.

In short:

Both approaches have drawbacks and advantages, and there's nothing inherently better or worse in either solution.

Just consider how often you run across this approach by other developers. If it's only a handful of folks that know you can do that, then adding a comment above the route to explain that everything in the [checkParams, checkUserEmail, checkPasswordLength, ...] are functions and they all have to pass for the route to get to the happy path will help others become familiar with your technique. Remember, document the pattern, not the minutia.

Just consider coming back to the code in 3-6 months:

  • What's needed to jog your memory
  • What might be needed to document the approach (even a link to a blog article will be sufficient)

I have found that not everyone appreciates indirect code like this. Software development is a team sport, so its best if your team is using the same playbook. If your team resists this construct, it's because their thinking about debugging or something you might not have considered. Just don't be dogmatic about it.

ライセンス: CC-BY-SA帰属
所属していません softwareengineering.stackexchange
scroll top