Azure Middleware Engine is developed inspired in web framworks like express, fastify, hapi, etc. to provide an easy-to-use api to use middleware patter in Azure Functions.
But, less talk and let see some code.
For example:
// index.js
const { someFunctionHandler } = require('./handlers');
const schema = require('../schemas');
const ChainedFunction = new MiddlewareHandler()
.validate(schema)
.use(someFunctionHandler)
.use(ctx => {
Promise.resolve(1).then(() => {
ctx.log.info('Im called second');
ctx.next();
});
})
.use(ctx => {
ctx.log.info('Im called third');
ctx.done(null, { status: 200 });
})
.listen();
module.exports = ChainedFunction;
Simply run:
npm install azure-middleware
Biggest benefit of serverless arquitectures is that you can focus on implementing business logic. The problem is that when you are writing a function handler, you have to deal with some common technical concerns outside business logic, like input parsing and validation, output serialization, error handling, api calls, and more.
Very often, all this necessary code ends up polluting the pure business logic code in your handlers, making the code harder to read and to maintain.
Web frameworks, like express, fastify or hapi, has solved this problem using the middleware pattern.
This pattern allows developers to isolate these common technical concerns into "steps" that decorate the main business logic code.
Separating the business logic in smaller steps allows you to keep your code clean, readable and easy to maintain.
Having not found an option already developed, I decided to create my own middleware engine for Azure Functions.
If you are familiar with Functional programming you will notice that behavior is similar to a pipeline. You can attach function handlers to the chain and them will be executed sequentially,
You can add a middleware using use
. The order which handlers are added to the handler determines the order in which they'll be executed in the runtime.
const ChainedFunction = new MiddlewareHandler()
.use(context => {
myPromise(1, () => {
context.log.info('Im called second');
context.next();
});
})
.use(context => {
context.log.info('Im called third');
context.done(null, { status: 200 });
})
.listen();
module.exports = ChainedFunction;
Similar to use
, but you can define a predicate as first argument. If predicates resolves in a false
then function handler won't be executed.
const OptionalFunction = new MiddlewareHandler()
.use(ctx => {
ctx.log.info('I will be called');
ctx.next();
})
.useIf(
(ctx, msg) => false, // function won't be executed
ctx => {
ctx.log.info('I won\'t be called');
ctx.next();
}
)
.catch((err, ctx) => {
ctx.done(err);
})
.listen()
module.exports = OptionalFunction;
Allows you to iterate over an array of elements that will be passed to an iterator function.
const IterateFunction = new MiddlewareHandler()
.iterate([1,2,3,4], index => ctx => {
ctx.log.info(index);
ctx.next();
})
.catch((err, ctx) => {
ctx.done(err);
})
.listen()
module.exports = IterateFunction;
You can define a schema validation to your function input. We use Joi to create and validate schemas.
const SchemaFunction = new MiddlewareHandler()
.validate(JoiSchema)
.use(context => {
context.log.info('Im called only if message is valid');
context.done();
})
.catch((err, context) => {
context.log.error(err);
context.done();
})
.listen();
Error handling functions will only be executed if there an error has been thrown or returned to the context.next method, described later, at which point normal Function Handler methods will stop being executed.
const CatchedFunction = new FunctionMiddlewareHandler()
.validate(EventSchema)
.use(() => {
throw 'This is an error';
})
.catch((err, context) => {
context.log.error(err);
context.done(err);
})
.listen();
Creates a function which can be exported as an Azure Function module.
A Function Handler is the normal syntax for an Azure Function. Any existing Node.js Functions could be used in this place. Note that you have to use the context.next
method to trigger the next piece of middleware, which would require changes to any existing code that was used with func-middleware.
Same as a normal Function Handler, but the first parameter is instead a context object.
Predicates are functions that have to return a boolean value. They are used to define a condition by which a FunctionHandler is executed or not.
The context.next
method triggers the next middleware to start. If an error is passed as a parameter, it will trigger the next ErrorFunctionHandler or, if there is none, call context.done with the error passed along.
The context.done
method works the same as normal, but it's been wrapped by the library to prevent multiple calls.
This project is maintained by Emanuel Casco.
azure-middleware-engine is available under the MIT license.
Copyright (c) 2019 Emanuel Casco
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.