Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Server-Driven UIで frontendの複雑性に立ち向かう

Avatar for nacal nacal
November 05, 2025

Server-Driven UIで frontendの複雑性に立ち向かう

Avatar for nacal

nacal

November 05, 2025
Tweet

More Decks by nacal

Other Decks in Technology

Transcript

  1. 2 ⾃⼰紹介 ロリポップ‧ムームードメイン事業部 for Gamersチーム 2022年 新卒⼊社 中⽥ 輝 Nakata

    Hikaru / nacal • Webアプリケーションエンジニア • フロントエンドがすきです • X : @_nacal
  2. 6 https://github.com/csmets/Server-Driven-UI type NewsCard { image: Image author: String postDate:

    String heading: String body: [String] } type Image { url: String description: String } Client-Driven UI
  3. 8 https://github.com/csmets/Server-Driven-UI Server-Driven UI type NewsCard { elements: [NewsCardElement] }

    type Heading implements Typography { text: String } type Paragraph implements Typography { text: String } ... union NewsCardElement = Button | Image | NewsCardMeta | Heading | Paragraphs
  4. 9 - frontendの複雑性 - 多種多様なゲームタイトル/バージョンによって⽣じるビジネスロジックの複雑化 - frontendはなるべく体験の創造に集中責務に集中したい - ゲームのアップデート対応 -

    元々の実装ではゲーム単位でそれぞれPRC/Componentsが存在しており、DRYでない - ゲームの最新リリースによる設定項⽬の変更のための作業が⼤変 - schema変更、設定ファイル編集、frontend Component修正... 解決したかった課題 最新アップデートは最速で遊びたいよ!
  5. 10 - ゲームの設定項⽬のList/formにServer-Driven UIを導⼊ - 画⾯全体には適⽤しない - 更新頻度/ビジネスロジックによる分岐の複雑性 が⾼い⼀部のView -

    UIを構成する要素(attrやElement)をAPIのResponseとする - backend側でビジネスロジックを完結させる - 技術要件:gRPC + Protocol Buffers 導⼊したもの
  6. protobuf schema 11 message GetGameSettingsRequest { string server_id = 1;

    } message GetGameSettingsResponse { // 設定グループ(Section) message Group { string group_id = 1; // "gameplay" string title = 2; // "ゲームプレイ設定" repeated Field fields = 3; } repeated Group groups = 1; } サーバーIDからゲーム/バージョン情報の取得 セクションレベルでの配列に格納
  7. protobuf schema 12 // 設定フィールド message Field { string field_id

    = 1; // "max_players" string label = 2; // "最大プレイヤー数" optional string description = 3; // ヘルプテキスト SettingValue value = 4; // 現在の値 // フィールドタイプごとの設定 oneof field_config { TextField text_field = 5; SliderField slider_field = 6; ToggleField toggle_field = 7; SelectField select_field = 8; …, etc. } } Fieldの共通要素と各Fieldをoneofで
  8. protobuf schema 13 // テキスト入力フィールド message TextField { optional int32

    min_length = 1; optional int32 max_length = 2; optional string pattern = 3; optional string placeholder = 4; } // セレクトフィールド message SelectField { repeated Option options = 1; optional string description = 2; } …, etc. Fieldごとに必要なattrを型として定義
  9. export const DynamicField = ({ field, values, onChange }: Props)

    => { const commonProps = { label: field.label, description: field.description, } switch (field.fieldConfig.case) { case 'textField': { const value = values.get(field.fieldId)?.value return ( <TextField {...commonProps} value={value.case } config={field.fieldConfig.value} onChange={(newValue) => onChange( field.fieldId, new SettingValue({ value: { case: 'stringValue', value: newValue } }), ) } /> ) } case 'sliderField': { … 14 FieldTypeごとに分岐 それぞれのComponentに Responseをそのまま渡す 各Componentの中では Stylingのみのロジック frontendでの変換
  10. 15 - コード差分 - (frontend) 共通Component: +12ファイル、+1000Lines - (frontend) 特定ゲームのComponent:

    -21ファイル、-1500Lines - 将来的にはこれ×対応ゲームの数削減できる - (backend) RPC: +2個 (List/Update)、共通ロジック: +150Lines、個別ロジック: +500Lines - ゲームのアップデート対応 - Responseの内容を変更するだけでUIの更新が可能になる - schemaの変更も不要、破壊的な変更にならない 「すべてのビジネスロジックはサーバーサイドで管理し、クライアントはUIのレンダリングのみを担当する」 導⼊した結果
  11. 16 - 特殊なFiledを扱うか - 現状、特定の設定項⽬にのみ適⽤されるような特殊なUIは対象外としている - 様々なケースを許容することによりschemaの複雑性も増すので判断の基準は必要 - Database-Driven UI?

    - DBでUIを構成する要素を管理 - 管理⽤APIなどから項⽬の編集を可能に - アプリケーションのリリースゼロでゲームのバージョンアップ対応が可能になりそう 今後の展望