iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
💭

Google Apps Script System Group Design: Silver Moss Design

に公開

Introduction

This guideline defines "Silver Moss Design" (Gingoke-Sekkei), a design philosophy for constructing GAS projects to ensure scalability and maintainability.

Background of the Naming

The name "Silver Moss" was chosen to emphasize the following points:

  • As a peripheral system, it exists in a symbiotic relationship with existing SaaS.
  • In silver moss, each life form is small and independent, yet they form colonies. Similarly, a group of these projects clusters together to build a resilient system.

Please refer to the following article for information regarding peripheral systems.
https://zenn.dev/nag8/articles/f40561baa85a0e

Basic Architecture — Project Decomposition and Library Utilization

1 Project = 1 Responsibility Principle

Each GAS project should be specialized for a single SaaS integration or one clear purpose, strictly adhering to the Single Responsibility Principle.

  • Specific Examples:

    • get-smarthr-data: Responsible for fetching data from SmartHR.
    • get-akerun-data: Responsible for fetching data from Akerun.
  • Why decompose? (Main Benefits):

    • Limiting Scope of Impact: Prevents a specification change or failure in one SaaS from affecting other unrelated projects.
    • Improved Security: Manage authentication information (API keys, etc.) via script properties on a per-project basis. Physically blocks access to unnecessary information.
    • Management Efficiency: Keeping projects small makes management tasks—such as deployment with clasp, trigger settings, and log reviews—faster and easier.

Shared Libraries for General Processing

Processes repeatedly used across multiple projects should be consolidated into a shared library, such as base_gas_library, to promote the DRY (Don't Repeat Yourself) principle.

  • Examples of processes to include in a library:
    • Slack notification functionality
    • Standard spreadsheet operations (getting the last row, clearing ranges, etc.)
    • Date and string formatting processes

By deploying as a library, each project can call stable shared functions by specifying a version.

Inter-project collaboration via libraries

Each project imports and collaborates with other projects or shared libraries as needed. This approach allows for the construction of a flexible and robust system.

  • Examples of collaboration:

    1. get-smarthr-data calls the Slack notification function in base_gas_library to report processing results.
    2. If a new requirement arises to "reconcile SmartHR and Akerun data," the logic is added by borrowing space within get-akerun-data. In this case, get-smarthr-data is called as a library to provide member information retrieved from SmartHR.
  • Advantages of the overall architecture:

    • Code Reusability: Centralized management of common processes dramatically improves development efficiency.
    • Separation of Concerns: Each project can focus on its own responsibility, and complex processes can be called simply via libraries.
    • High Maintainability: Fixes to common functions are completed just by updating the library. Specification changes in a SaaS are handled entirely by modifying the relevant project.

Internal Layered Architecture

Individual projects adopt a three-layer architecture for the purpose of separation of concerns.

Entry Point Layer (main.js)

This is the starting point of script execution and is responsible for orchestrating the business logic.

This layer focuses on defining the business flow—the "what to do." It controls the series of steps: calling functions in the data access layer to retrieve data, converting it into objects in the model layer, applying necessary processing, and then passing it back to the data access layer for persistence. Concrete implementation details of "how to handle the data" are completely delegated to the other layers.

Data Access Layer (e.g., sheet.js, kintone.js)

Responsible for all input/output of data with specific data sources such as Google Sheets, kintone, and external APIs.

  • Roles:

    • sheet.js: Encapsulates all processes related to reading from and writing to sheets using SpreadsheetApp.
    • akerun.js: Handles all communication with external APIs (request generation, error handling, etc.) using UrlFetchApp.
  • Benefits:
    By providing this layer, business logic does not need to be aware of details such as API URLs or cell addresses. Even if there are changes to API specifications or the configuration of the referenced sheet, the necessary modifications are limited to this data access layer, improving maintainability.

Model Layer (class/*)

Defines the core data handled by the system (e.g., employees, account information) as classes.

  • Role:
    Represents the application's primary data entities (e.g., Member, Account) as classes within the class/ directory.

  • Why use classes?:

    • Readability and Intuitive Operation: Data structures become obvious from the code, such as member.name, allowing for intuitive manipulation.
    • Logic Encapsulation: Logic related to the data (e.g., member.isActive()) can be centralized within the class, increasing code cohesion.
    • Consistency and Safety in Object Creation: By providing static methods like createFromSheetRow() or createFromJson(), objects can be created from various data sources in a safe and consistent manner.

Conclusion

Programming with GAS often carries the image of something done on the side of one's main duties. However, if the code is necessary for business operations, I believe it should have a design philosophy and that we should accumulate best practices.
As a side note, Gemini, which assisted in the creation of this guide, suggested regarding the inter-project collaboration section that "when combining multiple projects, instead of borrowing space in a 'get-' type project, a new project named 'check-***' should be created." While that argument certainly has merit, I have described the 'borrowing' approach here to prioritize the reuse of existing classes.

Discussion