お知らせ

現在サイトのリニューアル作業中のため、表示が崩れているページが存在することがあります。

最近思っているNext.jsを使った画面設計に関する考えを箇条書きで雑に殴り書きしていく。この記事は考えの垂れ流しなので深い説明はしない。AppRouterではなく、PageRouterの考え。

  • TypeScriptで実装し、型が騙せるような実装は極力避け、コードによる戻り値の型指定は不具合の原因になることがあるため、可能な限り型推論に任せる
  • SOLIDな設計を意識することで疎結合でテストしやすい設計になる
  • Clean Archtectureを意識することでSOLIDのSを意識しやすくなる
    • 画面として考える場合、実装レイヤーとしてはAPIを呼ぶ以外何もしないAdapter、ビジネスロジックやイベントハンドリングの実処理などを行うController、画面要素を配置しただけのView、画面状態を保持するState、それらをつなぎ合わせるUsecaseが、Usecaseを置くだけのPage(Next.jsのpageコンポーネントに埋め込むコンポーネント)、SSGやSSRをする場合のServer Side Controllerがあるとよいと考えている。大まかには下図のような感じで考えていて、過去の実務でもこれに相当するものを作ったことがある。
    • ただこれはModelに相当するものがなく、ビジネスロジックの共通化に課題が出てくるのと、ControllerがFatになりすぎると考えており、そこが課題になると考えている。
  • テストが容易なコードは必然的に疎結合になる
  • 疎結合にする場合、命名を抽象的にしておくと処理の入れ替えが容易になる(命名が具象、つまり実装の詳細に依存しないため)
  • 疎結合にするとパーツが増えるので認知負荷が上がる
  • 疎結合でかつ、命名が抽象化されている場合、仕様を知らない人にとっては実際の処理内容を推測しづらくなる
    • つまりこれは属人性が増えると考える
  • 例外についてはErrorクラスを継承し、カスタム例外を作成して、用途に応じたハンドリングができるようにする
    • 原則として処理を止める場合にのみ用いるべきで、続行する場合には使わない
    • 例外は原則としてスローして、カスタムエラーは特定の階層でフィルタしてハンドリング、全てすり抜けてきたものはルート処理でキャッチしてハンドリングすることで、取りこぼしをゼロにする。例外の握り潰しは原則行わない
      • 基本的にすべてロギングする
    • 処理を継続するものについては例外とせず、ワーニング用の処理フローを作成し、それに則って行う(例えば入力バリデーションはワーニング)

スマホでWebを見てるときにキーボードがUIにかぶって操作しづらくなることがあるので、いくつかのサイトでどうなっているか調べてみた。

去年の9月にAndroid Edgeで調べた内容なので、今とは事情が異なるケースもある。Android Chromeでは起きなかったケースもあったので、Edge特有の挙動と思われる。

Twitter

ログイン画面

キーボードが入力欄やログインボタンにかぶる。

ミュート設定

入力欄が上にあるためかぶらない。

投稿画面

入力欄が上にあるためかぶらない。

YouTube

コメント画面

キーボードがボタンにかぶる。

通報モーダル

キーボードがボタンにかぶる。

GitHub

Issueのコメント画面

入力欄が丸ごとキーボードにかぶる。

一休

検索モーダル

宿泊予算の入力が丸っとかぶる。

じゃらん

検索画面

キーボードと被らないようにするためか入力UIをモーダルにして画面トップに出すように工夫されている。これなら大抵の端末やブラウザで対応できそうなので、よくできていると思う。

Amazon.co.jp

レビュー画面

入力状態になると若干のラグの後に画面下に余白ができ、入力状態が外れると同様のラグの後、画面下の余白が消えるという挙動をする。

割と凝ったJSで何かしらの計算を行い、かなり頑張って調整しているようだった。タイムラグがあるのはイベント発火もあるだろうが、キーボードの検出や画面サイズに応じた余白計算に時間がかかっているのもあるのだろう。

ここまで凝った実装をしているのは他のサイトでは見られず、Amazon.co.jp特有に見えた。なお、Amazon.comのほうは見ていない。

あとがき

この調査時点では、じゃらんとAmazon.co.jpを除き入力欄を画面の上部に配置するなどレイアウトで調整しているサイトが比較的多く、どうしてもボタンなどが隠れる傾向があるように思った。

じゃらんは強制的に画面上部に入力欄を出すようにし、Amazon.co.jpは気合でキーボードが隠れないように調整していて、腐心の跡が見られた。

なお、今回動画を作成するにあたり一部をぼかす必要があったため、やり方を調べ実践したのでAviUtlで動画の一部にモザイクをかけ、動かす方法という記事を作成し、その過程を残している。

本動画の作成過程では上手くモザイクをかけられなかったが、上記の記事を作っているときに上手く行くようになったので、本記事の動画はモザイクではなく、ぼかしとなっている。

意外とわかりやすいサイトが少なかったので収穫はあまりないがこんな感じ。消したらログアウトされるCookieとして調べた。

サービス名 保持期間 セッションキー
GitHub 15日 user_session
Cookpad 30日 access_token_global_v2
pixiv 30日 PHPSESSID
fantia 31日 _session_id
ドリパス 2ヶ月 remember_user_token
last.fm 1年 sessionid

Amazon.co.jpやヨドバシもしばらくアクセスしていないとログアウト食らった記憶があるので、何かしらのリミットはついてそうだが、さっと調べる程度の範疇ではわからなかった。

しかしGitHubはやたらログアウト食らうとと思ったらたった15日とは…。

ドリパスはDPSSIDがなければremember_user_tokenからセッションを生成し、DPSSIDremember_user_tokenの両方がなければログアウト状態になるようだった。DPSSIDはExpiresがSessionとなっていたため、remember_user_tokenを削除してもブラウザを落とすまではログインを継続できる。

2024/05/16 20:03 開発::設計開発::Web

この記事では下図のシステム構成を前提に話を進めるものとする。この時、BFFの部分のアーキテクチャをREST APIにするか、GraphQLにするかというのが今回の話。

REST API

長所

  • Web標準であるため特別な技術が必要ない
    • ファイルアップロードのような特殊な通信プロセスも何も考えずに実装できる
    • エラーもエラーコードを返せばいい
  • 学習コストが低い
  • 設計が単純
    • エンドポイントに何かを要求すれば、それに対する応答が帰ってくるという点で非常にシンプル
    • CRUDはGET/POST/PUT/DELETEで分かれており、これも単純
  • 依存するライブラリがない
    • 破壊的変更を受ける可能性がない
    • renovateによるアップデート地獄とも無縁
  • BFFとフロントエンドで型情報の共有が容易
    • BFFをTypeScriptで実装していれば、そのまま型情報が流用できるし、他の言語で実装していてもOpenAPIを作り、そこから型情報を得ることが可能だ
  • curlからリクエストを投げやすい
    • 基本的にペイロードが単純であるため

短所

  • 複数のデータを取得する際に複数回APIを呼ぶ必要があり、オーバヘッドが大きい
    • 今回のケースでは呼び出し先のAPIはBFFによって集約されているため、適切にエンドポイントを設計すればこの問題は回避できる
  • オーバーフェッチとアンダーフェッチ
    • データを取得しすぎたり、取得しきれないといった問題が発生することがある
    • 例えば/api/v1/userからユーザー名だけを取得したい場合に、ユーザーIDや年齢といった余計なデータが付随してしまうことがあったり、このエンドポイントからそのユーザーのログイン履歴を取得することができなければそれはアンダーフェッチになる
    • 今回のケースでは呼び出し先のAPIはBFFによって集約されているため、適切にエンドポイントを設計すればこの問題は回避できる
  • エンドポイントが増えすぎると何が何かわからなくなる
    • GraphQLでもQueryが増えすぎれば同様の問題だと思うのでREST API固有の問題だとは思わない
  • レガシーな技術
    • レガシーというのはそれだけで悪である

GraphQL

長所

  • モダンであり、流行っている
    • モダンであり、流行っているということはREST APIより良い選択肢であると言える可能性がある
  • クエリを利用した柔軟な要求が可能
    • REST APIと異なり特定のエンドポイントに縛られることがない
    • またクエリを読むことで、フロントエンド側の実装のみで、どのような要求なのかを知ることができる
  • フロントエンド側で取得したいデータを制御できる
  • エコシステムが充実している
    • キャッシュやエラーハンドリングは勿論のこと、Reactなど特定のフレームワークと統合するための仕組みが存在する
  • デバッグ用のブラウザ拡張機能が存在する
    • デバッグ用のブラウザ拡張機能が存在し、これがあるとデバッグがしやすい

短所

  • Web標準ではない
    • ファイルアップロードのような特殊な通信プロセスは追加のライブラリなどが必要になるケースがある
  • 学習コストが高い
    • 非常に充実したエコシステムが存在するということは反面学ぶことも多いということになる
      • Web標準ではないが通信にはHTTPを使っているため、Web標準を知らなくていいわけではない
      • GraphQL標準のようなものは存在し、それと別にライブラリの構成についても学ぶ必要がある
    • REST APIのノリで利用するのであれば使う意味がない
  • 型システムとの相性が悪い
    • クエリを利用し柔軟にデータを要求できるということはデータ構造が不定であるため、静的型付け言語との相性が悪い
    • 多くの場合、辞書型を使って処理することになると思うが、このままでは不具合を引き起こしやすく、型の正規化を行う場合余計な処理プロセスが入ってしまう
    • また型の正規化をするための関数を個別に作っているとやっていることがREST APIと大差なくなる
  • 設計が難しい
    • スキーマ設計が難しいと言われており、HowTo記事が多くある。AWSにさえある
    • 何でもかんでも取得できる魔法のリゾルバみたいなのを作ってしまうとフロントエンド側で取得できてはならないデータまで取得できてしまうためよく考える必要がある
      • 例えばクエリに応じた内容をDBから引っこ抜いてくる実装だったりすると、他のユーザーの情報を取得するなどができてしまうので結局BFF側でもある程度の作りこみが必要になる
      • 他にもN+1問題が起きるとパフォーマンスが悪くなるどころか、最悪サーバーが落ちることすらあり得るだろう
  • 実装が難しい
    • 学習コストが高い部分と同じだが、全員がGraphQLとそのライブラリの理念を理解するのはあまり現実的ではなく、実装が難しくなる要因になると思う
  • 開発ツールの対応状況があまりよくない
    • VSCodeを開発に使おうとしたときにGraphQLの対応状況があまりよくなく、入力補完やLintなどをいい感じにしてくれる存在がいない
  • ライブラリの保守作業が必要
    • ライブラリはアップデートされ、時として破壊的変更も起きるものなので、それらの対応が必要
    • renovate地獄の一翼を担う存在になる
  • curlからリクエストを投げづらい
    • クエリ文が複雑になればなるほど投げづらくなる
    • 下手したらこれだけでオーバーフェッチとかどうでもよくなるレベルだと思う