メインコンテンツへスキップ
見出し画像

あえてDBを使わない!リモートワーク申請をKVで捌く設計判断

    こんにちは!Accel-Hack (アクセルハック)開発チームです。

    社内AIアシスタント「Airi」の開発プロセスをお届けする開発記ブログ。
    第18回目の今回は、第3章の締めくくりとして、柔軟な働き方を支える remote-work機能 をお届けします。

    前回までで、Airi は情報を届け(Vol.16)、チームの活動を可視化する(Vol.17)ようになりました。
    今回のテーマは、リモートワークの申請・承認・履歴表示という小さなワークフローです。

    一見ありふれた機能ですが、その裏には「あえてデータベースを使わない」という、少し尖った設計判断がありました。

    今回はその選択の理由と、それを成立させるための工夫を掘り下げます。


    1. 柔軟な働き方を、軽やかに支える

    リモートワークをするとき、「いつ・どんな業務をするか」を申請し、上長が承認する。

    多くの会社にある、ごく普通の手続きです。
    だからこそ、ここに重厚なシステムは要りません。

    求められるのは、Slack から数タップで申請でき、状況がひと目でわかる軽やかさです。

    この「軽やかさ」を技術的にどう実現するか。

    ここで私たちは、申請データの保存先として、リレーショナルデータベースである D1 ではなく、Cloudflare KV を選びました。


    2. あえて D1 ではなく KV を選んだ理由

    Vol.8 では「長く大切に持つデータは D1 へ」とお話ししました。
    では、なぜ今回はその D1 を使わないのでしょうか。

    理由は、リモートワーク申請データの性質にあります。
    この申請は、「本人ごとにまとまっていて」「対象日が過ぎれば不要になる」、いわば消えものです。
    過去の申請を横断して複雑に集計したり、他のテーブルと結合したりする用途はありません。
    こういう「本人単位で読み書きし、いずれ消えてよい」データに、がっちりしたテーブル設計は大げさです。

    それよりも、ユーザーIDをキーに、申請の一覧をまるごと出し入れできる KV のほうが、ずっと素直に扱えます。
    Vol.17 で「短命なものは KV、積み上げるものは D1」と使い分けたのと、まさに同じ判断。
    適材適所を突き詰めた結果の選択です。


    3. 申請フロー:Home からひと続きで

    実際の流れを見てみましょう。
    全体像は次のとおりです。

    画像

    Vol.12 の App Home に置かれた「新規申請フォームを開く」ボタンから、Vol.14 で紹介したモーダルが開きます。

    対象日・時間・業務内容・特記事項を入力して送信すると、対象日の妥当性などを検証したうえで KV に保存。

    同時に、本人には受付確認の DM が、承認者のチャンネルには承認ボタン付きの通知が飛びます。


    4. KVで安全に貯める――ロックと自動prune

    ここで、KV を使ううえで避けて通れない課題があります。

    KV には、データベースのような「トランザクション」がありません。

    つまり、複数の処理が同時に「申請一覧を読んで→追記して→書き戻す」を行うと、片方の更新がもう片方に上書きされて消える危険があるのです。

    そこで Airi は、Vol.16 のニュース機能でも使った 分散ロック(withKvLock) を、ここでも投入します。

    申請の追記や状態更新は、必ずロックを取ってから「読む→変更する→書く」を一人ずつ順番に行うようにしています。

    export async function updateRemoteWorkApplications(userId, update) {
      return withKvLock(getRemoteWorkApplicationsLockKey(userId), TTL, async () => {
        const apps = activeApplications(await readRemoteWorkApplications(userId)); // 読む
        const updatedApps = await update(apps); // 変更する
        await kv.put(
          getRemoteWorkApplicationsKey(userId),
          JSON.stringify(updatedApps),
        ); // 書く
      });
    }

    もう一つの工夫が、自動prune(お掃除)です。

    申請一覧を読み出すたびに、対象日が過ぎた古い申請を静かに取り除いています。

    誰かがバッチで消しに回る必要はなく、使われるたびにデータが自然と最新の状態に保たれる。

    消えものを扱う KV との相性が、ここでも活きています。


    5. 承認と、Homeでのひと目の履歴

    申請が承認フローに乗ると、承認者はチャンネルの通知に付いたボタンで「承認」または「否認」を押すだけ。

    その結果は KV の状態に書き戻されます。

    そして申請者は、わざわざ問い合わせなくても、Vol.12 の App Home を開けば自分の申請状況がひと目でわかります。
    最新5件が、状態つきで並ぶ仕組みです。

    const statusText =
      app.status === "approved"
        ? "✅ 承認"
        : app.status === "rejected"
          ? "❌ 否認"
          : "⏳ 待ち";

    申請して、Home を開けば「⏳待ち」。
    承認されれば次に開いたとき「✅承認」。

    申請から結果確認までが、Slack の中で完結する。

    これこそ、冒頭で掲げた「軽やかさ」の正体です。


    6. おわりに

    今回は、リモートワーク申請を題材に、「データの性質に合わせて、あえて DB を使わない」という設計判断と、それを支えるロック・自動prune・Home表示の工夫をお届けしました。

    重厚なDBを持ち出さずとも、KV の割り切りと少しの工夫で、実用的なワークフローは十分に作れます。

    技術は「立派なもの」を選ぶより、「ちょうどいいもの」を選ぶほうが、ずっと難しくて面白いのです。

    これで、日々の業務を支える機能たち(第3章)が出揃いました。

    次回からは新しい章に入り、Airi をさらに外の世界へ広げていきます。

    まずは 「Vol.19: エッジに Node は無い。Workers で Google サービスアカウント認証を“素手で”実装した話」 として、Google Service Account の導入と、セキュアな環境変数の管理手法をお届けします。
    お楽しみに!



    この記事は以下の体制で作成されました。

    • ドラフト作成・執筆: Airi (社内AIアシスタント)

    • アイデア出し・レビュー: CTO

    • デザイン: 事務スタッフ

    Accel-Hackでは、テクノロジーを駆使して世の中の課題を解決していく仲間を募集しています。
    私たちの取り組みに少しでも興味を持っていただけたら、ぜひ採用ページや会社サイトも覗いてみてくださいね。


    あなたへのおすすめ