共有タイマーでクライアントとサーバの役割分担を学ぶ

## 概要

この記事は、既存のアプリケーションを改修して、複数人で使えるようにした際の記録です。

主に、クライアント側とサーバ側のデータの流れをどのようにしたら良いか、というようなシステム構成部分の思考をまとめた内容になっています。

## 何を作ったか？


今回作った成果物は、ブラウザ上で動くカウントダウンタイマーです。
これは、アジェンダとそれにかかる時間を入力することで、円グラフを模したタイマーを表示します。
このタイマーは、色分けされたアジェンダごとに使える時間を表示し、「全体でどの程度時間がたって今何のアジェンダが進行しているのか？」を可視化することができます。
![IMG_4431.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/58447ab7-6d5b-d843-b4f6-f2eb66249fef.jpeg)





### 機能一覧

- タイマーをスタート出来る
- タイマーをリセット出来る
- 予定されているアジェンダと時間の進捗を確認出来る
- アジェンダを読み取ることが出来る
    - 読み取り方法
        1. テキストからの入力出来る
        2. 写真からの読み取ることが出来る
- アジェンダを更新することが出来る
    - タイマー起動中にアジェンダを更新すると時間が追加出来る
- アジェンダを削除することが出来る
- 完了したアジェンダをチェックすると残り時間が変更する

## 現状は？

このカウントダウンタイマーはスマホのブラウザからの利用を想定しており、PCの余計なタブを開かずに、スマホとPCを見比べることで進捗を把握することができるようになっています。

さて、実際のシステム内部の仕組みですが、、
これは、ほぼクライアント側のみの実装になっていて、時間の経過やアジェンダは、各PC側で保持しています。つまり、イベントの進捗状況をを参加者間で共有することが出来ない状態。

アプリケーション自体はNode-REDを用いて実装しており、現状のシステムは次のような流れで動作しています。ここでのサーバ側の役割は、Formから受け取ったアジェンダと時間を整形して、HTMLに渡すくらいの仕事しかしていません。

1. Formにアジェンダを含めてPOST
2. **Node-REDでFormを受け取って、アジェンダ部分をHTMLにレンダリング**
3. JavascriptでHTMLの要素を取得して、chart.jsを使って円グラフ形式のタイマーを表示

【仕事の分担】
![スクリーンショット 2021-03-04 9.58.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/c39d5a41-f71f-c9cf-de1d-afcae18875af.png)





## どうなっていたいのか？

イベント(や会議)の進捗状況が分かるようになるといっても、それはこのアプリの使用者のみで、時間を意識することができるのはせいぜいファシリテーターくらい。参加者全員が時間を意識することが出来れば、よりイベントや会議を有意義な時間と考えています。

### 経過時間を共有出来ることのメリット

- オンライン会議において参加者全員が時間を意識することが出来る。時間が押していたら、他の参加者も予定通りに進めようという気になる。
- オンライン会議は場所の制約がないため予定時間をオーバーしてしまいがちになる。
- 発表者が変わるような会議の際には、全体の会議時間の、自分の持ち時間を意識することが出来る
- 全員が時間を見えることで、ブレストなどでは無言の時間を減らそうという気になり議論が活発化する。

つまり今回のカウントダウンタイマーを次のような状態にしたいです。

1. **他の参加者にも見えるようにして、会議の進捗を全員で共有できる**
2. **会議室ごとにタイマーを動作できる（チャットルームのような）**

## どうやって実現するか？

データをクライアント毎に持っていると、参加者同士が同じものを見れないということなので、時間を共有するためには、サーバ側にもう少し仕事させなきゃいけないようです。

「Webページのルーティング」と「POSTされたデータの整形」くらいしかしてなかったサーバ側に、「データの保持」という役割も持たせてあげれば、やりたいことが実現できそう。
つまり、情報自体はサーバ側に持たせて、クライアント側ではその情報を表示させるだけの状態にする。

ここでサーバに持たせる情報を、先ほどの画像から「時間」や「アジェンダ」とすると、、
パッと思いついた実現方法が二つ。

1. クライアントがサーバにデータを取りに行く
2. サーバからクライアントにデータをプッシュ

### 1.クライアントがサーバにデータを取りに行く

![スクリーンショット 2021-03-04 10.01.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/b7e11a9f-9e4f-2255-2382-a0bab31eca87.png)


時間をサーバ側に持たせて、クライアントはそれを都度取りに行く方法
具体的には以下のような流れで進める
1. 会議スタート！
2. 「会議のトータル時間」をサーバに送る
3. サーバ側で「経過時間」のカウント開始
4. サーバは「会議のトータル時間」から「経過時間」を引いて「残り時間」を計算
5. 1秒毎にクライアントはサーバにリクエストし、「残り時間」を画面に表示

### 2.サーバからクライアントにデータをプッシュ

これは先ほどの案の逆で、時間をサーバ側に持たせて、サーバからそれをクライアントに投げる方法。サーバ主導でメッセージを送信する為に、双方向通信を実装する必要がある。
1. 会議スタート！
2. 「会議のトータル時間」をサーバに送る
3. サーバ側で「経過時間」のカウント開始
4. サーバは「会議のトータル時間」から「経過時間」を引いて「残り時間」を計算
5. サーバはクライアント(参加者)を識別して時間をプッシュ。

#### 懸念
二つの案を検討して、まず感じたのは、「タイマーという特徴がネック？」ということ。
時間というデータをサーバ上に保持しておくと、それをクライアントに伝えるために1秒毎に通信しなければいけなくなる。しかも、1秒毎に通信する割に情報自体は「経過時間」というそこまで重みのないもの。（やっていることはNTPっぽい？）初学者の自分でも、この構成はスマートじゃないということは、なんとなく理解出来る。また、案２は通信するクライアントを識別する必要もあるので、案１よりももっと考えることが多くなりそう。


### 【第3の案】時間の差分を使う

上の懸念点から学んだのは、「クライアント側に保持しているデータをごっそりサーバ側に移すとなんとなくよろしくない状態になる、、」ということ。
なので、もう一度思案して、クライアントとサーバ双方の仕事の分担を見直してみました。
![スクリーンショット 2021-03-04 9.58.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/c3c87e7f-7680-2bee-5c0e-85cf3ba7e580.png)

時間はもうクライアント側で計測をお願いしちゃいます。
その分サーバには別の仕事を割り振りました。この仕事はデータの保持になり、データは「会議室(ルーム )」に紐づいているアジェンダとそれがスタートされた時間になります。
そして同時に、このタイマーの利用者を**『ルームの作成者(親)』**と**『ルームの参加者(子)』**という立場に分け、立場ごとに出来ることの違いを持たせる仕組みも作りました。
「タイマーの利用者ごとの出来ることの違い」と「クライアントとサーバの役割分担」によって出来た流れが以下のようになります。
1. **親がルームを作成する**
2. **親がタイマーをスタートする**
3. **子がルームに入る**
4. **子はスタートされた時間と今の時間の差分だけタイマーを進める**

親と子の時間の差分でタイマーを進めることで、クライアントとサーバの通信を一回だけにおさめることが可能になり、尚且つ今回やりたかったことが実現出来ています。

![スクリーンショット 2021-03-04 12.05.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/bbfb0745-92dc-8713-3293-604d2fbc31ba.png)
![スクリーンショット 2021-03-04 12.09.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/9fbd4049-3ef5-692a-7fee-03b321e74ddc.png)



以下詳細な流れ
**ルーム作成者(親)**
1. Formにアジェンダを含めてPOST
2. Node-REDでFormを受け取って、アジェンダ部分をHTMLにレンダリング
3. roomIdを発行してアジェンダと紐付けてflow変数に格納しておく
4. JavascriptでHTMLの要素を取得して、chart.jsを使って円グラフ形式のタイマーを表示

```json
{
	"roomId":"od1isga90vo",
	"startTime":"",
	"agendaList":["12分","aaa"]
}
```

ここでルーム作成者が会議のスタートボタンを押すと、startTimeに現在の時刻を格納する

```json
{
	"roomId":"od1isga90vo",
	"startTime":"2021-02-25T05:58:30.186Z",
	"agendaList":["12分","aaa"]
}
```

その後、top画面に進行中のルームとしてroomIdが映し出される。

**参加者(子)**
1. ルーム作成者からrooIdを教えてもらい、ルーム選択画面に表示されているroomIdをクリック
2. 選択したroomIdを元に、紐づいているstartTimeとagendaListを参照
3. agendaListを元に、アジェンダ部分をHTMLにレンダリング
4. JavascriptでHTMLの要素を取得して、chart.jsを使って円グラフ形式のタイマーを表示
5. 参加者(子)がタイマーを表示させた現在時刻とstartTimeの差分を計算して、経過した時間を進める



## まとめ
第3の案の実装時点で、「参加者間が時間を共有出来る」「会議室ごとにタイマーを起動できる」「クライアントとサーバの通信は出来るだけ少なめに」を3点を達成出来ていますが、これだけだと参加者が何かしら操作をした時に変更が全員に反映されません。例えば、「タイマーのリセットボタンを押したとき」や「アジェンダを変更した時」。
これを解消するためには、第2の案で出た双方向通信を使って、参加者のクライアント側の変更をサーバを介して全員に通知する必要がありそうなので、今後検討していきたいです。

今回はクライアントサーバシステムの役割分担を色々と検討してみましたが、タイマーというアプリというのもあって、構成を考える良い勉強になったと思います。システムの設計をあまり考えずに、今回のカウントダウンタイマーを作成したのもあって、少し強引さがあったと思います。事前設計の重要性も痛感したので、次回に開発に生かしていきたいと思います。