AWS Lambda 用ミドルウェアフレームワークの作成方法を middy を題材に検証(1)の次の記事となります。
目次
5. エラー処理の実装
6. 機能の追加
7. 基本機能の仕上げ
5. エラー処理の実装
既にuse(middleware) の onError でエラー処理モジュールを追加していますが、まだ miggy はエラー処理ミドルウェアを処理していません。ここでエラー処理を実装しましょう。
エラー処理のためにコードが増えるため、runRequest プライベート関数を作成しています。
miggy5.js
const defaultMainHandler = () => {};
const MiddlewareFramework = (mainHandler = defaultMainHandler) => {
const beforeMiddlewares = [];
const afterMiddlewares = [];
const onErrorMiddlewares = [];
const lambdaHandler = async (event = {}, context = {}) => {
const request = {
event,
context,
response: undefined,
error: undefined,
};
return runRequest(
request,
[...beforeMiddlewares],
mainHandler,
[...afterMiddlewares],
[...onErrorMiddlewares]
);
};
lambdaHandler.use = (middlewares) => {
if (!Array.isArray(middlewares)) {
middlewares = [middlewares];
}
for (const middleware of middlewares) {
const { before, after, onError } = middleware;
if (!before && !after && !onError) {
throw new Error(
'"before", "after", "onError" キーのいずれかでミドルウェア関数を登録して下さい'
);
}
if (before) lambdaHandler.before(before);
if (after) lambdaHandler.after(after);
if (onError) lambdaHandler.onError(onError);
}
return lambdaHandler;
};
lambdaHandler.before = (beforeMiddleware) => {
// FIFO
beforeMiddlewares.push(beforeMiddleware);
return lambdaHandler;
};
lambdaHandler.after = (afterMiddleware) => {
// FILO
afterMiddlewares.unshift(afterMiddleware);
return lambdaHandler;
};
lambdaHandler.onError = (onErrorMiddleware) => {
// FILO
onErrorMiddlewares.unshift(onErrorMiddleware);
return lambdaHandler;
};
return lambdaHandler;
};
const runRequest = async (
request,
beforeMiddlewares,
mainHandler,
afterMiddlewares,
onErrorMiddlewares
) => {
try {
// 前処理を実行する
await runMiddlewares(request, beforeMiddlewares);
// メイン処理に行く前にすでにresponseがある場合はスキップ
if (typeof request.response === "undefined") {
// メイン処理
request.response = await mainHandler(request.event, request.context);
// 後処理を実行する
await runMiddlewares(request, afterMiddlewares);
}
} catch (e) {
// 新しくresponseを作るのでリセットする
request.response = undefined;
request.error = e;
try {
await runMiddlewares(request, onErrorMiddlewares);
} catch (e) {
// エラー処理ミドルウェアで発生したエラー
e.originalError = request.error;
request.error = e;
throw request.error;
}
// responseが作成されていなければ、エラー処理ミドルウェアで処理されていない事を意味する
if (typeof request.response === "undefined") {
throw request.error;
}
}
return request.response;
};
const runMiddlewares = async (request, middlewares) => {
for (const nextMiddleware of middlewares) {
const res = await nextMiddleware(request);
// 既にレスポンスが決まっていれば終了する
if (typeof res !== "undefined") {
request.response = res;
return;
}
}
};
module.exports.miggy = MiddlewareFramework;
index5.js
const { miggy } = require("./miggy5.js");
const businessLogic = async (event, context) => {
console.log("ビジネスロジック実行中");
const result = {
title: "<<< ビジネスロジック >>>",
message: event.body.artist + "さんこんにちは!",
event,
context,
};
return result;
};
// 文字列をJSONに変換
const middleware_json = (request) => {
try {
request.event.body = JSON.parse(request.event.body);
} catch (error) {
throw error;
}
};
// レスポンスを編集
const middleware_after = (request) => {
throw new Error("逃げられませんよ!");
};
const middleware_error1 = async (request) => {
try {
const response = {
message:
"エラーが発生しましたが、私ことエラー処理ミドルウェア(No.1)が対処致しました!",
};
if (request.error && request.error.message) {
response.error_message = request.error.message;
}
request.response = response;
return response;
} catch (error) {
throw error;
}
};
const middleware_error2 = async (request) => {
try {
const response = {
message:
"エラーが発生しましたが、私ことエラー処理ミドルウェア(No.2)が対処致しました!",
};
if (request.error && request.error.message) {
response.error_message = request.error.message;
}
request.response = response;
return response;
} catch (error) {
throw error;
}
};
const handler = miggy(businessLogic)
.use({ before: middleware_json })
.use({ after: middleware_after })
.use({ onError: middleware_error1 })
.use({ onError: middleware_error2 });
exports.handler = handler;
//const event = { body: '{"artist":"米酢元気","song":"Lemon酢"}' };
// 失敗するeventも準備
const event = { body: { artist: "米酢元気", song: "Lemon酢" } };
const context = { isContext: true };
handler(event, context)
.then((result) => {
console.log("handlerのresult: ", result);
})
.catch((error) => {
console.log("error", error.message);
});
$ node index5.js
handlerのresult: {
message: 'エラーが発生しましたが、私ことエラー処理ミドルウェア(No.2)が対処致しました!',
error_message: 'Unexpected token o in JSON at position 1'
}
6. 機能の追加
ミドルウェアやビジネスロジック(mainHandler)で使用するLoggerなどの機能を追加する場合、通常は以下のようにしてcontextの属性として追加します。
const add_logger_to_context_middleware = {
before: (request) => {
request.context.logger = {
info: console.info,
warn: console.warn,
error: console.error,
log: console.log,
};
},
};
const handler = middy().use(add_logger_to_context_middleware);
上記の例では、addLoggerToContextMiddleware
というミドルウェアを作成し、beforeメソッド内でrequest.context.loggerにログ関数を追加しています。これにより、ハンドラ内の他の部分でrequest.context.loggerを使用してログ出力ができるようになります。
7. 基本機能の仕上げ
本家のmiddy/coreでは、プラグインやAWS Lambdaのストリーミングレスポンスに対応していますが、miggyではそれらには対応していません。
最後に、ビジネスロジックのmainHandlerを後から追加する関数を追加しましょう。
lambdaHandler.handler = (replaceMainHandler) => {
mainHandler = replaceMainHandler;
return lambdaHandler;
};
これにより、以下のような使い方ができます。
const handler = miggy()
.use({ before: middleware_json })
.use({ after: middleware_after })
.use({ onError: middleware_error1 })
.use({ onError: middleware_error2 })
.handler(businessLogic);
以上が、miggy.jsの仕上げのソースコードです。index.jsで自由に試してみてください。
miggy.js
const defaultMainHandler = () => {};
const MiddlewareFramework = (mainHandler = defaultMainHandler) => {
const beforeMiddlewares = [];
const afterMiddlewares = [];
const onErrorMiddlewares = [];
const lambdaHandler = async (event = {}, context = {}) => {
const request = {
event,
context,
response: undefined,
error: undefined,
};
return runRequest(
request,
[...beforeMiddlewares],
mainHandler,
[...afterMiddlewares],
[...onErrorMiddlewares]
);
};
lambdaHandler.use = (middlewares) => {
if (!Array.isArray(middlewares)) {
middlewares = [middlewares];
}
for (const middleware of middlewares) {
const { before, after, onError } = middleware;
if (!before && !after && !onError) {
throw new Error(
'"before", "after", "onError" キーのいずれかでミドルウェア関数を登録して下さい'
);
}
if (before) lambdaHandler.before(before);
if (after) lambdaHandler.after(after);
if (onError) lambdaHandler.onError(onError);
}
return lambdaHandler;
};
lambdaHandler.before = (beforeMiddleware) => {
// FIFO
beforeMiddlewares.push(beforeMiddleware);
return lambdaHandler;
};
lambdaHandler.after = (afterMiddleware) => {
// FILO
afterMiddlewares.unshift(afterMiddleware);
return lambdaHandler;
};
lambdaHandler.onError = (onErrorMiddleware) => {
// FILO
onErrorMiddlewares.unshift(onErrorMiddleware);
return lambdaHandler;
};
lambdaHandler.handler = (replaceMainHandler) => {
mainHandler = replaceMainHandler;
return lambdaHandler;
};
return lambdaHandler;
};
const runRequest = async (
request,
beforeMiddlewares,
mainHandler,
afterMiddlewares,
onErrorMiddlewares
) => {
try {
// 前処理を実行する
await runMiddlewares(request, beforeMiddlewares);
// メイン処理に行く前にすでにresponseがある場合はスキップ
if (typeof request.response === "undefined") {
// メイン処理
request.response = await mainHandler(request.event, request.context);
// 後処理を実行する
await runMiddlewares(request, afterMiddlewares);
}
} catch (e) {
// 新しくresponseを作るのでリセットする
request.response = undefined;
request.error = e;
try {
await runMiddlewares(request, onErrorMiddlewares);
} catch (e) {
// エラー処理ミドルウェアで発生したエラー
e.originalError = request.error;
request.error = e;
throw request.error;
}
// responseが作成されていなければ、エラー処理ミドルウェアで処理されていない事を意味する
if (typeof request.response === "undefined") {
throw request.error;
}
}
return request.response;
};
const runMiddlewares = async (request, middlewares) => {
for (const nextMiddleware of middlewares) {
const res = await nextMiddleware(request);
// 既にレスポンスが決まっていれば終了する
if (typeof res !== "undefined") {
request.response = res;
return;
}
}
};
module.exports.miggy = MiddlewareFramework;
const { miggy } = require('./miggy.js');
const sleep = (waitSeconds) => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, waitSeconds * 1000)
})
}
const obayashi_main = async (event, context) => {
context.logger.log("ズンズンズンズンズンズン...");
await sleep(2);
return "ひだり!";
};
const middleware_migi = {
after: async (request) => {
const response = "miggy!";
request.response = response;
return response;
}
};
const obayashi1 = {
before: async (request) => {
request.context.logger.log("右かな〜左かな〜");
await sleep(1.5);
const event = request.event;
if( event.message ) {
event.message = event.message += " 右かな〜左かな〜";
} else {
event.message = "右かな〜左かな〜";
}
}
};
const add_logger_to_context_middleware = {
before: (request) => {
request.context.logger = {
info: console.info,
warn: console.warn,
error: console.error,
log: console.log,
};
},
};
const handler = miggy()
handler
.use(add_logger_to_context_middleware)
.use(middleware_migi)
.use(obayashi1)
.use(obayashi1)
.handler(obayashi_main)
handler({ message: "hello" }, { iam: "context" })
.then(result => {
console.log(result);
})
.catch(error => {
console.log("error:", error);
})