valid,invalid

関心を持てる事柄について

React Adminの感想

marmelab.com

A frontend Framework for building data-driven applications running in the browser on top of REST/GraphQL APIs, using ES6, React and Material Design. Previously named admin-on-rest. Open sourced and maintained by marmelab.

  • React Admin 管理画面を作るのに最適化された React アプリケーションフレームワーク
  • France のMarmelab社によってメンテナンスされている OSS がコア
  • Enterprise Edition もある
    • OSS として公開していない便利な private modules が使えたり、開発サポートが受けられたり、改善要求を優先的に Development Roadmap に載せてくれたりする
    • 利用したことがないので存在に言及するだけにとどめる

どんなものが作れるかは公式の Demo サイトを見てみるとよいです。

marmelab.com

Demo サイトのコードも https://github.com/marmelab/react-admin/tree/master/examples/demo から参照できます。

あらすじ

前回 バックエンド Web API に管理画面/管理機能を追加するアーキテクチャパターン - valid,invalid にて書いたとおり、React Admin の所感を書きます。

最初は有用性を疑っていたフロントエンドの admin 系フレームワークについてもだいぶ評価を改めたので、その感想も近々書きたいと思います。

チュートリアルや How to 的な内容はスキップします。また、タイトルの通り複数のフレームワークの比較ではなく実際に開発・本番運用している React Admin に限定した感想です。

どうやって使っているか

アーキテクチャ上の位置づけとしてはこんな感じで、既存のバックエンド API にプレゼンテーションレイヤを持たせず、管理機能専用のフロントエンドを作ったパターンです (前回の記事でいうパターン 2)。

バックエンド Web API

バックエンド Web API は RESTish な実装であり、React Admin のSimple REST Data Providerを利用しています。

管理機能を実装するにあたりバックエンド Web API にも相当数のエンドポイントとデータモデルを追加しており、いずれも Admin namespace 配下にコードを置いています。エンドポイントの URL も /admin/v1/... のように区切っています。

顧客機能と管理機能が同一コードベース上にあるわけですが両者の間で完全に境界を引いているわけではないです。

予め書いておくと、React Admin は管理機能のフロントエンドの開発生産性に寄与するもので、バックエンドの開発コストは通常の Web API 開発と変わらないです。

Data Provider とは

バックエンドの Web API とのつなぎこみには Data Provider と呼ばれる機構を使う。

const resposen = await dataProvider.getOne("posts", { id: 123 });

Data Provider はこういうメソッド呼び出しを HTTP リクエストに変換して実行する API adapter*1

https://marmelab.com/react-admin/DataProviders.html より引用

  • adopter は React Admin がデフォルトで提供している
  • 自前で書くこともできる
  • コミュニティが作っているものも大量にある
    • Amplify や Hasura をバックエンドとする Data Provider もある

アダプタを注入できるので Data Provider を呼び出すコードではバックエンドが REST なのか GraphQL なのかなどを気にせず、必要に応じて差し替えたりできる。

デプロイメント

  • ビルドしたアセットを含む Nginx コンテナを Fargate で動かしている
  • CDN は使っていない
    • 利用者が限定されていてアセット配信のパフォーマンスは気にするまでもない
    • コンテナによる抽象化を活用することで他のサーバアプリケーションと同じように CI/CD パイプラインを組める

権限管理 in バックエンド

詳細は書きませんがざっくり。

  • ログイン機構と権限管理機構を顧客と管理者で完全に分離
    • 顧客が間違っても管理機能を使えることはない仕組みを意図
  • 管理者のみアクセスできる静的コンテンツは Reproxy を使って認証
    • バックエンド API で認証したあとにバックエンド API の手前にいるリバースプロキシを経由してクラウドストレージから配信
    • URL が露出したり誤ってコピペされても管理者としてログインしていないクライアントからは参照できない

SPA をシステムコンポーネントとしてどう扱っているかという話をしてしまったが、そろそろ React Admin の話を書く。

React Admin 良いところ

管理機能の開発コストを下げる

事業会社で働くエンジニアとしてこの手のフレームワークを評価する観点は「開発生産性が上がったのかどうか」「事業の目的を妥当なコストで達成することに寄与するのか」に尽きると思うのでその点をまず書きます。

個人の結論として「よくある CRUD 程度なら本当にシュッと書ける」「生産性が上がった」「管理機能の開発コストが相対的に低くなった」と感じているのですが、単に「上がった!最高!」みたいなことを書いても空中戦ないし提灯記事なので具体例として React Admin の Demo サイトの Order page の例を見ます。

  • Order 一覧 (コード)
    • 特定フィールドでの検索
    • ページネーション
    • ソート
    • 複数行選択での一括操作
    • CSV エクスポート
      • 画面に見えてない行も含めて全件
    • 行クリックで編集画面に遷移
  • Order 編集 (コード)
    • 編集
    • 保存
    • 保存後に一覧に遷移
      • この時点で HTTP リクエストはまだ飛んでいないが、対象データはすでに楽観的に更新されている
    • Undo
      • HTTP リクエストを飛ばす前であれば画面下部の Snackbar から取り消せる

コードを見るともっと細かいビジネスロジックがいろいろ入っているのですが、まぁよくありそうで一部めんどうくさそうな管理機能群です。これぐらいの機能を持った SPA の開発、どれぐらいかかりそうでしょうか。

半年弱、片手間で開発してみた体感としては、慣れてくればざっくり1 週間程度といった見積もりです。

これが早いのか遅いのかは読み手の練度によってだいぶ印象が変わると思いますが、少なくともフロントエンドエキスパートではない自分からすると使い始める前の予測よりずっと早いです。

実際のコードを見るとわかるように Redux store や非同期処理のことは Data Provider 丸投げなので全く気にしていないし、レイアウトやマークアップに関してもデフォルトのスタイルを使うので CSS をほぼ書いていません。過去に自分が SPA 開発で時間を費やしていた部分の多くが削減され、UI を宣言的に書くだけで画面が構築できる、その点に集中して開発ができている印象です。

ちなみに unit test は期待する API response データを与えたときに意図した attribute が意図したフォーマットで表示されているか、を見るぐらいです。というのもほとんどがフレームワークの提供する機能なので unit test としてカバーするのは自前で書いた宣言的 UI の部分に限定されてくるため。フレームワークが多くのめんどうを見る場合に testing のコストも減る好例と感じます。

testing の方針次第では増減あり、たとえば E2E をがっつり書いていくことになるともっとかかると思います。

フロントエンドのプラクティスが詰め込まれている

前回の記事に書いたとおりです。

フロントエンドの admin 系ライブラリはバックエンド発祥の admin 系ライブラリに比べてフロントエンドのプラクティスの詰め込みが充実しているのが気に入っています。Optimistic UI, Partial loading, Undo, Back forward cache 等々、あると便利だけど自前で書くとすこし面倒な実装パターンたちも、フレームワークのレールに沿って書くだけで動くものができます。リッチな体験は不要かもしれませんが実質無料でついてくるのであればあるに越したことはありません。

上述の Demo でも一部見えているやつで、気に入っている便利 feature たち。

  • Optimistic UI
    • submit したらすぐに store と view が更新される
    • API コールに失敗したらロールバックされる
  • Undoable
    • submit した直後に undo ボタンが出る。Gmail みたい
  • Back forward cache
    • ブラウザバックしたときもリソースが store にあれば fetch する前に表示します

開発体験の良さ

React Hooks 時代に対応

React Hooks 対応しているので component 内で行うあらゆることが簡単になりました。

  • useTranslate
    • component 内で i18n
  • usePermission
    • ログインしているユーザーの permission を得る
  • useMutation
    • page に紐づく特定のリソースへの CRUD とは別に、任意の API コールを行いつつ React Admin の便利機能を使える

他にもいろいろあります。

フロントエンド資産が使える

React Admin に限らない話ですが、フロントエンドの各種資産が使える。

  • フロントエンドで印刷機能を作ったりグラフを描画したりするのにさほど苦労しない
  • サーバサイドでの Admin 系フレームワークではこうはいかない

TypeScript 時代に対応

権限

  • ログイン、auth 周りのサポートがある
  • ログインしたユーザのロールに応じて routes を動的に分けるテクニックが使える

React Admin悪いところ

テスト

Unit testing のサポートが薄い。

By default, react-admin acts as a declarative admin configuration: list some resources, define their controllers and, plug some built-in components or your own to define their fields or inputs. Thus, unit testing isn’t really needed nor recommended at first, because the internal API of the framework is already tested by its maintainers and each custom component can be tested by its own by mocking react-admin. https://marmelab.com/react-admin/UnitTesting.html

「こう宣言したらこう表示される」程度のテストはフレームワークの内部をテストしているようなものなので、確かに気持ちはわかる。

先述の通り、List や Show page のコンポーネントの unit test を書いているが、context provider でラップしてやらないといけない物も多いのでセットアップで工夫する。

react-testing-libraryの例を参考にこんな雰囲気の helper を書いている。

import "@testing-library/jest-dom";
import React, { FC, ReactElement } from "react";
import polyglotI18nProvider from "ra-i18n-polyglot";
import { render, RenderOptions } from "@testing-library/react";
import { TestContext, TranslationProvider } from "react-admin";
import { MuiThemeProvider } from "@material-ui/core";
import { getTheme } from "./my-theme";
import messages from "./my-translation-file";

const i18nProvider = polyglotI18nProvider(() => messages);

const withProviders = ({ initialState = {} }): FC => {
  return ({ children }) => {
    return (
      <TranslationProvider i18nProvider={i18nProvider}>
        <MuiThemeProvider theme={theme}>
          <TestContext initialState={initialState}>{children}</TestContext>
        </MuiThemeProvider>
      </TranslationProvider>
    );
  };
};

type Options = Omit<RenderOptions, "queries"> & {
  initialState?: Record<string, unknown>;
};

export const renderWithProviders = (
  ui: ReactElement,
  options?: Options
): ReturnType<typeof render> =>
  render(ui, {
    wrapper: withProviders({ ...options }),
    ...options,
  });

// re-export everything
export * from "@testing-library/react";

その他もろもろ

  • ちょっとした融通の効かなさ
    • 細かいけど DELETE メソッドには body を含められない
    • セマンティクス的には正しいが、諸事情によりセマンティクスから外れた HTTP API を呼ばなければいけないことも人生には、ある
  • 内部で利用しているライブラリの選定
    • react router, redux final form, redux-sagaなど、2021 年で主流かどうかあやしいライブラリ
    • 後述の通り、利用者からが意識しなくて良い程度に隠蔽されているので辛みは今のところない
  • 公式ドキュメントが JavaScript 時代のもの
    • 内部実装は書き換わったがドキュメントがまだ追いついていない

印象

良し悪しではなく「こうだなぁ」と思ったこと。

Simple よりも Easy

思想としては間違いなく Simple ではなく Easy です。

利用者が意識しないですむよう多くのことが隠蔽されていますが、全容を知ろうとすると complex で理解が及ばない箇所にぶつかります。

Easy は早期に成功体験を与え、はずみをつけるもの (momentum builder という表現が気に入っている) なので、フレームワークの性質としてスタートアップや新規事業の性質に適合しているといえると思う。

ohbarye.hatenablog.jp

React Admin が内部で使っているライブラリの知識がどれだけ必要になるか

内部的に利用されている主要なライブラリの知識がどれだけ必要になるか。使い方によっては印象が変わるかもしれない。

material-ui

react-roter

  • ちょっと必要
  • default ではHashRouterになっているがBrowserRouterにすぐ切り替えると思う

redux-final-form

  • ほとんど意識しない
  • React Admin が提供する edit/create 画面とは異なる独自 Form を作るときは必要
    • そういったシーンは少ない

redux

  • ほとんど意識しない
  • デバグのときに dev tool で store を覗いたりする程度
  • store にアクセスする hooks もあるがほとんど使わない

redux-saga

  • 全く意識しない
  • redux-saga をついに理解できないままだったので不安に感じながら react-admin を使い始めたが、これは素晴らしい

オブジェクトベース UI と相性がよい

上述の Demo のようにオブジェクトが中心にあり、その操作をあとに決めるような UI 設計と相性が良い。

オブジェクト、言い換えればリソースの再利用ができると Redux Store が活きるシーンが多い。逆に言えば「注文画面では User object はaddressee_nameを持つがユーザー一覧画面ではaddressee_namenull」みたいなリソース設計をしていると再利用が難しい。ユーザー一覧画面表示後に注文画面を表示するとき、同一リソースだと React Admin が気を利かせて Store データをフィルインしようとしてくれるのだが null 安全ではないので失敗したりする。

// 大統一ユーザー
interface User {
  first_name: string;
  last_name: string;
  addressee_name: string | null;
}

バックエンドでは同じテーブルのデータであっても別のリソースとして宣言し、エンドポイントも分けておくほうが React Admin 的にも扱いやすい。

// ユーザー一覧ページ用ユーザー
interface User {
  first_name: string;
  last_name: string;
}

// 注文一覧ページ用ユーザー
interface UserForOrderPage extends User {
  addressee_name: string;
}

管理機能はオブジェクトベース UI が向いている?

余談ですが、管理機能はオブジェクトベース UI とタスクベース UI のどちらが向いているのでしょうか。まぁ、これもまた…事業・プロダクト特性・ユースケースに依存するのでどちらともいえないと思います。

承認ワークフローのような機能を作るのであればステップに合わせたタスクベース UI が向いているでしょうし、同一リソースの表現が何種類も生まれる可能性が高い。

一方、カスタマーサポート/カスタマーサクセスが顧客の行動履歴を参照するために活用する管理機能が中心であれば、顧客のイベントリソースをオブジェクトベース UI で扱うのが向いているかもしれません。

同一アプリケーションでもシーンによってメンタルモデルは異なるので、まぁ答えはないと思います。

f:id:ohbarye:20210124145624p:plain
こんな雰囲気の一覧ページも作れる

つらそうなケース

つらいケースの開発をしてないのですが、こうだったらつらそうと思うシーン。

  • API が RESTish でないとき
    • 先述の「DELETE に body を含める」のような HTTP の標準や規約から逸れるような場合は React Admin の Data Provider ではサポートしづらい
      • API を呼び出す際に独自の hook を書いて個別にカバーする
    • API のあり方を常識的な範囲で矯正・強制するといえる
  • 既存の React, Redux アプリケーションにあとから足すとき
    • react-admin はフレームワークなのでアプリケーションの根っこから変えないといけない
    • routing や非同期処理・ストア設計などをマージしないといけないので、react-admin 単体で利用するときには隠蔽されていた redux-saga などの知識が要求される
  • 独自の UI デザインシステムごりごりやっていく場合
    • 共通の考え方・ツール・アセットが material-ui なので、良くも悪くも見た目は material-ui になる
    • Atomic Design などをちゃんとやっていくとなると material-ui を react-admin がラップしたコンポーネントをさらにラップしたものを作らないといけない

トータルで見て

多機能なのでまだ使いこなせていない機能もあるが、現時点では開発体験は良いし生産性高く管理機能を開発できていると感じている。

企業のフルタイム開発者がメンテナンスしているだけあり 2019 年〜の開発ペースを見るに安定している。

あと数年は生き残りそうなので React Admin よりもプロダクトの寿命が長くなるように頑張っていきたい。

環境

  • react-admin v3.11.3

参考リンク

*1:データストアを Web API にする場合。localStorage を Data Provider にする際はリクエストは行われない