LoginSignup
0
1

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

Last updated at Posted at 2023-06-26

AWS Lambda 用ミドルウェアフレームワークの作成方法を middy を題材に検証(1)の次の記事となります。

目次

5. エラー処理の実装
6. 機能の追加
7. 基本機能の仕上げ

5. エラー処理の実装

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

miggy5.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

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

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;
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);
  })
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1