データ設計と向き合う

サーバサイドエンジニアの坂根です。

最近某企業から販売されるウイスキーの価格改定が発表されましたね。

国産ウイスキーが転売されることなく、安定供給されることを切に願います。

さて、今回はデータ設計で気をつけていることについて話していきます。

何を大事にしているか

ヒトコトで言ってしまうと、現実に忠実であることです。

システムのデータと現実は、しばしば対になります。 そしてシステムの関心事が増えると、現実に沿ってデータを増やします。 現実に反するマジカルな設計になっていると、データを増やす際に歪な形で積み上げていくことしかできず、最終的にはジェンガのように崩れてしまうのです。

現実に反するマジカルな設計

では、マジカルな設計とは何か?具体例を基に見ていきましょう。

初期

資産管理システムを構築します。 どの社員に液晶モニタを貸与したか、一覧で表示できるようにしましょう。

マジカル設計の初期
マジカル設計の初期

中期

一部の社員からモニタが小さいというクレームがあり、大きめのモニタを導入することになりました。 資産としては別物なので、管理システムでも違いが分からなければなりません。

インチ数が分かればいいんだな!とストレートに付加します。

マジカル設計の中期
マジカル設計の中期

後期

資金が潤沢になり、業務効率を少しでも上げるために予備のモニタを購入できることになりました。 社員に貸し出さない予備資産を登録します。

社員にぶら下げることができなくなってしまったため、やむを得ず在庫として登録することを考えます。 しかし資産を一覧で可視化しようと思うと、社員と在庫の双方を集計しなければならなくなりました。

マジカル設計の後期
マジカル設計の後期

現実に忠実な設計

では、現実をできるだけ忠実に再現するとどのようになるでしょうか?

初期

社員に貸与した液晶モニタを、一覧で表示できるようにします。

現実に忠実な設計の初期
現実に忠実な設計の初期

中期

貸与したモニタのインチ数が区別できるようにします。

現実に忠実な設計の中期
現実に忠実な設計の中期

後期

在庫を登録できるようにします。

現実に忠実な設計の後期
現実に忠実な設計の後期

拡張性

後者の設計では、今後1人に貸与するモニタの数が複数に増えた場合でも無理なく対応できそうです。 それは、データが現実に通じており、似た土台から変化を起こすことができるからです。

つまり、拡張性高く保つためには、現実に忠実であることが重要なのです。

まとめ

検索効率を上げる、特定の何かに特化する、そのために現実を犠牲にすることもあるでしょう。 それでも私は、現実に忠実なモデルを起こした後、犠牲にするもの(リスク)を認識したうえで取捨選択します。

みなさんも良いデータ設計ライフを!

ジモティーではバックエンドに熱い魂を捧ぐ仲間を募集しています。 ご興味のある方はこちらを御覧ください。

ジモティーのフロントエンドをNext.jsに移行していくという話

どうも鈴木です。好きなプレインズウォーカーは初代ガラクです。

最近スタンダードでは緑単が強かったみたいですが、新弾出てどうなるんでしょう。

それはさておき、今回はジモティーのフロントエンドをNext.jsに移行していくという話を書きます。

背景

ジモティーは今年でリリース後10年を迎え、その間、Webブラウザ版のジモティーはモノリシックなRailsアプリケーションとして開発を継続してきました。 当然ながら技術負債の蓄積はさけられず、特にフロントエンドの生産性の低下が顕著でした。

まず、以下の理由でフロントエンド単体で見た時に生産性が悪くなっていました。

  • デザインガイドライン整備前の無秩序なCSS
  • メンテナンス性の低いJavaScriptコード
    • 一定以上の複雑さを持つ機能が無理矢理jQueryで書かれている
    • 一部Vue.jsを採用しているがRailsのview上に実装するためにベターjQuery的な活用に留まっている

また、Railsエンジニアがフロントエンドからバックエンドの実装を担当するため、以下の問題がありました。

  • フロントエンドとバックエンドが密結合しているため分業がしづらく、並行開発できない=リードタイムが長い
  • チームとしてフロントエンドの専門的なスキルが伸びにくい

ということで、これらの負を解決し、以下を実現することを目的にフロントエンドの刷新に着手しました。

  • フロントエンドエンジニアとバックエンドエンジニアが分担してそれぞれの領域に集中しやすくする
  • 技術負債になっていたコードを置き換え、モダンなフロントエンド技術を採用することで、フロントエンド単体での開発生産性を上げる

制約

さて、やると決めたのは良いものの、ジモティーのサービスの特性を考えた時にいくつかの制約がありました。

  • SEO上重要なページはSSR(Server Side Rendering)で実装する
  • 既存ページを移行する際、UX・パス・機能は基本的に維持する
    • (開発生産性のための再構築なので、UXの大幅変更により事業KPIに影響がでるのを避けるため)

これらを踏まえた上で技術選定しました。

技術選定

選択の理由も添えて紹介します。

  • TypeScript: 採用
    • 型安全性の恩恵を受けたいので採用
  • React or Vue.js: Reactを採用
    • 現時点でのTypeScriptの親和性を考えるとReactが優勢と判断
    • 検討時、Vue 3が出てから間がなく知見が溜まっていないということも考慮した
  • フロントエンドフレームワーク: Next.jsを採用
    • SEO上重要なページでSSRを採用するためSSR可能なフレームワークは必須
  • UIフレームワーク: 不採用
    • 新規サービスのこちら ではMaterial UIを採用していたがジモティーでは独自のデザインシステムがあるため既製のCSSフレームワークを導入するとカスタマイズの工数がかえってかさむと判断。
  • BFF(Backend For Frontend): 不採用
    • 今回の目的に照らすと必須ではない
    • 通信速度の悪化や運用・保守コストの増大などのデメリットが見込まれるため不採用
    • BFF で裏側のRESTを束ねて、GraphQL + Apollo Clientでハッピーになるんや、みたいな話もあったけど一旦冷静になった
  • StoryBook: 採用
    • デザイナーとエンジニアのコミュニケーションがスムーズになる
    • 長期的にメンテナンスするような自社サービスなら必須レベルと判断

移行の課題

ジモティーの場合、大量の既存ページがありそれらを一気にNext.jsに移行することは現実的ではないため、ページ単位あるいはまとまった機能単位で移行していきます。 したがって、移行済のページはNext.js、移行前のページとAPIコールはRailsにリクエストを振り開ける必要があります。

そこで問題になるのは以下の点です。

  • リクエストの分配方法
  • ログイン認証
  • CSRF対策

リクエストの分配方法

以下の方法を検討しました。

  1. Next.jsとRailsでドメインを分ける
  2. Nginxをリバースプロキシとして使って振り分ける
  3. ALB(Application Load Balancer)のパスベースのルーティングで振り分ける

ドメインを分ける方法の場合、移行前後でページのURLが変わるデメリットが大きいと考え不採用にしました。

Nginxをリバースプロキシとして運用する場合、スケーリングやメンテナンスの対象となるサーバーが一種類増えるという問題があります。

一方、ALBについては既に使っているため管理対象が増えないという状況でした。

したがって、ALBのパスベースのルーティングを使って、Next.jsとRailsへの振り分けを行うことにしました。

f:id:jmty_tech:20211105201720p:plain

ログイン認証

ブラウザからのリクエスト先がRailsの場合とNext.jsの場合ある、という前提でログイン状態を維持する必要があります。

この問題はSPAのログイン認証をどう実装するか、という問題とよく似ており、 こちらの 記事 が非常に参考になりました。

今回の場合は、Next(フロント)とRails(API)が同一Originのため、参考記事内の「Sessionを用い、SessionのCookieはSameSiteCookie」を採用することができます。

あとはNext.jsからRailsにAPIコールする箇所でクライアントから受け取ったCookieを引き渡すように実装すれば、Next.jsページとRailsページ(API)の間でログイン状態を維持することができます。

※図を挿入

CSRF対策

RailsではフォームにCSRFトークンを埋め込み、セッションに保持しているトークンと同一か検証することでCSRF対策を行っています。

一方、Next.js移行後の画面ではフォームをNext.jsが提供することになります。Railsが発行するCSRFトークンをNext.jsのフォームに埋め込めば従来の方法で検証が可能ですが、フロント〜Rails間でのCSRFトークンの通信が発生するので通信のオーバーヘッドを考慮すると好ましくありません。

そこで、以下の2つの対策を組み合わせて実施することにしました。

  1. SameSite Cookie
  2. Originヘッダの検証

SameSite Cookie

SameSite Cookieとはクッキーに指定できる属性の一つです。SameSite=Laxを指定するとブラウザは別サイトからのPOSTの際にCookieを送信しません。これによってCSRFを防ぐことができます。

2021年時点ではモダンなブラウザでSameSite=Laxがデフォルトになっていますが、まだ未対応のブラウザもあり得るため明示的に指定します。

Rails.application.config.session_store :cookie_store, same_site: :lax

Originヘッダの検証

古いブラウザでSameSite属性をサポートしていないものが存在するため、Rails側でのOriginヘッダの検証も実施します。

ALLOWED_ORIGIN = "https://example.com"

def allowed_origin?
  origin = request.headers["origin"]

  # 同一Originの場合にOriginヘッダを付与しない挙動のブラウザもあるため、空の場合は許可する
  return true if origin.blank?

  ALLOWED_ORIGIN == origin
end

We are hiring !

ジモティーではこの記事で紹介しているフロントエンドの刷新をはじめ、様々な挑戦のために新しい仲間を求めています! ご興味のある方はこちらを御覧ください。

jmty.co.jp

リリース作業をかんたんに! git-pr-release + Google Apps Script + Ruby スクリプト + GitHub Actions によるリリース作業改善の取り組み

ジモティーでサーバサイドとインフラを担当している吉田です。

前回は ISUCON10 に参加した話を投稿しました。今年もちょうど ISUCON11 を終えたばかりですが、残念ながら予選敗退となりました。ISUCON への参加はウェブエンジニアとしてのキャリアを見つめ直すとてもよい機会で、来年こそは決勝に進みたいと思っています。

さて、今回はジモティーのサーバサイドで1年ほど運用している、リリース作業の一部自動化の取り組みについて、背景から実施までをご紹介します。

従来のリリースフローとその課題

まず、弊社サーバサイドのリリースフローについて、当時の概要は次の様になっていました*1

変更前のリリースフロー
変更前のリリースフロー

  1. コードレビューを経て、トピックブランチを master ブランチにマージ(作業者: 各機能担当者
  2. リリース PR を作成(作業者: リリース担当者
  3. 機能担当者にリリース前テスト環境での動作確認依頼(作業者: リリース担当者
  4. 動作確認(作業者: 各機能担当者
  5. Jenkins のジョブを実行してリリース(作業者: リリース担当者

リリース担当者はリリースしたいメンバーによる立候補制で、サーバサイドのメンバーであれば誰でもこの役割になる可能性があります*2

この作業の1番の課題は、マージされた PR の「タイトル」「作成者」を目視で抜き出す事が大変な点です(図の2. および 3. に該当)。

詳細な流れは次の内容になります。

リリースPRの作成と動作確認依頼
リリースPRの作成と動作確認依頼

  1. master ブランチへのコミットログの中からマージコミット (メッセージが Merge pull request #xxxx from jmty/xxxx) を確認
  2. マージコミットの PR から「タイトル」「作成者」を確認
  3. 確認した「タイトル」をリリース PR の本文に入力
  4. 確認した「作成者」にリリース前テスト環境での動作確認を Slack のメンションで依頼

また、PR の「作成者」は GitHub のアカウント名なので、Slack のアカウント名に変換して Slack で本人にメンションする必要があります。そのため、たまに誤って違うメンバーにメンションをしてしまう問題も発生していました。

変換ミスによる誤メンション
変換ミスによる誤メンション

これをマージ済み PR の数だけ繰り返す必要があり、リリースタイミングによっては1度に10件を超える事もあります。

改善

事前準備

まず、課題を明確にします。こちらは前述の通りです。

  • リリース PR 作成時に、マージされた PR の「タイトル」「作成者」を目視で抜き出す事が大変
  • 誤って違うメンバーに動作確認依頼を行ってしまう

次に、その解決方法を調べます。

基本的に手作業で行っていた、マージされた PR の「タイトル」「作成者」を目視で抜き出して作業担当者に依頼する作業を自動化する方針で、かんたんな実装を行いました。これは、次で行う提案のための準備になります。フィードバックの結果、内容を大きく変更する可能性も十分あるため、あまり作り込まず、かつ GO サインをもらえるレベルにします。

KPT での提案

準備した内容をもとに、週1で実施しているウェブチームの KPT で下記の提案をしました。

  • 課題の共有
    • リリース作業がツラい
  • 解決案の提示
  • FAQ
    • リリースフローはどう変わるのか
    • 導入・運用コスト
    • メリットだけではなくデメリットも
    • なぜ CI は既存で利用している CircleCI ではなく GitHub Actions で動作させるのか、など

想定していた実装工数は比較的小さいため、一気に導入する手もありますが、次の観点から提案のステップを挟むことにしました。

  • 共通の課題である事の確認および、ニーズの確認
  • 異動や退職などで技術的な負債となる可能性がある
  • リリースフローの変更により、障害に繋がりやすくなる可能性がある

結果はおおむね期待していた良い反応だったため、具体的な実装に移りました。

変更後

最終的に次の流れになっています。

変更後のリリースフロー
変更後のリリースフロー

  1. コードレビューを経て、トピックブランチを master ブランチにマージ(作業者: 各機能担当者
  2. リリース PR を作成(作業者: リリース担当者
  3. 機能担当者にリリース前テスト環境での動作確認依頼
    1. 本文を自動更新 (git-pr-relase gem)
    2. GitHub と Slack ID のマッピングを取得 (Apps Script)
    3. 動作確認依頼 (Ruby スクリプト)
  4. 動作確認(作業者: 各機能担当者
  5. Jenkins のジョブを実行してリリース(作業者: リリース担当者

「機能担当者にリリース前テスト環境での動作確認依頼」の作業を自動化しています。

それぞれの技術トピックについてご説明します。

git-pr-release

git-pr-release はリリース PR を作成・更新する gem です。マージされた PR からタイトルや担当者を取得して、PR を作成・更新してくれます。

当初自作をしようと考えていたところ、希望の機能を満たすこちらの gem が見つかり、OSS かつ定期的にメンテナンスされていたため採用しました。

最近 GitHub の仕様変更があり、期待する動作をしなくなる事がありましたが、すぐに対応されたものがリリースされました。

もし自作していた場合、同様の対応を行う可能性が高かったはずです。

Google Apps Script

GitHub アカウント名と Slack ID のマッピングを JOSN として返すウェブアプリケーションサービスです。

次はサービスのURLに対して curl からの擬似的なアクセス例になります。

$ curl -H "Authorization: token xxxx" -L \
  --dump-header - \
  "https://script.google.com/xxxxxx"
(省略)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
(省略)

[
  {
    "slack_member_id"=>"XXXX",
    "slack_display_name"=>"kyoshida", 
    "github_login_name"=>"kyoshidajp"
  },
  {
    "slack_member_id"=>"YYYY",
    "slack_display_name"=>"yyyy",
    "github_login_name"=>"zzzz"
  },
  ...
]

マージされた PR からその作成者(機能担当者)のアカウント名を取得した後、Slack ID に変換する際にこのマッピング情報を参照します。

事前に Google スプレッドシートにそれぞれの対応を記載しておきます。新しいメンバーが加入したら都度こちらに追加します。このシートのメンテナンスが手動になってしまいますが、他の利用用途でも使えるようになります。

Ruby スクリプト

リリース PR の本文から、マージされたPRの「タイトル」「担当者」を取得して Slack に通知します。

Ruby は弊社サーバサイドにてメインで利用しているプログラミング言語です。ユニットテストや JSON の変換など、豊富なライブラリが付属している事に加えて、Slack や GitHub 連携など16万を超える*3サードパーティのライブラリによる素晴らしいエコシステムがあります。

同様の目的には、後述する GitHub Actions のワークフローコマンドから gh や jq を駆使して行うことも可能だと思いますが、上記観点から Ruby スクリプトとしました。

GitHub Actions

git-pr-release および、Slack に通知する Ruby スクリプトの実行環境です。

master から release ブランチへマージするリリース PR が作成されたタイミング、もしくはmaster ブランチにコミットが行われたタイミングで実行されます。後者については、リリース PR が作成された後に、追加でリリース機能を含めるケースに対応するためです。

CI 環境としてすでに利用している CicleCI ではなく、GitHub Actions を採用したのは次の理由によるものです。

  • 世の中の流れとして GitHub Actions がかなり普及してきており、キャッチアップをしておきたい
  • 想定の利用ケースでは無料枠に十分収まる
  • 面白そう

結果

改善により、当初の課題であった、リリース担当者による目視かつ手作業での依頼が不要になりました。

自動で通知される動作確認依頼
自動で通知される動作確認依頼

また、誤って別のメンバーに確認依頼してしまう問題も発生していません。

さらに、工夫したポイントとして、デプロイ時に Slack で通知されるリリースノートの内容が分かりづらかったため、同様の仕組みで整理しました。

整理されたリリースノートの通知
整理されたリリースノートの通知

この結果、エンジニアだけでなく、CS を含む社内メンバーは機能概要機能担当者リリース担当者が明確になりました。

まとめ

日常で感じた課題の解消をモチベーションに、その改善の提案、そして実施までをご紹介しました。

自動化した作業は、以前は1回あたりおよそ5〜15分程度だったので、リターンを時間で換算するとどうしても投資効果を低く感じてしまいがちです。しかし、日々課題に感じていて改善の目処が立っているのであれば、積極的に取り組むべきだと思います。

そもそも、対象の作業は手作業で日々繰り返され、自動化が可能なものでした。こういった、いわゆるトイルの撲滅は(インフラ)エンジニアが価値を発揮しやすい領域ではないでしょうか?

少なくとも、以前の状態には戻りたくないのが率直な気持ちです。

宣伝

ジモティーではエンジニアを募集中です。

jmty.co.jp

日々の不満や課題を一緒に解決していきましょう!

*1:master ブランチへのマージ後にリリース前テスト環境への自動反映など、細かい内容は省略

*2:これは執筆時点も同じ

*3:執筆時点の https://rubygems.org/stats より

ジモティーのフロントエンド事情

初めまして。

2020年末からジモティーでフロントエンドエンジニアとして開発している山口です。

今回はジモティーのフロントエンド事情について紹介します。

ジモティーフロントエンドの現状

現在のジモティーのフロントエンドはRailsのAction Viewを用いて

  • jQuery で Javascript 周り(一部のページの一部の DOM に対して jQuery 代替として Vueも利用)
  • Rails の decorator でフォーマットして Haml でマークアップ
  • Sass でスタイル付け

といった構成になっています。

しかしサービスの規模が大きくなってきたこともあり、どのidやclassをどの jQuery コードが参照しているのか追いづらかったり、 似たスタイルを持つ似た名前のclassが乱立してしまいどれを利用すべきなのかの見通しがつかないなど、開発が辛くなってきているのが正直なところです。

Railsの開発チームはアプリ向けのAPIの作成やバックエンドの開発と並行してフロントエンド開発も行っているため、 辛さが出てきた現状が会社として求める開発スピードUPの足枷になってきています。

新規プロジェクトにてReact(NextJS)を利用

今月リリースした新規プロジェクト、自治体向けの電子申請・申請承認システム「LOCONET(ロコネット)」にて、弊社としては初めて React を利用した開発体制を敷きました。

バックエンドはジモティーに引き続きReilsを利用し、フロントエンドはバックエンドとは完全に分離してReact(NextJS)+Typescript で作成、さらにOpenAPIによる APIドキュメント整備とStorybook によるコンポーネントのドキュメント整備を行うことでバックエンド開発者やデザイナーとのコミュニケーションを取りやすい環境を構築しました。

技術負債との比較

前述した現在のジモティーの開発状況と比べると今回の開発体制では

  • styled-compomentsを利用でスタイルのスコープを制限してスタイルの見通しが付きやすくなる
  • contextの利用を避けてあえてpropによるバケツリレーを選択することでstateなど変数の変更が追いやすくやる
  • Typescript によりコードがよりドキュメントとして理解しやすい形で残るようになったため、第三者がコードを追いやすくなる
  • バックエンドと分離されることでアプリチームと似た分担で開発が可能になり、適材適所の分担で開発が行えるようになる
  • デザイナーとパーツ単位で実装しているスタイルを見ながら密なコミュニケーションが取れるようになる

などのメリットを得ることができました。

代わりに

  • Typescript 導入により、追加で工数が発生してしまう
  • 保守していかなかればならないものが増えた(OpenAPI,StoryBook)

といったデメリットは発生しています。

Typescript導入において型管理以外に特に時間を要したのは次に紹介する2項目です。

外部ライブラリ利用時の型調査の時間

外部ライブラリ使用した際にライブラリ内に定義されている型の内容、利用方法の確認に時間を要しました。

特に初めて利用するライブラリの場合、javascript時の倍は時間がかかる感覚です。

直で呼び出す場合より子コンポーネントへライブラリの一部を受け渡しする必要があった場合など子コンポーネントのpropの定義に悩むことが多かったです。(大抵公式のドキュメントにその場合の型の定義方法が書いてないので直接ライブラリを追ってみるしかなかったです)

例) React Hook Formの場合 https://react-hook-form.com/jp/get-started/

const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>()

useFormで呼び出してきた各種メソッドregister, handleSubmit, watch, errors をコンポーネントへ受け渡したい時、どのような型で待ち受けるべきかがドキュメントからはわからず、なるべくanyなどを使わず実装しようとすると最適解を見つけるのに時間がかりました。

型推論のための追加記述

頻出するわけではないですが、遭遇した場合に解決の時間を用意したのがこちら。

適切な型推論が行われるようにするために javascriptだけならそのままで要件を満たして動作するコードに対して - typescriptが型推論に対応しているメソッドに書き直す - 自前で型推論させる処理を追加する(javascriptの動作上は必要のないコード) という対応をする必要が発生しました。

例) filterを利用してarrayを整理しても方が絞り込まれない例

const x = [123, null] // 型推論結果 const x: (number | null)[]
const y = x.filter(v => v !== null)
console.log(y) // [123]
// 型推論結果 const y: (number | null)[]
// number[] にならない

このままではビルドが通らないので型ガードを駆使したりして型を絞り込む必要が出てきますがチームへのTSの知見が溜まっていないと解決まで時間がかかってしまいます。

...

しかしながら、 Typescript導入やOpenAPI、Storybookはある程度のコード規模が大きくなり、チームの人数が増えてきたときに効力を発揮する要素なので投資として受け入れるべきコストだと考えています。

VueとReactの違い

実は私はジモティージョイン前までVueをフロントエンド開発に利用していたため今回のプロジェクトが React に触れる初の機会でした。

そのため Vue 開発と比べた時の違いについてもいくつか感じることができましたので紹介します。

自由度が高く、良くするも悪くするも開発者次第なReact vs 形式がしっかりしており、とっつきやすいVue

Vue はコンポーネント内で何をどこに定義すべきかはっきりしているのに比べて、React はとにかく自由度が高いため、開発者がやりたいように柔軟な書き方ができる反面、選択肢が多いため、知見がない状態ではベストプラクティスを見出すのが難しいと感じました。

コンポーネントファイル内でstateやeventなどを定義する場所(Vueはdataやwatch,methodなど書き場所が固定されていて開発者でバラつかない)や、スタイルの定義方法は他のオープンソースの事例をいくつか参照してみましたがどれもバラバラで自分たちに合ったやり方を見出す必要がありました。

スタイル付けに関してはスコープをコンポーネント内に留めたいのと、なるべくcssそのままの形で記載ができることをとってstyped-componentをチョイスしましたが正直Vueの方がシンプルにcssそのまま埋め込め、sassなども導入しやすかったので扱いやすかった印象です。

Typescriptとの親和が進んでいるReact、やれることが多いNextJS

自分がしっかりTypescriptに触れていなかった原因でもあるのですが、どうしてもVueの方がTypescript導入が遅れているので、きれいに導入できるReactの方が今のところは軍配が上がるという印象でした。

また、NextJSの方がISRなどへの対応が進んでおり、よりサービスに適した形でビルド方法を検討することができる点も好印象でした。

今後のフロントエンド開発

今後ジモティー自体でも Reils の MVC モデルに乗っかって jQuery を利用している現環境をやめて、今回 LOCONET で作ったようなフロントを分離した開発体制へ移行していく予定です。

かなり機能が多いサービスになっているため、大掛かりなプロジェクトにはなりますが、移行後はより良いユーザー体験と開発スピードを実現できると思います。

ジモティーは今回の私のように未経験技術へ挑戦する機会を積極的に与えてくださり 新規技術への興味関心度合いも高く、エンジニアとして働くのが非常に面白い組織です。

今後もこのサイトではジモティーのフロントエンド事情についての記事を投稿予定ですのでご期待ください。

ジモティーのUI/UXに関する取り組みについて🧸

f:id:jmty_tech:20210625183109p:plain:w250

ジモティーエンジニア紅一点のnaruです🧸
iOSチームで開発を行っています。

最近は自粛で気軽に外に行けないこともあり、自分の所有する車を擬人化したりなどしておうち時間を過ごしたりしてます🧸

f:id:jmty_tech:20210625172747j:plain:w200

前回は↓こんな記事を書いております。 jmty-tech.hatenablog.com

今回もデザイン関連についての話をしたく、UI/UXに関して自社で行っている取り組みの一部を紹介しようと思います。

まず、UI/UXに関するコミュニケーションは今年に入ってからより活性化するようになりました。

f:id:jmty_tech:20210625183029p:plain:w200

というのも、
会社全体のチーム編成や考え方の見直しや、
ジモティーの根幹のDesignSystemの見直しについてデザイナーエンジニア間でMTGする機会が増えたこと、
エンジニア側からもUI/UXの提案を行うようになったことで、
よりお互いが互いの分野に歩み寄って一緒に学んでいきたいという思いが強くなってきた部分があるのかなと思います🤗
(DesignSystemについては、絶賛検討が進んでおりますので、然るべきタイミングでまた話ができれば😌)


#design_infoというオープンチャンネルを作り、


他社の事例を紹介したり、 f:id:jmty_tech:20210625183422p:plain


デザイナーがプログラムについて調べてみたり、 f:id:jmty_tech:20210625183506p:plain


新しいOSの変更点などを共有したりしています🌟 f:id:jmty_tech:20210630135741p:plain

そして、今回メインで紹介したいのが、「UIパッション会」という取り組みです💪

UIパッション会

f:id:jmty_tech:20210625191255p:plain

「もっと色々話したいよ〜🥺」というところで
名前の通り、「UIにもっと目を向けていこうぜ!意見交換しようぜ!💥」という名目の共有会を月1で有志を集い開催しています。

f:id:jmty_tech:20210625182115p:plain:w250

もともと自分は月1で他社アプリのアップデートの内容を共有する、
ということをやっていたのですが

f:id:jmty_tech:20210630135803p:plain

もっと具体的に詳しく話し合ったり、

デザイナーから「こういうの実装したいんだけどどうかなあ」などをフランクに聞いてもらったり

f:id:jmty_tech:20210625194718p:plain:w250


ゴールは案件化ですが、あくまでもざっくばらんに話すを目的にしています:)


これまで実際話された内容としては、

「xxxみたいなUIって難しい?🤔」
「xxx画面の改善を考えているんだけど....🥺」 f:id:jmty_tech:20210630141144p:plain

「某アプリ考察した!😀」
f:id:jmty_tech:20210625201543p:plain:w280

「ios14からAppleが提供するようになったこんな機能があるんだ😏」 f:id:jmty_tech:20210625201744p:plain

あとは、
「〜〜〜っていう機能を作りたいからちょっとざっくり感想教えて欲しい🤗」

などなど....



実際ここから案件起案があがったりもあり、
新しい視点での発見や学びもあり良い取り組みだというところで今後も継続して実施できればと思います。

その他の取り組み

カスタマージャーニーワークショップ

有志で集まってはじめてのカスタマージャーニーマップワークショップという本を参考に、自社サービスについて分析。 各画面の優先度や既存機能の重要さ、改善ポイントをユーザー視点で考えるきっかけになりました💪

f:id:jmty_tech:20210625174342p:plain

UXきんにくん

ディレクターやUXチームを中心に、チームに分かれて他社アプリを洞察して発表し合う、なども行っています。

おわりに

ジモティーでは上記のような取り組み以外にも 色々な分野でユーザー視点での考えを推奨し、評価される制度となっています。
今回書いた内容に興味をもっていただけたり、共感いただけたら幸いです。

jmty.co.jp

AndroidのローカルDBをSQLiteからRoomに置き換えてみた

はじめに

はじめまして。ジモティーに2021年1月からAndroidアプリエンジニアとしてい働いている谷です。

今回はAndroidアプリエンジニアとしてローカルDBをSQLiteからRoomに置き換えた話をさせていただければと思います。

Roomとは

置き換えの話に入る前にさらっとRoomについて説明したいと思います。 RoomとはGoogleが推奨しているSQLiteのORMです。 SQLiteHelperの作成やCursorの操作などめんどくさい処理を一手に引き受けてくれます。

実装も非常にわかりやすく、3つのコンポーネントを作成するだけでローカルDBの構築が可能です。

  • Entity
  • Dao
  • Database

合わせて非同期処理としてRxやKotlin Coroutinesなどのサポートもされており、非常に使いやすいです。

さらにデバッグツールとしてAndroid Studio 4.1から追加されたDatabase Inspectorというものがあり、APIレベル26以上の端末でアプリを実行している時に使用できます。 作成したテーブルの中身をSQLを使って操作したり、Database Inspectorのウィンドウでそのまま書き換えたりといったことが可能です。 こちらにどんな感じのものか詳しく書かれている記事がありますので、是非見てください。

そして以下簡易的ではありますがRoomの実装例です。

まずはEntityを作ります。 これがテーブルの役割を果たします。

 @Entity(tableName = "tbl_todos")
 data class Todo(
     @PrimaryKey(autoGenerate = true)
     val id: Long,
     val title: String,
     val isActive: Boolean
 )

続いてDaoを作成します。 データを取得したい場合はこちらに定義したメソッドを呼び出すことになります。

 @Dao
 interface TodoDao {
     @Insert
     suspend fun insertTodo(todo: Todo)
 
     @Query("SELECT id, title, is_done FROM tbl_todos")
     suspend fun getAll(): List<Todo>
 
     @Query("SELECT id, title, is_done FROM tbl_todos WHERE is_done = :isDone")
     suspend fun getDoneTodo(isDone: Boolean): List<Todo>
 
     @Update
     suspend fun updateTodo(todo: Todo)
 
     @Delete
     suspend fun deleteTodo(todo: Todo)
 }

最後にDBを実装します。

 @Database(entities = [Todo::class], version = 1, exportSchema = false)
 abstract class TodoDatabase : RoomDatabase() {
     abstract fun getTodoDao(): TodoDao
 
     companion object {
         private const val DATABASE_NAME = "todo-db"
 
         fun createTodoDatabase(context: Context): TodoDatabase {
             return Room
                 .databaseBuilder(
                     context.applicationContext,
                     TodoDatabase::class.java,
                     DATABASE_NAME
                 ).build()
         }
     }
 }

おそらく基本的にはDaggerなどでインジェクションすると思いますが、以下のように呼び出すことが可能です。

 // Daoの取得
 val todoDao = TodoDatabase.createTodoDatabase(context).getTodoDao()
 
 // 取得したDaoから各種メソッドでローカルDBにアクセスする
 todoDao.insertTodo(Todo("保存したいTodoのタイトル"))

ここまでが基本的なRoomの使い方となります。

背景

元々ジモティーのAndroidアプリではローカルDBとしてSQLiteとRealmが採用されておりました。 ジモティー内での使い分けとしては以下のような感じです。

  • SQLite
    • サーバーから取得したマスターデータをキャッシュするために使用。
  • Realm
    • 検索結果の履歴保存などに使用。

上記の内、SQLite部分で

  • 実装されているコードが古い。
  • 可読性が低くマイグレーションなどが発生した際にメンテナンスしづらい。
  • ローカルDBへの接続処理がUIスレッド上で実装されている。

という負があり、その辺りが解消されるのではないかということで導入することにしました。

以下、置き換え実施時に参考にした情報です。

  • Migrate from SQLite to Room
    • SQLiteからRoomへのマイグレーション方法について基本的なことが書かれており非常に分かりやすかったです。
  • Incrementally migrate from SQLite to Room
    • 後述するのですが、今回は完全な置き換えができなかったので段階的に置き換えをするにはどうしたら良いかが書かれておりこちらも非常に有用でした。

結果

結果は完全な置き換えはできず、段階的な置き換えを実施する形になりました。 修正範囲が当初想定していたよりも広くなってしまったのと、アーキテクチャに則って作られていない部分の改修難易度が高く時間的にも厳しかったためこの判断になりました。

完全な置き換えはできなかったのですが、最終的には以下が実施できました。

  • 既存テーブルをRoomのEntityとして再定義
    • Stringの配列で設定されていた既存のテーブルがKotlinのdata classとなったことで可読性が上がりました。
  • SQLiteを呼び出していた箇所の一部RoomのDao化
    • Dao内に定義しているQueryアノテーションでSQLが書けるので何をしているのかすぐに把握できるようになった。
    • さらにRoomには決まった形で使われる挿入や更新、削除はコンビニエンスクエリとして事前に定義されたアノテーションが存在するためSQLを書く手間が省け、小さいですが実装時の工数削減になりました。
    • RoomのDaoに置き換えられた箇所では、ジモティーではRxをまだ使用しているのでSingleを返却するようにでき非同期実行できるようになりました。(アプリ全体としてKotlin Coroutinesへの移行を行っているのでどこかのタイミングでsuspend functionに変更する予定です。)
  • SQLiteからRoomへの置き換え時に発生するマイグレーション処理の追加
    • Migrationを使うことで各スキーマバージョン単位でマイグレーション処理を記述することができるため、処理が追いやすく、メンテナンスしやすい状態になりました。
  • SQLiteOpenHelperをSupportSQLiteOpenHelperに置き換え
    • これにより既存のSQLiteの実装を壊すことなく、Room側のSQLiteに移行することができ、今後他の箇所のDao移行も行えるようになりました。

まとめ

今回移行してみて、置き換えたことにより非常に可読性が高く、UIスレッドをブロックしないように作れるためパフォーマンスも良いアプリになっていくだろうと思っております。

今回置き換えを行っていて、途中で方針転換をしないといけなくなったり、リリース直後にマイグレーション周りで不具合を出してしまったりと進め方として改善すべき点も見えてきた経験でした。

今後はSQLiteの処理からRoomのDaoに置き換えることができていない部分がまだまだあるので、そちらも随時進めて行きたいです。

弊社iOSアプリにアーキテクチャを導入してみた ~計画編~

はじめに

サーバサイドチームに所属している丁(てい)です。

前回の記事弊社のiOSアプリにアーキテクチャを導入する前の状況とアーキテクチャを導入してどう変わったのか?をスーパーざっくりご紹介させていただき、その記事を皮切りにアーキテクチャ導入過程の紆余曲折記事を順番に書いていくことを予定していたのですが、その後全く気が向かず長いこと放置していました。

4月に入り気候も少し暖かくなってきたこともあり気が向いてきたので、本日はアーキテクチャ導入当初のお話をもう少し詳しくお話しできればと思いますので、気が向いた方は暫しの時間お付き合いいただけますと幸いです。

導入計画の策定

アーキテクチャを導入しよう!ということは決まりましたが、意気込みだけでは何も前に進みません。 そう、何をどうしていくのか?という計画が必要になります。 そこでまずはこんな項目を網羅した導入計画書を作成しました。

・目的
  ・アーキテクチャを導入することの素晴らしさについて嘘をつかない程度に誇張表現を交えつつ400字程度でまとめた。
・方針
  ・MVP+CleanArchitectureを採用するメリットなどをなるべくわかりやすく400字程度でまとめた。
・進め方
  ・担当者が誰でどの画面から着手してどの順番で進めていく。。。などをもっともらしい文章で800字程度でまとめた。
・ガント
  ・進め方をベースに週単位、月単位でスケジューリング。

実際のガントイメージ f:id:jmty_tech:20210427155317p:plain

この導入計画書はチーム内の意識共有のためという側面ももちろんありますが、主目的は他部署に対する説明です。

アーキテクチャ導入はリファクタリングの範疇なので長期的視点に立つと生産性の向上に大きく寄与するのですが、短期的には特に非エンジニアの方から見るとメリットをいまいち把握することが難しいです。

また画面単位で大きく手を入れることになるのでその期間は該当画面に対するエンハンス案件を回すことが難しくなり、仮にビジネスサイドの人が「この画面の改修案件進めたい!!!!」って思っても、「いやいや今リファクタリング中だから無理っす。。。」というなんとも気まずい空気を作り出す危険性を孕んでいます。

そう言った認識齟齬や納得材料として非エンジニアの方にもある程度納得いただけるように少し時間をかけて導入計画書を作成して他組織に展開して同意を獲得する作業を一番最初に行いました。

ここで一番効果があったのはやはりガントをある程度ちゃんと引き共有して、該当画面の作業期間中は事前にエンハンス案件検討から外してもらえるように調整できたことで、組織間コンフリクトを少なく抑えることができたことだと思います。

回帰テストケースの作成

計画書を作って全体共有もして、さあいよいよ待ちに待ったコーディングだ!!!。。。とは残念ながら行きません。

アーキテクチャが適用されていない画面にアーキテクチャを適用しようとするとかなり膨大な修正となり、(Objective-Cで作られてる画面など)場合によっては完全リプレースとなる大掛かりな修正となります。

アーキテクチャが適用されていない画面はほぼ全てのビジネスロジックがViewControllerに無秩序に記述されているのでその部分にガッツリ手を加えるとビジネスロジックが簡単に壊れることは想像に難くありません。

また画面の仕様を全て表す仕様書などがあればそれを拠り所とすることは可能かもしれませんが、日本全国津々浦々のどのシステム開発現場においても、現状の機能と寸分違わない仕様書の存在は都市伝説だと思っています。(偏見) ※ 日々刻々と変わっていく仕様を全てドキュメントに落としていくのは無駄という側面もありますしね。

したがってアーキテクチャ適用後もビジネスロジックの挙動に変化がないことを何かしらの手段で担保する必要があるのですが、我々は画面別に回帰テストケースを作ることで担保する方針としました。

回帰テストケースイメージ f:id:jmty_tech:20210427155011p:plain

ここで言う回帰テストケースとは「画面の全ての機能を網羅したぽちぽちテストケース」のことを指します。 ※ 「ぽちぽちテスト」とは自動テストじゃなく人間の手で行う動作確認テストのことを指しますが、この言葉を使ってるのは私だけかもしれません。

実装者はまず担当画面の仕様をくまなく洗い出すために以下の二つの切り口で画面の全機能を洗い出していきます。

  • FatViewControllerを頑張って読み解いて機能を洗い出す
  • 対象の画面をぽちぽち触りまくって見てソースリーディングで見落とした機能がないかを洗い出す

もしソースが綺麗に分割されていて洗練されていたら後者の作業は必要なかったかもしれませんが、残念ながらそうではなかったので非常に地味でつらい作業ですがこの過程を無視することはできませんでした。

この地味で孤独できつい作業を一通り終えて乗り越えたらいよいよ本番のアーキテクチャ適用作業に取り掛かることができるようになります。

今回のまとめ

iOSアプリへのアーキテクチャ適用に関する記事の2回目ですが、いまだにXcodeのXの字も出てきませんね。

ただし広範囲にわたるソースコードの改修は関係各所至るところに大きな影響を与えるので、前準備をしっかりすることがかなり重要なキモとなります。

今回ご紹介させていただいた内容の範疇だと以下の2点ですかね。

  • リファクタリングをスムーズに進めるためには計画をしっかりと立てて、他部署への共有をしっかりとしましょう。
  • 大規模改修の際はできるだけ緻密なテストケースを作りましょう。(回帰テストケースは仕様書と同様にその後の画面改修の仕様に追いつかせるのが大変(無理?)という課題はありますが。。。)

次回はいよいよ導入編に入る予定ですが、また気が向かない期間が長くなりそうな予感はしてます。

気が向いたら書こうと思いますので、その時にはまたお付き合いいただけますと幸いです。