見出し画像

Chatworkでお知らせを定期送信する

クラス設計についてなかなか前進しないので、
まずはかにが手を動かす過程を記録してみることにした。

今回作るのは、「ガチもく部」の活動のひとつである週イチのもくもく会の開催をお知らせするChatwork定期送信ツール。言語はGoogle Apps Script。

本記事は、コードの説明が目的ではなく、クラス設計を学び途中のかにが実際にツールを作ってみることで設計のヒントを探していく過程のメモです。

今回のGOAL

チャットワークの「ガチもく部」ルームに「もくもく会を開催するよ!」のお知らせメッセージを毎週火曜日のお昼に自動送信するツールの作成

スタート前にもし、使う関連サービスについての知識が足りてなかったら、先にリファレンス読んでミニマムの接続テストして仕様の確認をしておく。今回は仕様確認については省略。

また、業務フローの整理とユースケース整理も省略。要件定義が終わっている状態からスタート。開発環境の準備についても省略。

ではさっそくいってみよ!

ファイルの準備

1ファイル1クラス、1ファイル1実行関数のルールで。
ここで用意するのは、始める前に想定されるものだけでいい。ざっくりでいい。登場するモノ・コトにラベルをつける感じ。

メイン関数のファイル準備
・sendMokumokuEventMsg.js
クラスのファイル準備
・ChatworkMsgService.js
・ChatworkRoomDomain.js
・MsgDomain.js

メイン関数の動き

まずはメイン関数の動きについて、コメントだけ書く。

sendMokumokuEventMsg.js

'use strict';
/**
* チャットワークにもくもく会開催のお知らせを送信する
*/
function sendMokumokuEventMsg() {

    //送信するメッセージを取得
     
    //送信先のルームIDを取得
     
    //チャットワークにメッセージ送信
 
}

次にコメントに合わせて中身を書く。

sendMokumokuEventMsg.js

'use strict';
/**
* チャットワークにもくもく会開催のお知らせを送信する
*/
function sendMokumokuEventMsg() {

    //送信するメッセージを取得
    const msg = new MsgDomain().createMsg();
     
    //送信先のルームIDを取得
    const roomName = 'ガチもく部';
    const roomId = new ChatworkRoomDomain(roomName).getRoomId();
    
    //チャットワークにメッセージ送信
    new ChatworkMsgService().send(roomId, msg);
 
}

あくまで仮でOK。各クラスを定義してみて変更が必要になったら、後から変更してもいいと思って気楽にさささっと書く。もしこの時点でクラスの枠組みに修正を加えたほうがよさそうなことがわかったら、戻ってファイルの整理をやり直す。

お絵描きしながら考えるといいと思う。

画像1

我ながらヒドイ(´⊙ω⊙`)
ちなみに上記の絵でsendとchatworkが分かれているのはControllerクラスを作ろうか迷ったから。結局今回はServiceクラスにまとめた。

図の描画とエディタとを行き来しながら構成を固める。

文章で言うと、テーマとキーワードが決まった状態。

各クラスの枠作り

メイン関数で呼び出しているメソッドとコンストラクタ引数をクラス側で定義するための箱を作る。

まずはドキュメンテーションコメントだけ書いて中身は書かない。

戻り値も大事なので書いておく。型がわかれば中身は空でいい。

ChatworkMsgService.js

'use strict';
/**
* チャットワークのメッセージに関するサービスクラス
*/
class ChatworkMsgService {

     /**
      * チャットワークのメッセージに関するコンストラクタ
      * @constructor
      */
     constructor() {
     
     }
     
     
     /**
      * チャットワークにメッセージを送信する
      * @param {string} roomId
      * @param {string} msg
      * @returns {string} APIリクエストのレスポンス
      */
     send(roomId, msg) {

           const response = '';
           return response;
     
     }
     
}

ChatworkRoomDomain.js

'use strict';
/**
* チャットワークルームに関するドメインクラス
*/
class ChatworkRoomDomain {

     /**
      * チャットワークルームに関するコンストラクタ
      * @constructor
      * @param {string} roomName
      */
     constructor(roomName) {
     
           /**@type {string} チャットワークのルーム名*/
           this.roomName = roomName;
       
     }
     
    
     /**
      * ルームIDを取得
      * @returns {string} ルームID
      */
     getRoomId() {

           const roomId = '';
           return roomId;
     
     }
 
}

MsgDomain.js

'use strict';
/**
* メッセージ本文に関するドメインクラス
*/
class MsgDomain {

     /**
      * メッセージ本文に関するコンストラクタ
      * @constructor
      */
     constructor() {
     
     }
     
    
     /**
      * メッセージ本文を成形する
      * @returns {string} メッセージ本文
      */
     createMsg() {
    
           const msg = ``;
           return msg;
     
     }
 
}

枠組みが整った。
文章でいうと、見出しの構成が決まった状態。

いよいよ処理の中身を書く

1つ1つメソッドの処理の中身を書いていく。

このとき、必要に応じてプライベートメソッドも追加し、1つのメソッドが大きくならないように注意する。

だいたい書けたら、テスト実行用の関数を用意してメソッド単位で実行してテストしながらクラスを完成させていく。

ChatworkMsgService.js

'use strict';
/**
* チャットワークのメッセージに関するサービスクラス
*/
class ChatworkMsgService {

     /**
      * チャットワークのメッセージに関するコンストラクタ
      * @constructor
      */
     constructor() {

           /**@type {string} */
           this.version = 'v2';
           /**@type {string} */
           this.baseUrl = `https://api.chatwork.com/${this.version}`;
           /**@type {string} */
           this.token = PROP.getProperty('CW_TOKEN');
           /**@type {string} */
           this.roomId = PROP.getProperty('CW_ROOM_ID');
     
     }
     
     
     /**
      * チャットワークにメッセージを送信する
      * NOTE: https://developer.chatwork.com/ja/endpoint_rooms.html#POST-rooms-room_id-messages
      * @param {string} msg
      * @returns {string} APIリクエストのレスポンス
      */
     send(msg) {

           const url = `${this.baseUrl}/rooms/${this.roomId}/messages`;
           const param = {
                 'method': 'post',
                 'headers': this._getHeader(),
                 'payload': {
                       'body': msg,
                       'self_unread': '1'
                 },
           };
           const response = UrlFetchApp.fetch(url, param).getContentText();
           return response;
     
     }


     /**
      * ヘッダーを取得
      * @private
      * @returns {Object} リクエストヘッダー
      */
     _getHeader() {
    
           const header = {
                 'X-ChatWorkToken': this.token,
                 'Content-Type': 'application/x-www-form-urlencoded',
           };
           return header;
    
     }
     
}


/**
* ChatworkMsgServiceクラスのテスト関数
*/
function test_ChatworkMsgService() {

     /**テスト用設定項目ここから */
     const msg = 'テストです';
     /**テスト用設定項目ここまで */
     
     console.log(new ChatworkMsgService().send(msg));

}

※PROPはglobal変数(Propertiesオブジェクト)です。

ここまで書いて、
ChatworkRoomDomainクラスはいらないと判断して削除。
(ルームIDはスクリプトプロパティに持たせることにしました。)

ということでメイン関数も修正。

sendMokumokuEventMsg.js

'use strict';
/**
* チャットワークにもくもく会開催のお知らせを送信する
*/
function sendMokumokuEventMsg() {

    //送信するメッセージを取得
    const msg = new MsgDomain().createMsg();
    
    //チャットワークにメッセージ送信
    new ChatworkMsgService().send(msg);
 
}

ルームIDの取得をChatworkMsgServiceクラスの中に内包したので、メイン関数からルームIDの取得とsendメソッド引数のroomIdを削除。

ここまではサービスの仕様通りなので中身については考えることはほぼないはず。ただし、リファレンスを読み解くのに悩むことはあるあるあるある。

クラス単位で結果がOKになったら次のクラス。どんどんいこ。

MsgDomain.js

'use strict';
/**
* メッセージ本文に関するドメインクラス
*/
class MsgDomain {

     /**
      * メッセージ本文を成形する
      * @returns {string} メッセージ本文
      */
     static createMsg() {
           const today = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'M月d日');
        
           const msg = '[toall]\n今夜もくもく会を開催しますー!';

           return msg;
    
     }

}


/**
* MsgDomainクラスのテスト関数
*/
function test_MsgDomain() {

     console.log(MsgDomain.createMsg());

}

MsgDomainクラスはインスタンスなしの静的メソッドに変更。変更に合わせてメイン関数も修正。

sendMokumokuEventMsg.js

'use strict';
/**
* チャットワークにもくもく会開催のお知らせを送信する
*/
function sendMokumokuEventMsg() {

    //送信するメッセージを取得
    const msg = MsgDomain.createMsg();
    
    //チャットワークにメッセージ送信
    const result = new ChatworkMsgService().send(msg);
    console.log(result);
 
}

createMsgメソッドの呼び出しを静的に変更したのと、結果のログ出力を追加。

こうしてすべてのクラスのメソッド単位のテストがすべてOKになったら、メイン関数で一連の流れをテストする。

すべてのテストOKだったらメイン関数のトリガー設定をして、トリガー情報をドキュメンテーションコメントに追加。

* ちょっと脱線 *
ところで時間主導型のインストーラブルトリガーに週ベースのタイマーと月ベースのタイマーが追加されたの超うれしい。ナイスすぎるアップデート。

sendMokumokuEventMsg.js

'use strict';
/**
* チャットワークにもくもく会開催のお知らせを送信する
* トリガー:時間主導型 / 週ベースのタイマー / 毎週火曜日 / 午後12時~1時
*/
function sendMokumokuEventMsg() {

    //送信するメッセージを取得
    const msg = MsgDomain.createMsg();
    
    //チャットワークにメッセージ送信
    const result = new ChatworkMsgService().send(msg);
    console.log(result);
 
}

以上で完成(V)o¥o(V)

結果:クラス便利

メイン関数
・sendMokumokuEventMsg
クラス
・ChatworkMsgService:チャットワークメッセージに関するサービスクラス
・MsgDomain.js:メッセージに関するドメインクラス

今回やってみて思ったのは、やっぱり小さなツールであっても、ある程度の期間使い続ける予定のものであればちゃんとクラスでカプセル化して書いておいたほうがわかりやすいんじゃないかなということ。

たとえば、
チャットワークAPIに仕様変更があったら、ChatworkMsgServiceだけ見ればいい。送るメッセージを変えたいときは、MsgDomainだけ見ればいい。送るツールを変えたかったら、新しいクラスを作ってメイン関数でmsgを受け渡す先を変えてやればいい。

便利(/・ω・)/

反省とネクストアクション

クラス設計の考察のためにツール作りながら記録してはみたけど、ここから考察しようにも、小さすぎてムツカシイ・・(´・ω・`)

次はリポジトリクラスとコントローラークラスも登場するもう少し大きな題材でやってみようかなと思うけど、規模を大きくしたときのコードの説明ってどうしたらいいんだろ。とりあえず何も考えずに全部載せるか。

いや待てよ。
次は今回つくったものに機能追加してみるのも面白いかもしれない。
うん、そうしよう(/・ω・)/

おしまい。


==追記==

下記記事から何も進歩してないな。。


この記事が気に入ったらサポートをしてみませんか?