💭

GASのシステム群設計:銀苔設計

に公開

はじめに

本ガイドラインは、拡張性と保守性を担保するための、GASプロジェクトを構築するための設計思想「銀苔設計」を定義します。

命名の背景

以下点を強調するために銀苔と命名しました。

  • 周辺システムであるため、既存SaaSと共生関係にあること。
  • ギンゴケは一つ一つの生命は小さく独立しており、それが群を形成している。こうしたプロジェクト群も寄り集まることで、しなやかなシステムを構築する。

周辺システムについては以下記事を参照してください。
https://zenn.dev/nag8/articles/f40561baa85a0e

基本アーキテクチャ — プロジェクトの分割とライブラリ活用

1プロジェクト = 1責務の原則

各GASプロジェクトは、単一のSaaS連携や一つの明確な目的に特化させ、単一責任の原則を徹底します。

  • 具体例:

    • get-smarthr-data: SmartHRからのデータ取得に責務を負います。
    • get-akerun-data: Akerunからのデータ取得に責務を負います。
  • なぜ分割するのか?(主なメリット):

    • 影響範囲の限定化: あるSaaSの仕様変更や障害が、他の無関係なプロジェクトへ波及することを防ぎます。
    • セキュリティの向上: 認証情報(APIキー等)をプロジェクト単位のスクリプトプロパティで管理。不要な情報へのアクセスを物理的に遮断します。
    • 管理の効率化: プロジェクトが小さく保たれることで、claspでのデプロイ、トリガー設定、ログ確認といった管理タスクが迅速かつ容易になります。

汎用処理の共通ライブラリ化

複数のプロジェクトで繰り返し利用される処理は、base_gas_libraryのような共通ライブラリに集約し、DRYの原則を推進します。

  • ライブラリに含める処理の例:
    • Slackへの通知機能
    • スプレッドシートの定型操作(最終行取得、範囲クリアなど)
    • 日付や文字列のフォーマット処理

ライブラリとしてデプロイすることで、各プロジェクトはバージョンを指定して安定した共通機能を呼び出すことが可能になります。

ライブラリによるプロジェクト間連携

各プロジェクトは、必要に応じて他のプロジェクトや共通ライブラリをインポートして連携します。このアプローチにより、柔軟かつ堅牢なシステムを構築します。

  • 連携の例:

    1. get-smarthr-dataは、処理結果を通知するためにbase_gas_libraryのSlack通知機能を呼び出します。
    2. 「SmartHRとAkerunのデータを突き合わせる」という新要件が発生した場合、get-akerun-dataに間借りして処理を追加します。その場合、get-smarthr-dataはライブラリとして呼び出され、SmartHRから取得したメンバー情報を提供します。
  • アーキテクチャ全体の利点:

    • コードの再利用性: 共通処理を一元管理することで、開発効率が飛躍的に向上します。
    • 関心の分離: 各プロジェクトは自身の責務に集中でき、複雑な処理はライブラリ経由でシンプルに呼び出せます。
    • 高い保守性: 共通機能の修正はライブラリの更新のみで完了します。SaaSの仕様変更も、該当プロジェクトの修正だけで対応が完結します。

プロジェクト内部のレイヤーアーキテクチャ

個々のプロジェクト内部は、関心の分離を目的とした3層のレイヤーアーキテクチャを採用します。

エントリーポイント層 (main.js)

スクリプトの実行起点であり、ビジネスロジックのオーケストレーションを担当します。

この層は「何をするか」というビジネスフローの定義に集中します。データアクセス層の関数を呼び出してデータを取得し、モデル層のオブジェクトに変換、必要な処理を施したのち、再びデータアクセス層に渡して永続化する、といった一連の流れを制御します。「どのようにデータを扱うか」という具体的な実装は、他の層に完全に委譲します。

データアクセス層 (e.g., sheet.js, kintone.js)

Google Sheets, kintone, 外部APIといった特定のデータソースとのデータの入出力に全責任を負います。

  • 役割:

    • sheet.js: SpreadsheetAppを使い、シートの読み書きに関する全ての処理をカプセル化します。
    • akerun.js: UrlFetchAppを使い、外部APIとの通信(リクエスト生成、エラーハンドリング等)をすべて担当します。
  • メリット:
    この層を設けることで、ビジネスロジックはAPIのURLやセル番地といった詳細を意識する必要がなくなります。API仕様の変更や、参照するシートの構成変更が発生した場合も、修正箇所をこのデータアクセス層内に限定でき、メンテナンス性が向上します。

モデル層 (class/*)

システムが扱う中心的なデータ(例: 従業員、アカウント情報)をクラスとして定義します。

  • 役割:
    class/ディレクトリ内で、アプリケーションの主要なデータエンティティ(例: Member, Account)をクラスとして表現します。

  • なぜクラスを使うのか?:

    • 可読性と直感的な操作: member.nameのように、データ構造がコードから一目瞭然となり、直感的に操作できます。
    • ロジックのカプセル化: データに関連するロジック(例: member.isActive())をクラス内に集約でき、コードの凝集度が高まります。
    • 生成処理の一貫性と安全性: createFromSheetRow()createFromJson()のような静的メソッドを用意することで、様々なデータソースから安全かつ一貫した方法でオブジェクトを生成できます。

終わりに

GASというだけで、業務の片手間に行うようなプログラミングというイメージが付きがちではあります。しかし業務上必要なコードであるならば設計思想もあったほうがよく、プラクティスを蓄積するべきかなと思い書きました。
なお、作成を支援したGeminiはライブラリによるプロジェクト間連携部分において「複数のプロジェクトをまとめる場合、get系のプロジェクトに間借りするのではなくcheck-***というプロジェクトを新設するべきだと提案しました。確かに主張には妥当性があるものの、作成したclassを流用できるという点を採用するために間借り案を記載しています。

Discussion