AWS Lambda 用ミドルウェアフレームワークの作成方法を middy を題材に検証（２）

[AWS Lambda 用ミドルウェアフレームワークの作成方法を middy を題材に検証（１）](https://qiita.com/Katsumi1972/items/0cfe293543fb82ccefb7)の次の記事となります。
## 目次

[5.　エラー処理の実装](#5-エラー処理の実装)
[6.　機能の追加](#6-機能の追加)
[7.　基本機能の仕上げ](#７-基本機能の仕上げ)

## 5. エラー処理の実装

既にuse(middleware) の onError でエラー処理モジュールを追加していますが、まだ miggy はエラー処理ミドルウェアを処理していません。ここでエラー処理を実装しましょう。
エラー処理のためにコードが増えるため、runRequest プライベート関数を作成しています。

miggy5.js

```js: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

```js: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);
  });
```

```sh
$ node index5.js
handlerのresult:  {
  message: 'エラーが発生しましたが、私ことエラー処理ミドルウェア(No.2)が対処致しました！',
  error_message: 'Unexpected token o in JSON at position 1'
}
```

## 6. 機能の追加

ミドルウェアやビジネスロジック（mainHandler）で使用するLoggerなどの機能を追加する場合、通常は以下のようにしてcontextの属性として追加します。

```js
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を使用してログ出力ができるようになります。

## ７. 基本機能の仕上げ

本家のmiddy/coreでは、プラグインやAWS Lambdaのストリーミングレスポンスに対応していますが、miggyではそれらには対応していません。

最後に、ビジネスロジックのmainHandlerを後から追加する関数を追加しましょう。

```js
lambdaHandler.handler = (replaceMainHandler) => {
  mainHandler = replaceMainHandler;
  return lambdaHandler;
};
```

これにより、以下のような使い方ができます。

```js
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

```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;
```

```js:index.js
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);
  })
```