-
Notifications
You must be signed in to change notification settings - Fork 32
What's the advantage of afterware vs. middleware? #7
Comments
I should have a better answer for you in a couple of hours. The current reasons are simplicity of the middleware and aftwerware in their implementations and clarity of the order of their application. Your suggestion is certainly worth investigating. Are there any use cases you can think of that might need this sort of access to request and response? While we are thinking about the middleware/afterware structure, another plan to look at is the specification for apollo-link, especially "Composing Links into a chain". Links are designed to modify GraphQL control flow with an HttpLink(calls ApolloFetch) or other transport link at the end of the chain. apollo-fetch deals with everything fetch and http specific. I'm curious to hear your thoughts on apollo-link and the interplay between link and fetch. |
I guess I just think adding the concept of "afterware" just means that people have to deal with two concepts and the library has to handle two stacks/loops/etc. When everything is implemented as middleware it keeps it simple, and it allows for middleware that modifies the request and response. So in your auth example: function makeAuthMiddleware(auth) {
return (req, opts, next) => {
opts.headers = opts.headers || {};
opts.headers.authorization = auth.createAuthorizationToken();
const res = next(req, opts);
if (401 === res.status) {
auth.logout();
}
return res;
};
} Using a pipeline is pretty straightforward: function runMiddleware(middleware, req = {}, opts = {}) {
const final = (req, opts) => {
// FIXME:
return `build body from ${JSON.stringify(req)} and do fetch() with ${JSON.stringify(opts)}`;
};
if (0 === middleware.length) {
return final(req, opts);
}
if (1 === middleware.length) {
return middleware[0](req, opts, final);
}
const pipeline = middleware.reduce((next, current) => {
return (req, opts) => current(req, opts, next);
}, final);
return pipeline(req, opts);
} Demo: const req = { foo: 'bar' };
const opts = { headers: { 'x-bar': 'baz'}};
const middleware = [
(req, opts, next) => {
opts.headers = opts.headers || {};
opts.headers.authorization = 'auth header';
const res = next(req, opts);
return `Check auth, then ${res}`;
},
(req, opts, next) => next(req, opts).replace(/"/g, ''),
];
console.log(runMiddleware(middleware, req, opts));
// Check auth, then build body from {foo:bar} and do fetch() with {headers:{x-bar:baz,authorization:auth header}} I think the apollo-link spec looks interesting in a lot of ways, but it seems to me that apollo-fetch should be as simple as possible. |
There are lots of cases where middleware would want to modify both the request and response. Another common one would be logging: function log(req, opts, next) => {
console.log('Requesting:', req, opts);
const res = next(req, opts);
console.log('Result:', res);
return res;
}; |
@inxilpro Thank you for the suggestions! Right now, we are going to stick with the current interface for middleware and afterware, so that we get backwards compatibility with Apollo Client's middleware and afterware. Are you interested in contributing the functionality for middleware's The proposed order of execution would be: apolloFetch.use(m1).use(m2).useAfter(a1).useAfter(a2)
m1 > m2 > fetch > a1 > a2 > m2 > m1 Let me know what you think! This could be a good migration path toward deprecating afterware in It also sound like you had some ideas on how to apply the middleware/afterware more efficiently. If you are interested in that more, this code handles the ware application. |
Another use case is for JWT-refresh. The order of operation is basically:
The implementations I've seen (outside of Apollo) require keeping some state:
Just my 2 cents. :) (I eventually got here via this discussion.) |
Middleware could easily function before/after a request:
Is there a reason to have explicit middle/after stacks that I'm not seeing?
The text was updated successfully, but these errors were encountered: