近年、Fluxのように双方向の更新を極力避けて、単方向の更新に還元させようという流れがあります。ただ、どうしても双方向の更新が避けられない場合はあるでしょう。Goole Docsのように、複数の人が同じドキュメントを編集する場合。画面上の複数の入力コンポーネントが相互に同期しているような場合です。
特定の言語に依存しない話ですが、Javascriptで使うことが多いとは思われるため、タグにJavascriptを入れています。
1. 一番単純な実装
まず、一番シンプルに、Aが更新されたらBに反映させる、Bが更新されたらAに反映させる、という実装を考えます。
#2. 外部から更新を受け入れた場合は、更新の報告をしない
この際、単純に実装するとA→B→A→Bという無限ループになってしまいます。これを解決するためには以下のような方法が考えられます。
それは、「外部から更新を受け入れた場合は、更新の報告をしない」ことです。
#3. サーバ側でupdateIdを管理し、最後のコミットを反映させる
ただ、これだと、AとBが同時に更新した場合、最終的に、どちらのサーバ反映が早かったかに関わらず、AではBが反映され、BではAが反映されることになります。つまり、AとBの現在状態がずれてしまいます。
これに対応するためには、サーバ側で、更新の受付を管理し、最新の状態を確定させる必要があります。
これは、socket.ioを使う場合、フレームワーク側で更新順序の管理するので、サーバ側で特別な対応は不要です。
この場合、クライアント側では、自分が行った更新がサーバを通して通知されてきた時に無視せずに、受け入れるような実装が必要です。自分が行った更新がサーバを通して通知されてきた時に無視するような実装にすると、更新がAとBで同時に行われて、Aの方が先にサーバに到達した時、本来、現在状態はBの更新でなければいけないのに、Bの側ではそれを受け取れず、現在状態がずれてしまいます。
4. 更新中の通知の無視
しかし、これにはまた問題があります。
自分が行った更新がサーバを通して通知されてきた時に受け入れるとなると、
そのラウンドトリップの間に行われた変更が一時的にであれ破棄されることになってしまいます。
これは、ユーザから見ると、行った変更が一時的に巻き戻されるように見えることになり、
受け入れられるものではありません。
これを解決するための方法は、
「自分が更新を行ってそれが通知されるまでの間、更新の受け入れを先延ばしする」
ことです。
5. 更新中の自己更新の無視
しかし、これにもまた問題があります。それは、外部からの更新の受け入れもストップしてしまうため、AとBがずっと更新し続けると、永遠にAとBの状態がずれたままになることです。
これを解決するには、上のルールを以下のように修正すれば良いことになります。
「現在状態が、自己の更新の結果によるものである場合、自分が行った更新の通知は無視する」
つまり、自分が更新をしている間でも、他の人の更新は受け入れます。そして、いったん他の人の更新を受け入れれば、その後に到着した自分が行った更新は受け入れます。
これで初めて、現在状態のズレがなくなり完全な同期が実現することになります。
6. まとめ
こうやって見ると、双方向更新を実現するため考慮しないといけないことが想像以上に多いことが分かると思います。
ここから分かる重要なことは、双方向更新が必要になったとき、一番最初に考えないといけないことは「双方向更新をしないで良いように設計できないか検討すること」です。そして、Firestoreのような既存サービスの利用を検討すること。それがどうしても無理だった場合、この記事のことを思い出してもらえると幸いです。