Slide 1

Slide 1 text

複雑性に立ち向かうための サーバーサイドコード分割 前田 浩邦 1

Slide 2

Slide 2 text

自己紹介 ▌前田 浩邦 / Hirokuni Maeta ▌2014年4月 サイボウズ入社 ▌kintone開発チーム / ソフトウェアエンジニア ▌バックエンドとフロントエンド ▌最近はkintoneのインフラ移行プロジェクトに参加 2

Slide 3

Slide 3 text

kintone ▌業務システムを自分たちで作成 ⚫案件管理、顧客管理、… ▌様々なアクセス権、ワークフロー ▌REST APIを使った他システムとの連携 3

Slide 4

Slide 4 text

目次 ▌コード分割とは ▌コード分割トライアルプロジェクト ▌コード分割を通して見えてきた複雑性 ▌今後の課題とまとめ 4

Slide 5

Slide 5 text

コード分割とは 5

Slide 6

Slide 6 text

背景 ▌肥大化・複雑化するコード ⚫リリースして10年経過 ⚫src/main/java下のファイル総行数 ≧ 35万 ▌開発効率の低下 ⚫どうリファクタリングすればいいか分からない、コードの可読性を上げづらい ⚫影響範囲の確認が難しい ⚫覚えることが多く、新メンバーがキャッチアップしづらい 6

Slide 7

Slide 7 text

コード分割 ▌コードを以下のような状態にすること ⚫機能ごとにパッケージに分割 ⚫機能間で関わらないなら、コード間も関わらない、依存しない ▌機能に沿っていて、独立して理解可能なコードの塊を増やしていくことで、開発の効率化を 目指す 7

Slide 8

Slide 8 text

機能の特徴を捉えた上で分割する ▌コードだけを基に分割しても、複雑性を解消できるとは考えづらい ⚫例えば、同じクラスを返り値にするメソッド、ということで共通化されるかもしれない ⚫その実装で動くからという理由だけで、偶然同じになってしまった可能性もある ⚫つまり、コードだけを基にした分割は、偶然を基にした分割になってしまうかもしれない ▌コードで実現したいところの機能の特徴を捉えておくことで、偶然を排した意思決定ができそう 8

Slide 9

Slide 9 text

kintoneのアプリ ▌業務の数だけ作成 ⚫案件管理、顧客管理、… ▌データを蓄積する簡単な“データベース” ▌データを蓄積する以外にも ⚫ワークフロー、アクセス権、通知、など 9

Slide 10

Slide 10 text

アプリ機能とアプリ設定機能 ▌アプリ機能: “データベース“にあるデータ (レコード) の表示、編集 10

Slide 11

Slide 11 text

アプリ機能とアプリ設定機能 ▌アプリ設定機能: “データベース”の設定 11 フィールドの一覧 登録するデータの形を設定 (フィールドの設定)

Slide 12

Slide 12 text

コード分割で目指す構成 UpdateController GetController FieldService FieldRepository com.kintone.appsettings AppData FieldData CategoryData 共有するデータ ArchUnitで 依存を禁止 アプリ機能 アプリ設定機能 12 com.kintone.app RecordData RecordService RecordRepository GetRecordController

Slide 13

Slide 13 text

コード分割で目指す構成 RecordData RecordService RecordRepository GetRecordController AppData FieldData CategoryData 共有するデータ ArchUnitで 依存を禁止 UpdateController GetController FieldService FieldRepository com.kintone.appsettings com.kintone.app 機能の性質上関わらないならば、 コードでも関わらせない 13 アプリ機能 アプリ設定機能 機能に対応するパッケージを作成 機能に属する全コードをパッケージに配置

Slide 14

Slide 14 text

コード分割で目指す構成 AppData FieldData CategoryData 共有するデータ ArchUnitで 依存を禁止 UpdateController GetController FieldService FieldRepository com.kintone.appsettings 機能間で共有が必要なデータ 現状は共有を許容 14 RecordData RecordService RecordRepository GetRecordController com.kintone.app アプリ機能 アプリ設定機能

Slide 15

Slide 15 text

依存の管理 ▌ArchUnit ⚫JUnit等で使うライブラリ ⚫パッケージ間の依存関係をチェックすることができる ▌依存を監視する仕組みを整備 ⚫ArchUnitを使って依存のルールを定義し、単体テストに組み込む ⚫意図しない依存が入り込めばテストが落ちる 15

Slide 16

Slide 16 text

なぜ機能単位でのコード分割をするのか? ▌機能に対応したパッケージ分けは、既にある程度されていた ⚫これをさらに推し進めることは次のステップとして自然 ▌新規機能の開発と平行して進められる ⚫分割の前後でコードの中身はほとんど変わらない ⚫機能開発しているチームが「なにこのコード!?」と驚かない ▌分割の基準を一から考える必要が無かった ⚫Product Manager (PM) のメンタルモデルをそのまま適用できそうだった 16

Slide 17

Slide 17 text

コード分割の基準: PMのメンタルモデル ▌メンタルモデル: どのような部分から構成され、どう関係し合ってるかという捉え方 ⚫PMのメンタルモデルがコード分割にも違和感なく適用できるものだった アプリを使う人 (現場で 業務をする人) アプリを作る人 (情シス・業務リーダー) レコードを表示する フィールドを変更する kintone アプリ機能 アプリ設定機能 17 com.kintone.app com.kintone.appsettings

Slide 18

Slide 18 text

PMのメンタルモデルはどのようにして得られた? ▌kintoneの機能提供を継続してきたことから ⚫リリースして10年の間、いろいろな立場の人に、新規機能の提供を継続 ⚫共通の機能をいろいろな立場の人に提供する中で、一貫したモデルが形成 ▌アプリとアプリ設定という捉え方も、このような経緯の中から ⚫もともとはあまり区別せず開発されてきた ⚫けれども、分けたほうが考えやすい 18

Slide 19

Slide 19 text

コード分割トライアルプロジェクト 19

Slide 20

Slide 20 text

コード分割はなかなか進まず ▌仕組みは整えたものの、2~3人のメンバが週に1時間ほど集まって行うのみ ⚫分割するのも小さな機能 ▌チームのメンバにヒアリング ⚫やり方がイメージできてなくて手が出しづらい ⚫優先順位が下がりがち 20

Slide 21

Slide 21 text

プロジェクト ▌普段の開発では難しい大きめの改善やPoCなどを行う仕組み ⚫目的やゴール、参加メンバと期間を説明し、承認を受ける ⚫人と時間を確保できるので、集中的に取り組むことができる ▌コード分割が抱えていた課題の解消に使えそう ⚫時間が少ない、分割対象が小さめ → 時間を確保できる ⚫やり方がイメージできない → 参加メンバと分割に集中的に取り組むことができる 21

Slide 22

Slide 22 text

コード分割トライアルプロジェクト ▌目的・ゴール ⚫分割について学習し、プロジェクト終了後でも参加メンバが分割を行えるようになる ⚫(学習が目的であり、分割すること自体は目的ではない) ▌分割対象: アプリ設定機能 ⚫kintoneのコア機能でありかつ、ある程度複雑 ⚫機能的に他から依存が無いので、他の機能よりかは手間が少なくなりそう ⚫コードの分量的には小さくはなくちょうどよいサイズ → コード分割の経験を積むにはちょうどよさそう 22

Slide 23

Slide 23 text

コード分割トライアルプロジェクト ▌参加者 ⚫コード分割に興味があるメンバを募集 ⚫アプリやアプリ設定を担当している開発メンバ ▌期間 ⚫2022年11月~2023年1月 ⚫週4時間の分割作業を3ヶ月 23

Slide 24

Slide 24 text

分割の進め方: モブプログラミング ▌コア機能の分割はこれが初めてだったので、いろいろ手探り状態 ⚫一気に分割することは難しいため、どこから始めてどのあたりを目指すか ⚫分割後の命名について (巨大なサービスクラスの分割時など) ▌モブプログラミングだとやりやすかった ⚫同じコードを見ながら一緒に考える → 認識がブレづらい、分割に集中できる ⚫100~200ファイルものdiffが出るので、PRをレビューするやり方だと大変 ▌知見の共有や学習にも効果的 ⚫知っている人は序盤だけリードして終盤は観察、のような工夫 24

Slide 25

Slide 25 text

プロジェクトを終えて ▌フィールド設定、アクセス権設定など20ある項目のうち、5つ分を分割 ▌コア機能を分割をしてみて、コードの可読性が向上しているのを実感 ⚫コードの肥大化・複雑化に対してコード分割は効果的という確信が深まる ▌プロジェクト解散後、参加メンバは各自の担当領域の分割を推進中 ▌ゴールは達成されたといってもよさそう 25

Slide 26

Slide 26 text

コード分割を通して見えてきた複雑性 26

Slide 27

Slide 27 text

APIトークンの分割 ▌APIトークン ⚫REST APIを呼び出す際に、リクエストヘッダに付与する ⚫ユーザーを使わずにレコードの取得や更新ができる ▌アプリ設定のAPIトークン設定画面で設定する 27

Slide 28

Slide 28 text

分割前のAPIトークンの仕組み ApiTokenService - getToken - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する 設定を 変更する 28 APIトークンを表現する データクラス APIトークンに関する処理 を担当するサービスクラス アプリを 作る人 アプリを 使う人

Slide 29

Slide 29 text

分割前のAPIトークンの仕組み ApiTokenService - getToken - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する 29 listで設定を取得 updateで変更 アプリを 作る人 アプリを 使う人 設定を 変更する

Slide 30

Slide 30 text

分割前のAPIトークンの仕組み ApiTokenService - getToken - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する 30 getTokenで取得 canViewを確認 アプリを 作る人 アプリを 使う人 設定を 変更する

Slide 31

Slide 31 text

このまま分割することは不可能 ApiTokenService - getToken() - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する com.kintone.appsettings 31 getTokenのある appsettingsに依存 アプリを 作る人 アプリを 使う人 ArchUnitで 依存を禁止 設定を 変更する

Slide 32

Slide 32 text

getTokenがappsettingsにあるのが変? ApiTokenService - getToken() - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する com.kintone.appsettings 32 - list, update, ApiToken → 変更する際に利用される - getToken → 変更することとは関係ない アプリを作る人が設定を 変更する際に利用されるコード アプリを 作る人 アプリを 使う人 ArchUnitで 依存を禁止 設定を 変更する

Slide 33

Slide 33 text

分割後のAPIトークンの仕組み (サービス、データクラスを分割) ApiTokenService - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する com.kintone.appsettings ApiTokenRightService - getToken() ApiTokenRight - long appId - bool canView, canAdd Impl 33 アプリを 作る人 アプリを 使う人 設定を 変更する

Slide 34

Slide 34 text

Dependency Injectionによって実装への依存を避ける ApiTokenService - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する com.kintone.appsettings ApiTokenRightService - getToken() ApiTokenRight - long appId - bool canView, canAdd Impl 34 DIを使うことで appsettingsへの依存を回避 アプリ設定のテーブルを参照 するため、appsettings内 アプリを 作る人 アプリを 使う人 設定を 変更する

Slide 35

Slide 35 text

データクラスの可読性の向上 35 ApiTokenService - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する com.kintone.appsettings ApiTokenRightService - getToken() ApiTokenRight - long appId - bool canView, canAdd Impl アプリを作る人向けの変更 がしやすくなった (memoの追加など) 不要なプロパティに依存 しなくなった アプリを 作る人 アプリを 使う人 設定を 変更する

Slide 36

Slide 36 text

サービスクラスの可読性の向上 36 ApiTokenService - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する com.kintone.appsettings ApiTokenRightService - getToken() ApiTokenRight - long appId - bool canView, canAdd Impl アプリを 作る人 アプリを 使う人 機能と実装の対応が取れている - できること → ApiTokenService - 設定の表示 → #list - 設定の変更 → #update 設定を 変更する

Slide 37

Slide 37 text

サービスクラスの可読性の向上 37 ApiTokenService - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する com.kintone.appsettings ApiTokenRightService - getToken() ApiTokenRight - long appId - bool canView, canAdd Impl getTokenとlistが分かれた - 同じApiTokenを返しているので、共通化できそう にすら思える - 事前のアクセス権チェックの有無などが違うため、 実は大きく異なる アプリを 作る人 アプリを 使う人 設定を 変更する

Slide 38

Slide 38 text

コードを複雑にしていたもの ▌使う側と作る側で同じサービスクラス、データクラスを使用していたこと ▌APIトークン以外でも同様の現象が見られた ApiTokenService - getToken - list() - update() ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する 作る人のための情報、 使う人には不要 実行するのが使う人か作る人かで、 必要な処理や情報が異なる → serviceの実装がちぐはぐに見える 38 アプリを 作る人 アプリを 使う人 設定を 変更する

Slide 39

Slide 39 text

使う側と作る側で分けると、素直な構成になりそう ▌使う側から呼ばれるか、作る側からかで、必要とされる情報や処理が変わる ⚫パッケージやクラス分割することで、可読性の向上が期待できる ▌APIトークンの分割では効果を実感 ⚫不要なプロパティのロード、ちぐはぐな実装の問題の解消 ⚫機能と実装の対応をつけやすくなる ▌機能追加もしやすくなる ⚫作る人がより作りやすくする機能追加は、使う側に関係なく生じる ⚫分けておくことで、影響範囲を抑えるのが簡単になる 39

Slide 40

Slide 40 text

使う側と作る側で分けるための実装 ▌使う側と作る側でどう依存をコントロールするかは、やり方はいろいろありそう ⚫今回はDIを利用したが、もっとよい分け方もあるはず ▌機能的な要素だけでなく、技術的な要素も関わってきそう ⚫DIが使えるか ⚫DBまで分割したらどうか ▌技術的に何を選べば機能的にはどう変わるか、など、まだまだ未知 ⚫いろいろと探求しがいがありそう 40

Slide 41

Slide 41 text

今後の課題とまとめ 41

Slide 42

Slide 42 text

今後の課題 ▌コードを分割しきる ⚫現在、参加していたメンバは自チームに戻って分割を開始 ⚫共通部分の分析、整理 ▌さらなる分割 ⚫DB分割、サービス分割 ※ ユーザーやPMのメンタルモデルを気に留めるのは忘れない 42

Slide 43

Slide 43 text

まとめ ▌コードの複雑化と開発効率の低下が課題 ▌コード分割の仕組みを整えた ⚫分割の基準はPMのメンタルモデル ▌コード分割の学習のため、アプリ設定を分割するプロジェクトに挑戦 ⚫プロジェクト終了後、参加メンバは分割を開始 ⚫可読性の向上を実感、複雑化の要因らしきものも発見 ▌今後は更なる分割を目指す 43