Chienomi

webチャットソフトウェアのExolyteの第2章をGoogle Antigravityとともに

開発::webapp

Exolyteはwebチャットソフトウェアである。 Elixir/Phoenixを使用しており、LiveViewを採用。WebSocketによる快適な体験と切断に弱い特性を持っている。

もともとExolyteは私がElixir/Phoenixの手触りを確認するために書かれた。 実用的コードとしては最初の作品である。

このため、一旦満足に動くところまで持っていった段階でExolyteはその役目を終了していた。

しかし、Sessionが終了するという話、そして連絡先交換のときにSessionでちょっと困ったという体験から、よりよい代替を探し、ほしいポジションのものがなかったために自作を考え、イチから作り直さなくてもExolyteを改修すればそれなりに使えるものになるのではないかという結論になった。

これによりExolyteの第2章? 第2世代? が始動したわけだが、まずExolyteの改修はそれなりに複雑なコードになるし、その上条件わけなどで単調かつ同じようなコードを並べることになるといういかにもウェブアプリなコードでもあるためなかなか着手できずにいた。

そこで導入したのがGoogle Antigravity。 これが、Exolyteの大きな進展につながった。

Elixir/PhoenixはAIと相性抜群

Google AntigravityはGoogleの「コードを書いてくれるタイプのAI」である。 Antiravityアプリ上でプロジェクトを開き、チャットで対話しつつコードを書いていく。 Devinに似た感じだ。

Google Antigravityはいくつかのプロジェクトで使ってみたが、私が普段書くようなRubyコードはまずそのまま使えるようなコードは出てこない。 大抵は自分で書くほうが早い。

対してElixir/Phoenixは書き方が決まっているため、微に入り細を穿ち説明しなくてもいい感じのコードを書いてくれる。 基盤部分を作って「これを使ってくれ」と言ったり、一例を作っておいて「これと同じ感じで」と言うとより精度が高い。 基盤自体は細かく説明して作ってもらうというのもアリ。

Railsよりもマジックが少ないこともうまく作用していると思う。

重要な点として、Antigravity/Geminiは「コードをまとめることはしない」ということを意識したい。 基盤を用意した上でその基盤を使うように指示したり、あるいはこういった基盤を作った上で実装するようにといった指示をすればちゃんと共通化してくれるのだが、単に実装する機能だけを伝えると基本的に共通化はせず、コード自体を似せる方向で書く。

このあたり個人的にはユーザー指示に忠実だと感じるが、気が利かないと感じる人もいるかもしれない。

今回の開発の中でAntigravity/Geminiが明らかに正しくない実装をしたのは2つ。

ひとつは、WebPushの実装にweb_push_encyptionを使ったこと。 このライブラリはout-of-dateなライブラリで、依存関係が脆弱である。

もうひとつは、もともとデータをアトムキーで入れているところに文字列キーでいれるようにした上で、アトムキーから文字列キーにフォールバックするコードを書いたこと。 何に引っ張られたのかは不明だ。

Exolyteのポジション

このようなソフトウェアは実際にどのように使えるかだけでなく、どのように概念をレイヤーするかによって位置づけや使い勝手が大きく変わる。 Slackを個人的な連絡手段として使うことは普通に可能だが決してやりやすくはないし、Wireのチームアカウントは従来の個人アカウント同様の使い方ができるにも関わらず個人の連絡手段としては使いづらくなったと感じる。

Exolyteはそれを念頭に自分のポジションを構成している。

まず大きな特徴がWebSocketを用いるLiveViewを採用していることである。 WebSocketは切断に弱く、実質的にフロントアプリをサーバーコードに組み込むことになるためサードパーティアプリが使えない。API提供も完全に別途実装しなければならないため、難しい。 一方でフォアを維持したまま使う場合はかなり快適だ。

このため、非同期的な置き手紙方式よりは同期的なチャットという色が強い。 現代のアプリの使い方自体がより非同期な方向になっている(特にモバイル)ということもあるし、メッセージングのあり方そのものも非同期な方向になってきているから、熱狂的なチャットの使い方を想定するソフトウェアは現代ではかなり珍しい。

存在として近いのはIRC。 というか第1世代Exolyteの機能はほぼ「IRCがすべてにおいて上」みたいなもので、これが開発意欲を削ぐ部分でもあった。

第1世代ExolyteのIRCとの最大の差別化ポイントは、IRCはチャンネルは基本的にオープンなものであるのに対し、Exolyteはチャンネルに所属していることが必須でオープンなチャンネルはない。 オープンチャットではなくプライベートメッセージングのソフトウェアであるという決して小さくはない違いが存在していた。

第2世代Exolyteでは「ライトな連絡先に使えるように」という方向に強化された。 ダイレクトメッセージとユーザー招待機能の追加がその中心にある。 連絡先として見ればダイレクトメッセージは必要なものだ。

実体験として、私はLINEをやっていないので連絡先交換を求められたときに困ることがある。 基本的にはSessionアカウントを交換しているのだが、アプリインストールが必要になるために難しいケースがある。 これに対してExolyteはウェブブラウザでQRコードを読み取ってアカウントを作るという手順が取れる。 個人的にはSessionのようにアカウント概念が薄いメッセンジャーのほうが扱いやすいと思うのだけど、別の手段として機能するというわけだ。

通知が安定しないので重要な連絡をする相手には向かないが、一方でSessionやWireのように「メッセージを受け取りそこねる」という心配がないというメリットもある。 このあたりを実現するために、あえてE2EEを採用していない。 E2EEは実装こそ大変だが、運営はE2EEを採用したほうが楽になる。ログが読めないためにユーザーのやりとりに運営者が関知しなくて良くなるからだ。しかし、明確に利便性を下げることになるので不採用。

Exolyteのキャラクターのために他とは大きく違う仕様を採用している部分もある。

最大の特徴とも言えるのが、ユーザー登録が招待制、かつeメール等を必要しないがためにパスワードリセットもできないということだ。 ユーザーによるユーザー招待を有効にしていればある程度広がりは生まれるが、パスワードリセットに管理者の操作が必要なため、基本的には管理者を中心とした広がりになる。

また、ダイレクトメッセージまわりの仕様に若干クセがある。 挙動としては次の通り

  • ユーザーAが「ユーザーBを友達に追加」とした場合
    • (なければ)ユーザーAとユーザーBのダイレクトメッセージチャンネルが作られる
    • ユーザーAはダイレクトメッセージチャンネルに参加する
    • ユーザーBが当該ダイレクトメッセージチャンネルをブロックしていない場合、ユーザーBがダイレクトメッセージチャンネルに参加する

チャンネルはブロックと退出ができる。 退出していた場合は相手に参加させる方法として自分がダイレクトメッセージチャンネルに参加している状態でも重ねて友達に追加することが可能。 一方、ブロックされていても友達に追加し、自分はダイレクトメッセージに参加することはできるが、相手には無視される。

ダイレクトメッセージチャンネルのブロックはユーザーのブロックの意味も一部兼ねていて、ダイレクトメッセージチャンネルをブロックしているユーザーがchopであるチャンネルの招待も無視される。 チャンネルへの招待はchopしかできないので、結果として「ダイレクトメッセージチャンネルをブロックすると、その相手からの一切の招待を無視する」になる。

このあたりは、Exolyteがユーザーではなくチャンネルを中心としているが故の仕様だ。

以上を踏まえてExolyteの立ち位置としては

  • 管理者をルートとして広がるユーザー構成
  • ネットの人とリアルの人、両方の「連絡先交換」に対応
  • グループでの会話に良く、話題ごとに部屋を分かることもできる
  • 機密性のある内容ではないが集中的にチャットする機会がある相手には最適
  • 動作が軽くリアルタイム性が高い。webアプリにも関わらずバッテリー消費も少なめ
  • 通知は弱いがアプリインストールが不要、モバイルでもwebアプリとして使えてPWAにも対応
  • セルフホストしやすく、コミュニティごとにインスタンスを建てる方式に適する

といったところである。

Exolyteの機能

チャンネル

招待制で複数人で話すことができるチャットルーム。グループチャットに近いが、同じメンバーでも複数のチャットを作ることができる。

ダイレクトメッセージ

1対1のチャットルーム。コンタクトリストと一体の概念で、「友達への追加」は「ダイレクトメッセージチャンネルを作成」に等しい。

チャンネルブロック

チャンネルから退出すると共に、招待を無視するようにする機能。ダイレクトメッセージのチャンネルブロックは事実上ユーザーブロックとして機能する。ただし、参加しているチャンネルにダイレクトメッセージをブロックしたユーザーが含まれている場合は影響がない。

通知

メンションとチャンネルへの招待が通知される。通知はマイページで確認できるほか、WebPushとUnifiedPushに対応。

ユーザー招待

管理コンソールから作成できるほか、設定で有効にしていればユーザー招待が作られる。新規ユーザーの登録は管理者による直接追加か招待によってしかできない。招待作成時、招待リンクのQRコードが表示されるのも特徴のひとつ。

チャンネル招待

チャンネルへの参加も招待制。Slackのプライベートチャンネルなどに近い仕様。

Markdownメッセージ

メッセージはMarkdownとして解釈される。改行が反映されないのでここもちょっとクセのある部分。

メンション

1行目の先頭から継続する@user_id形式がメンションとして機能する。相手に通知がいく。ダイレクトメッセージも通知がいくのはメンションだけ。

管理コンソール

機能は乏しいが、私にしては珍しい存在。

LiveView

WebSocketを用いたクライアント。DaisyUIを採用。非常に軽快。

PWA対応

モバイルでも利用可能なwebアプリでPWAにも対応している。逆にサードパーティアプリは厳しい。

Elixir/Phoenixアプリについて

現状、私にはwebアプリを作る上でいくつかの有力な選択肢がある。 Elixir/Phoenixは最近新たに加わった武器であり、よりモダンなビジュアルと一般的な手法であることが特徴だ。

ExolyteはElixir/Phoenixだが、Ectoは使用せずデータベースはCubDBとJSONファイルを使用している。

Phoenixは数あるwebフレームワークの中でかなり明確なメリットを持ったものであると言える。 その中で最たるものがLiveViewだ。

WebSocketを用いたLiveViewはクライアントの操作をGUIアプリケーションのコールバックのように書くことができ、一般的なウェブアプリとは大きく異なる書き心地になっている。 データの所在を気にすることなくステートフルに書くことができ、Tailwind CSS/DaisyUIを用いているため見栄えもする。

Elixir/Phoenixで簡易なアプリを構築することは可能……というか容易だ。 フル機能のフレームワークとしては要求される要素も少ないほうだと言えるし、複雑性を著しく向上させるわけでもない。

ただし、やることをピンポイントに解決するには大きすぎるものではあり、過剰に感じられはする。 Phoenixを採用しても機能として使わない選択肢はだいたいのものにはあるので、「存在しているけれど使わない」という形が気にならないのであれば小さいアプリのためにPhoenixを使うことはそれほど悪い選択肢ではない。 これはPhoenixが動作としては重いフレームワークではないという点も大きい。機能的にはRailsを彷彿とさせるものがあるが、Plugの薄めのラッパーとして機能するためPhoenixそのものは軽い。EctoもActiveRecordのような重さはない。

Phoenixを使わない素Plug構成はRuby/Sinatraに近い感覚である。もうちょっと気が利いているが。

「Phoenixを使わない」はそれなりに頑固な選択であると思っていい。 というのも、Phoenixを採用したときにPhoenixの流儀に従うことを強要される部分が非常に少なく、便利だと感じる部分だけつまみ食いすることが可能なのだ。 (私のように)コントロールできない箇所があることがとても嫌で全部自分で書きたい派の人の選択だと考えていいだろう。 あとは、動作のシンプルさ次第では素Plugがいいということもあるにはあるが。

Phoenixを採用しない最大のメリットは、ライブラリアップデートの義務が発生しないことだろう。 Phoenixが依存しているライブラリがそれなりにあり、脆弱性対応のためにアップデートを余儀なくされることはある。 使っているのがPhoenixだけならば多くの場合mix deps.update --allで済むようだが。

総じてElixirで書くことがPhoenixを使う使わないどちらの場合も含めてなかなかのメリットを持っているものだが、ことAIを使う前提だとElixir/Phoenixが際立って良い。

そもそもElixirは(動くかどうかとは別に)書き方が厳密に決まっているOTW言語である。 だから「コードの書き方」には迷いがあまりないのだが、アプリの作りには幅がある。Phoenixを使う場合、アプリの作りがパターン化されるので、一例を作ればAIがそれを延長していける性質が非常に強い。

この場合、Elixirが関数型言語で紛れが発生しにくいことがAIに対する強力なミスの抑止にもなっている。 今回はGoogle Antigravityを使って拡張したが、AIとの相性の良さはどのAIにも通じる部分だと思う。

Elixir/Phoenixの人気は高まってきてはいるが、カウンターの域を出ない。 しかしAIとの相性の良さが注目されれば爆発的に人気となる可能性もあると思う。

Elixirはところどころで関数型言語としての本性を表してくるが、比較的書きやすい言語ではある。 Phoenixに関しても割と素直でマジックが少ないので、Railsと比べれば圧倒的に学習コストが安い。 私は、AIでの開発を見越してElixir/Phoenixを使えるようにしておくというのも全然ありだと思う。