BFT名古屋 TECH BLOG

日々の業務で得た知識を所属するエンジニアたちがアウトプットしていきます。

【DynamoDB・Lambda】お店が営業時間内か判断するテーブルの構成とLambda関数(Node.js)の例

f:id:bftnagoya:20210826093543p:plain

はじめに

こんにちは!
BFT名古屋支店・インフラ女子(?)のやまぐちです。

とりあえず作ってみようぜ!がコンセプトの混雑状況可視化サービス「こんどる?」ですが、インフラをアジャイル開発風に構築するもんじゃないなぁという感想を想定通りに抱きました。

特にデータベースのテーブルは何度も作り直すことになり、ちゃんと設計というか、どういう使い方をするのかを明確にした上で手を付けるのが一番だと身に染みました。

今回は何度も作り直したDynamoDBのテーブルで、登録している店舗が営業時間内なのかどうかを判断するためにどんな作りにして、Lambdaからどう呼び出しているのかを記載したいと思います。

営業時間かどうか判断する

「こんどる?」は小規模な個人経営のお店をターゲットとしています。度重なる緊急事態宣言やまん延防止等重点措置で休業や営業時間の変更があることでしょう。

利用する側からすると、この状況で飲食店を応援したい気持ちはあれども

  • 今やっているかわからない
  • ホームページの情報は(今は)あてにならない
  • お店に行ったり、電話したりするのは少し面倒

こんな思いがあるかと思います。

前提条件

以下の項目はお店の滞在人数とCO2濃度を表示するWebサイトを作るにあたり、メンバーと意見を出し合ったものです。

  • 営業時間内は情報が見え、閉店している時はそれがわかってほしい
  • 個人経営のお店がターゲットなので、臨時営業・臨時休業にも対応したい
  • 居酒屋によくある、昼営業・夜営業などにも対応したい

DynamoDBのテーブル

テーブルは「通常の営業時間のテーブル」と「臨時営業・休業のテーブル」の二つを用意しました。

1. 通常の営業時間のテーブル
例として、月~金曜日に9:00-18:00を通常営業とする場合のテーブルです。weekday以外はString型です。weekdayは0を日曜日として数値で入っています。

後から判明したのですが、pythonだと1が日曜日なんですね…。 LambdaはJavascriptpythonで作っているのですが、最初に作ったのがJavascript側だったため0を日曜として入れています。

shopid weekday open_1 open_2 close_1 close_2
ABC 0 00:00 00:00 00:00 00:00
ABC 1 09:00 18:00 00:00 00:00
ABC 2 09:00 18:00 00:00 00:00
ABC 3 09:00 18:00 00:00 00:00
ABC 4 09:00 18:00 00:00 00:00
ABC 5 09:00 18:00 00:00 00:00
ABC 6 00:00 00:00 00:00 00:00

ポイントはopen_1, close_1が通常の営業時間で、open_2, close_2が二部制の営業時間がある場合に利用する、というところです。
また、データが入っていないとLambda側でエラーになってしまうので、閉店や二部制ではない場合にも00:00を入れています。

2. 臨時営業・休業時間のテーブル
臨時営業・臨時休業が予定されていなければこのテーブルは入力する必要はありません。通常営業時間のテーブルとの違いはweekdayがdateになっているくらいで、型もdateだけが数値です。

shopid date open_1 open_2 close_1 close_2
ABC 20210822 09:00 18:00 00:00 00:00
ABC 20210824 00:00 00:00 00:00 00:00

ポイントはopen_1, close_1, open_2, close_2がすべて00:00であれば「臨時休業」を表し、時間が入っていれば「臨時営業」を表すという点です。

Lambda関数の作り(ランタイム:Node.js 14.xバージョン)

以下は店舗の滞在人数とCO2濃度を表示する画面へ情報を渡すLambda関数の一部を抜粋しています。
店舗ID(shopid)はWebアクセス時のURLのクエリパラメータから取得し、そのshopidと現在の日時をキーにしてDynamoDBからデータを取得してきます。

なお、最終的にopen_flgが1だったら営業時間内、0だったら営業時間外としてAPI Gatewayへ値を渡します。

const AWS = require("aws-sdk");
const dynamo = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event, context) => {
  
  //URLのクエリパラメータから店舗IDを取得
  const shopids = event.shopid;

  //取得した値が一桁だったら0を付ける
  const toDoubleDigits = function(num) {
    num += "";
      if (num.length == 1) {
      num ="0" + num;
    }
    return num;
  };
  
  //現在の年月日をの8桁の数字にする
  const exectime = new Date();
  const yyyy = toDoubleDigits(exectime.getFullYear());
  const mm = toDoubleDigits(exectime.getMonth() +1);
  const dd = toDoubleDigits(exectime.getDate());
  const now_date = Number(String(yyyy) + String(mm) + String(dd));
  
  //現在の時間を4桁の数字にする
  const now_hour = toDoubleDigits(exectime.getHours());  
  const now_min = toDoubleDigits(exectime.getMinutes());
  const now_time = Number(String(now_hour) + String(now_min));

  //曜日を取得
  const day = exectime.getDay();
 
  //通常営業日時を取得するパラメータを設定
  const paramsopen = {
    TableName: 'NPR_CondorU_ShopOpenPolicy',
    Key: {
    shopid: shopids,
    weekday : day 
    }
  };
  
  //臨時営業・休業日時を取得するパラメータを設定
  const paramstempopen = {
    TableName: 'NPR_CondorU_ShopOpenTempPolicy',
    Key: {
    shopid: shopids,
    date : now_date
    }
  };
  
  //DynamoDBからデータを取得
  const opendata = await dynamo.get(paramsopen).promise();
  const tempopendata = await dynamo.get(paramstempopen).promise();
  
  //臨時営業・休業日時が設定されていなかったら通常営業時刻をopen_1, open_2, close_1, close_2に代入
  var open_1, open_2, close_1, close_2;
  if (tempopendata.Item === undefined ) {
    open_1 = opendata.Item.open_1;
    close_1 = opendata.Item.close_1;
    open_2 = opendata.Item.open_2;
    close_2 = opendata.Item.close_2;
  } else {
    open_1 = tempopendata.Item.open_1;
    close_1 = tempopendata.Item.close_1;
    open_2 = tempopendata.Item.open_2;
    close_2 = tempopendata.Item.close_2;
  }
  
  //開店時刻1と閉店時刻1を数値に変換
  const open_1_time = Number((open_1.split(':', 2)).join(''));
  const close_1_time = Number((close_1.split(':', 2)).join(''));

  //開店時刻2と閉店時刻2を数値に変換
  const open_2_time = Number((open_2.split(':', 2)).join(''));
  const close_2_time = Number((close_2.split(':', 2)).join(''));
  
  //現在時刻が開店時刻1~閉店時刻1 または 開店時刻2~閉店時刻2の間だったらopen_flgに1をセットする
  var open_flg = 1;
  if ((open_1_time <= now_time && now_time <= close_1_time) || (open_2_time <= now_time && now_time <= close_2_time)) {
    open_flg = 1;
  } else {
    open_flg = 0;
  }

ポイントは取得した現在時刻を一度文字列にして連結(hour + minute)した上で、数値に変換している部分です。そうすることで現在が一桁の時間でもまず四桁にしているので想定通りの数値になります。

const now_time = Number(String(now_hour) + String(now_min));

例)
現在が13:30の場合 → 1330(数値)
現在が09:01の場合 →  901(数値)
↑ 一度文字列にしないとこの場合91になってしまう

また、臨時営業・休業時間のテーブルにデータが入っていた場合に優先して開店時間・閉店時間をセットする部分もポイントです。最終的には現在時刻が、セットした開店時間と閉店時間の間にあれば営業時間内、そうでなければ営業時間外としてopen_flgにそれぞれ値をセットして返します。

open_flgが1であれば、冒頭のように滞在人数とCO2濃度が表示され、open_flgが0であれば、以下の図のように表示されます。
f:id:bftnagoya:20210826094101p:plain

終わりに

臨時営業・休業時間を店舗オーナーから連絡してもらう必要があることや手動でテーブルにデータを追加しなければいけないことは少しネックですが、このサービスは店舗オーナー側の労力は可能な限りゼロにしたいということを鑑みると致し方ない部分です。

他にこんな方法あるよ!とかこうした方が簡単だよ!というご意見、お待ちしてます。 ここまで読んでいただきありがとうございました~ ^ ^