🌊

カカリアツールボックス〜社内ツールをサクッと作るための工夫〜

2023/07/28に公開

はじめに

Happy Elementsカカリアスタジオでは社内ツールをサクッと開発するための仕組みを構築して3年以上運用しています。
本記事では「業務をシステム化したいけど開発が億劫だ」、「社内の各所に似たようなツールが存在するのをなんとかしたい」とお考えの方に向けて、どういった仕組みに仕上がっているのかを共有いたします。

いつも他の方の記事を参考にさせていただいてばかりで執筆の方は得意ではないのですが、本記事がどなたかの参考になりましたら幸いです。

必要性

カカリアスタジオではスマートフォン向けのゲームを自社開発・運用しているのですが、ゲームの高度化や市場環境の高速な変化によって業務が増えていく中で、(ゲーム作りに集中するために)雑務を処理するシステムがほしいと感じていました。
一方で、エンジニアのリソースはゲーム作りに集中したいため、業務改善用の社内ツールを開発する時間はあまり確保できません。

そのような状況をうけて、私がゲームプロデューサー兼エンジニアをしながら、マネジメント業務の一部をツール化したものをもとに、全社向けの社内ツールをサクッと開発するための仕組み「カカリアツールボックス」 として運用しています。

カカリアスタジオツールボックスのコンセプト

自分の業務をシステム化できると感じても実際にツールを開発するのは億劫なものです。
この億劫さをできるだけなくすことをツールボックスのコンセプトにしています。

より具体的には、ツールボックスは多数のインプットサービスとアウトプットサービスをつなぎ合わせるハブのようなシステムです。

構成

  • Infra
    • AWS(ECS Fargate)
  • Server
    • Ruby on Rails
  • Frontend
    • あえて用意せず(後述)

アクセスできる外部サービス群

  • Slack
  • Box
  • Notion
  • Google Calender
  • Google Drive
  • Google SpreadSheet
  • Google BigQuery
  • Pinterest
  • Adjust
  • DMM Games
  • AndApp
  • Rakumo
  • SmartHR
  • HRBrain
  • MicroCMS
    などなど

ご覧のようにかなり沢山あります。

これらのサービスにアクセスできるクラスがlibに入っているRails環境をひとつ用意して、各種ツールをバッチにして定時実行、もしくはSlackBotとして実行しています。

実装したツールの例

ツールは多数あるのですが、だいたい「◯◯からデータを取得してきて、整形して、◯◯と◯◯に結果を出力する」といったバッチが多いです。

  • Rakumoのワークフローから稟議情報を取得して、申請内容をチェックした結果をSpreadSheetに出力してGoogleDriveに保存、保存したらSlackチャンネルにURLを通知する。
  • AdjustやゲームプラットフォームからKPIデータを取得して、分析用のデータ形式に変形してGoogleBigQueryに出力。また、その際にExcelにも出力してSlackで担当者に送付する。
  • 従業員マスターが修正されたら職種や所属部署に応じて、KibelaとSlackのユーザーグループを更新する。退職者が出た場合は関係各所にSlackで通知する。
  • GoogleCalenderで勤怠の予定が登録されている場合は、所属チームのSlackチャンネルに前日・当日に通知する。
  • ipaファイルやapkファイルが送られてきたら、S3に保存してインストール用のQRコードをSlackに送付する。

などなど、こういったツールが多数をツールボックス内に存在します。

ツールボックスを3年運用してみてどうだったか

ツールごとにリポジトリを分けてしまうと、ツールごとにライブラリから実装せねばなりませんしクレデンシャルの管理やCI/CDの構築などいちいち工数がかかるのですが、このRails環境があるおかげでツールのコア機能のみに開発を集中できます。
この環境とRubyの生産性の高さも相まって、ツールの開発は非常に高速に行えていますし、強力な足回りをしっかり用意したことで、メリットをしっかり享受できているように思います。(ほとんどのツールの開発は、1日もかからず数時間で完了します。)

内部の細かな工夫

ログ出力

私はツールボックスのメンテナーではありますが他の主業務を抱えているため、運用が簡単になるように以下の仕組みを使ってログをSlackに送るようにしてあります。
https://zenn.dev/happy_elements/articles/8dbbc522b3e7ff

この仕組みがあればサーバーに入らなくてもSlackで実行状況が確認できますし、情報が多いときはテキストファイルの形式でスレッドに投下するなども可能です。

最小限のコストでSlackBotを作るための仕組み

Slack上で流れるメッセージはツールボックスのAPIにイベント通知されるようにしており、ツールボックス側で以下のようにして、複数のSlackBotクラスで(正規表現などで)ヒットしたメッセージのみを処理するようにしています。

SlackBot::Base.subclasses.each do |slack_bot_class|
  if slack_bot_class.should_execute?(text, event_message, params)
    slack_bot_class.execute!(text, event_message, params)
  end
end

これにより、SlackBotBaseクラスを拡張してshould_execute?とexecute!のメソッドだけサクッと実装すれば好きなSlackBotを作れるようにしています。
※ 「Slackメッセージが正規表現にヒットしたら◯◯する」などここでも多彩なサービス群にアクセスできるので、Botの守備範囲も非常に広いです。

あえてフロントエンドを作らない

ツール開発以外の開発工数を最小化するために、管理画面ふくめ一切のフロントエンドを用意していません。

マスターデータの保存場所としてGoogle SpreaeSheetを使うことで実現しています。
※ RDBも使っていますが、ユーザーが更新する設定系のテーブルはSpreadSheetにしてしまっています。

Railsならフロントエンドはサクッと作れますし、SpreadSheetをDBにして壊れたりしないの?みたいなご意見があるかもしれませんが、実際はシートを触るメンバーは限られていますし弊社では問題になったことはありません。

またGoogle アンケートフォームを使ってSpreadSheetに登録・更新させる手法も使えるため、なかなか快適です。

実装紹介(モデル基底クラス)

SpreadSheetを使うモデルクラスは、ActiveRecord::Baseクラスでなく以下のクラスを利用するようにしています。

class SpreadSheetRecord
  def self.service
    @service ||= begin
      service = Google::Apis::SheetsV4::SheetsService.new
      authorizer = make_authorizer
      authorizer.fetch_access_token!
      service.authorization = authorizer
      service
    end
  end

    # シートのデータ範囲
  def ranges
    "A2:ZZ"
  end

    # シートの中身を取得
  def values
    res = self.class.service.batch_get_spreadsheet_values(sheet_id, ranges: ranges)
    res.value_ranges.first.values
  end

  def rows
    values.map { |arr| self.class::Row.new(arr) }
  end

    # レコードを扱いやすくするためのクラス(Hashのままだと使いにくいので)
  class Row
    class_attribute :cols
    def self.define_cols(cols)
      self.cols = cols
      cols.map { |col| self.attr_accessor(col) }
    end

    def initialize(arr)
      self.cols.each_with_index do |col, i|
        self.send("#{col}=", arr[i])
      end
    end

    def to_a
      self.cols.map { |col| self.send(col) }
    end
  end

    # 配列の内容をシートに反映
  def update_all!(rows)
    request = Google::Apis::SheetsV4::BatchUpdateValuesRequest.new(
      value_input_option: "USER_ENTERED",
      insert_data_option: "OVERWRITE",
      data: [{
        range: ranges,
        majorDimension: "ROWS",
        values: rows.map(&:to_a),
      }],
    )
    self.class.service.clear_values(sheet_id, ranges)
    self.class.service.batch_update_values(sheet_id, request)
  end
end

実装紹介(モデルクラス)

このクラスを利用するモデルクラスは以下のようになります。
以下の簡単なクラスを書くだけで済むようにしています。

class CalendarNotifySetting < SpreadSheetRecord
  def sheet_id
    # SpreadSheetのIDを設定
    "xxxxxxxxxxxxxxxxxxxxx"
  end

  class Row < SpreadSheetRecord::Row
    # シート内のA列からE列までの列名を設定
    self.define_cols [
      :title,
      :calendar_id,
      :channel,
      :color,
    ]
  end
end

実装紹介(利用箇所)

データ取得と利用は以下のように行います。

sheet = CalendarNotifySetting.new

# sheet.rowsでシート内の設定データが取得可能です。
sheet.rows.each do |setting_row|
  if event = GoogleCalender.client.list_kintai_events(setting.calendar_id, target_date)
    # カレンダーに勤怠の登録があればSlackに通知
    Slack.client.chat_postMessage({
      channel: setting_row.channel,
      attachments: [
        {
          title: event.summary,
          title_link: event.html_link,
          color: setting_row.color,
          text: event.description,
        }
      ].to_json,
    })
  end
end

おわりに

最後までみていただきありがとうございます。
以上のように、カカリアスタジオではツールボックスを使って、エンジアリソースを節約しながら働きやすい環境を整備しています。コンセプトの工夫、実装の工夫が少しでも伝わっていれば幸いです。
未熟な点も多々あるかとは思いますが、引き続き皆様の記事を参考に改善を重ねて、楽しくゲーム作りに集中できる環境を整えていきたいと思います。

最後に広告させてください。

自分の手がけたゲームを、何万人もの人にあそんでもらえる体験

Happy Elements株式会社は「エリオスライジングヒーローズ」、女性向け大人気ゲーム「あんさんぶるスターズ!!」、「メルクストーリア」などGoogle PlayやApp Store上でいくつもの人気スマートフォンゲームを運営しています。
現在新規タイトルを複数制作中です。

「面白いゲームを作りたい」とお考えの方、「スキルの高い仲間と一緒に働きたい」と感じている方からのご応募をお待ちしています。「自分の手がけたゲームを、何万人もの人に遊んでもらえる」そんな体験を、ぜひ私たちと一緒にしてみませんか?
https://recruitment.happyelements.co.jp/

Happy Elements

Discussion