Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
複雑性に立ち向かうためのサーバーサイドコード分割 / JJUG CCC 2023 Spring
Search
hirokuni-maeta
May 30, 2026
Programming
18
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
複雑性に立ち向かうためのサーバーサイドコード分割 / JJUG CCC 2023 Spring
hirokuni-maeta
May 30, 2026
More Decks by hirokuni-maeta
See All by hirokuni-maeta
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
680
コード分割から始める複雑さの解消に向けたkintoneのアーキテクチャ改善 / アーキテクチャConference 2025
hirokunimaeta
0
16k
Other Decks in Programming
See All in Programming
壊れたパーサから始める関数型設計と構成的なパーサ #fp_matsuri
raiga0310
2
180
Dataformのリポジトリを立ち上げるときにまずやること / dataform-day0-2026
snhryt
0
210
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
4
1k
技術記事、 専門家としてのプログラマ、 言語化
mizchi
14
7.3k
AI時代の仕事技芸論〜ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ(スクフェス仙台 2026バージョン)
kuranuki
0
460
ローカルLLMでどこまでコードが書けるか -拡張版 / How much code can be written on a local LLM Extended
kishida
12
4.7k
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
270
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
460
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
350
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
390
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
240
TAKTでAI駆動開発の品質を設計する
j5ik2o
7
1.7k
Featured
See All Featured
We Have a Design System, Now What?
morganepeng
55
8.2k
Tell your own story through comics
letsgokoyo
1
990
Testing 201, or: Great Expectations
jmmastey
46
8.2k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.8k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2k
エンジニアに許された特別な時間の終わり
watany
107
250k
Docker and Python
trallard
47
3.9k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.7k
The Cult of Friendly URLs
andyhume
79
6.9k
Evolving SEO for Evolving Search Engines
ryanjones
0
240
Transcript
複雑性に立ち向かうための サーバーサイドコード分割 前田 浩邦 1
自己紹介 ▌前田 浩邦 / Hirokuni Maeta ▌2014年4月 サイボウズ入社 ▌kintone開発チーム /
ソフトウェアエンジニア ▌バックエンドとフロントエンド ▌最近はkintoneのインフラ移行プロジェクトに参加 2
kintone ▌業務システムを自分たちで作成 ⚫案件管理、顧客管理、… ▌様々なアクセス権、ワークフロー ▌REST APIを使った他システムとの連携 3
目次 ▌コード分割とは ▌コード分割トライアルプロジェクト ▌コード分割を通して見えてきた複雑性 ▌今後の課題とまとめ 4
コード分割とは 5
背景 ▌肥大化・複雑化するコード ⚫リリースして10年経過 ⚫src/main/java下のファイル総行数 ≧ 35万 ▌開発効率の低下 ⚫どうリファクタリングすればいいか分からない、コードの可読性を上げづらい ⚫影響範囲の確認が難しい ⚫覚えることが多く、新メンバーがキャッチアップしづらい
6
コード分割 ▌コードを以下のような状態にすること ⚫機能ごとにパッケージに分割 ⚫機能間で関わらないなら、コード間も関わらない、依存しない ▌機能に沿っていて、独立して理解可能なコードの塊を増やしていくことで、開発の効率化を 目指す 7
機能の特徴を捉えた上で分割する ▌コードだけを基に分割しても、複雑性を解消できるとは考えづらい ⚫例えば、同じクラスを返り値にするメソッド、ということで共通化されるかもしれない ⚫その実装で動くからという理由だけで、偶然同じになってしまった可能性もある ⚫つまり、コードだけを基にした分割は、偶然を基にした分割になってしまうかもしれない ▌コードで実現したいところの機能の特徴を捉えておくことで、偶然を排した意思決定ができそう 8
kintoneのアプリ ▌業務の数だけ作成 ⚫案件管理、顧客管理、… ▌データを蓄積する簡単な“データベース” ▌データを蓄積する以外にも ⚫ワークフロー、アクセス権、通知、など 9
アプリ機能とアプリ設定機能 ▌アプリ機能: “データベース“にあるデータ (レコード) の表示、編集 10
アプリ機能とアプリ設定機能 ▌アプリ設定機能: “データベース”の設定 11 フィールドの一覧 登録するデータの形を設定 (フィールドの設定)
コード分割で目指す構成 UpdateController GetController FieldService FieldRepository com.kintone.appsettings AppData FieldData CategoryData 共有するデータ
ArchUnitで 依存を禁止 アプリ機能 アプリ設定機能 12 com.kintone.app RecordData RecordService RecordRepository GetRecordController
コード分割で目指す構成 RecordData RecordService RecordRepository GetRecordController AppData FieldData CategoryData 共有するデータ ArchUnitで
依存を禁止 UpdateController GetController FieldService FieldRepository com.kintone.appsettings com.kintone.app 機能の性質上関わらないならば、 コードでも関わらせない 13 アプリ機能 アプリ設定機能 機能に対応するパッケージを作成 機能に属する全コードをパッケージに配置
コード分割で目指す構成 AppData FieldData CategoryData 共有するデータ ArchUnitで 依存を禁止 UpdateController GetController FieldService
FieldRepository com.kintone.appsettings 機能間で共有が必要なデータ 現状は共有を許容 14 RecordData RecordService RecordRepository GetRecordController com.kintone.app アプリ機能 アプリ設定機能
依存の管理 ▌ArchUnit ⚫JUnit等で使うライブラリ ⚫パッケージ間の依存関係をチェックすることができる ▌依存を監視する仕組みを整備 ⚫ArchUnitを使って依存のルールを定義し、単体テストに組み込む ⚫意図しない依存が入り込めばテストが落ちる 15
なぜ機能単位でのコード分割をするのか? ▌機能に対応したパッケージ分けは、既にある程度されていた ⚫これをさらに推し進めることは次のステップとして自然 ▌新規機能の開発と平行して進められる ⚫分割の前後でコードの中身はほとんど変わらない ⚫機能開発しているチームが「なにこのコード!?」と驚かない ▌分割の基準を一から考える必要が無かった ⚫Product Manager (PM)
のメンタルモデルをそのまま適用できそうだった 16
コード分割の基準: PMのメンタルモデル ▌メンタルモデル: どのような部分から構成され、どう関係し合ってるかという捉え方 ⚫PMのメンタルモデルがコード分割にも違和感なく適用できるものだった アプリを使う人 (現場で 業務をする人) アプリを作る人 (情シス・業務リーダー)
レコードを表示する フィールドを変更する kintone アプリ機能 アプリ設定機能 17 com.kintone.app com.kintone.appsettings
PMのメンタルモデルはどのようにして得られた? ▌kintoneの機能提供を継続してきたことから ⚫リリースして10年の間、いろいろな立場の人に、新規機能の提供を継続 ⚫共通の機能をいろいろな立場の人に提供する中で、一貫したモデルが形成 ▌アプリとアプリ設定という捉え方も、このような経緯の中から ⚫もともとはあまり区別せず開発されてきた ⚫けれども、分けたほうが考えやすい 18
コード分割トライアルプロジェクト 19
コード分割はなかなか進まず ▌仕組みは整えたものの、2~3人のメンバが週に1時間ほど集まって行うのみ ⚫分割するのも小さな機能 ▌チームのメンバにヒアリング ⚫やり方がイメージできてなくて手が出しづらい ⚫優先順位が下がりがち 20
プロジェクト ▌普段の開発では難しい大きめの改善やPoCなどを行う仕組み ⚫目的やゴール、参加メンバと期間を説明し、承認を受ける ⚫人と時間を確保できるので、集中的に取り組むことができる ▌コード分割が抱えていた課題の解消に使えそう ⚫時間が少ない、分割対象が小さめ → 時間を確保できる ⚫やり方がイメージできない →
参加メンバと分割に集中的に取り組むことができる 21
コード分割トライアルプロジェクト ▌目的・ゴール ⚫分割について学習し、プロジェクト終了後でも参加メンバが分割を行えるようになる ⚫(学習が目的であり、分割すること自体は目的ではない) ▌分割対象: アプリ設定機能 ⚫kintoneのコア機能でありかつ、ある程度複雑 ⚫機能的に他から依存が無いので、他の機能よりかは手間が少なくなりそう ⚫コードの分量的には小さくはなくちょうどよいサイズ →
コード分割の経験を積むにはちょうどよさそう 22
コード分割トライアルプロジェクト ▌参加者 ⚫コード分割に興味があるメンバを募集 ⚫アプリやアプリ設定を担当している開発メンバ ▌期間 ⚫2022年11月~2023年1月 ⚫週4時間の分割作業を3ヶ月 23
分割の進め方: モブプログラミング ▌コア機能の分割はこれが初めてだったので、いろいろ手探り状態 ⚫一気に分割することは難しいため、どこから始めてどのあたりを目指すか ⚫分割後の命名について (巨大なサービスクラスの分割時など) ▌モブプログラミングだとやりやすかった ⚫同じコードを見ながら一緒に考える → 認識がブレづらい、分割に集中できる
⚫100~200ファイルものdiffが出るので、PRをレビューするやり方だと大変 ▌知見の共有や学習にも効果的 ⚫知っている人は序盤だけリードして終盤は観察、のような工夫 24
プロジェクトを終えて ▌フィールド設定、アクセス権設定など20ある項目のうち、5つ分を分割 ▌コア機能を分割をしてみて、コードの可読性が向上しているのを実感 ⚫コードの肥大化・複雑化に対してコード分割は効果的という確信が深まる ▌プロジェクト解散後、参加メンバは各自の担当領域の分割を推進中 ▌ゴールは達成されたといってもよさそう 25
コード分割を通して見えてきた複雑性 26
APIトークンの分割 ▌APIトークン ⚫REST APIを呼び出す際に、リクエストヘッダに付与する ⚫ユーザーを使わずにレコードの取得や更新ができる ▌アプリ設定のAPIトークン設定画面で設定する 27
分割前のAPIトークンの仕組み ApiTokenService - getToken - list() - update() ApiToken -
long appId - bool canView, canAdd - String memo レコードを 取得する 設定を 変更する 28 APIトークンを表現する データクラス APIトークンに関する処理 を担当するサービスクラス アプリを 作る人 アプリを 使う人
分割前のAPIトークンの仕組み ApiTokenService - getToken - list() - update() ApiToken -
long appId - bool canView, canAdd - String memo レコードを 取得する 29 listで設定を取得 updateで変更 アプリを 作る人 アプリを 使う人 設定を 変更する
分割前のAPIトークンの仕組み ApiTokenService - getToken - list() - update() ApiToken -
long appId - bool canView, canAdd - String memo レコードを 取得する 30 getTokenで取得 canViewを確認 アプリを 作る人 アプリを 使う人 設定を 変更する
このまま分割することは不可能 ApiTokenService - getToken() - list() - update() ApiToken -
long appId - bool canView, canAdd - String memo レコードを 取得する com.kintone.appsettings 31 getTokenのある appsettingsに依存 アプリを 作る人 アプリを 使う人 ArchUnitで 依存を禁止 設定を 変更する
getTokenがappsettingsにあるのが変? ApiTokenService - getToken() - list() - update() ApiToken -
long appId - bool canView, canAdd - String memo レコードを 取得する com.kintone.appsettings 32 - list, update, ApiToken → 変更する際に利用される - getToken → 変更することとは関係ない アプリを作る人が設定を 変更する際に利用されるコード アプリを 作る人 アプリを 使う人 ArchUnitで 依存を禁止 設定を 変更する
分割後の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 アプリを 作る人 アプリを 使う人 設定を 変更する
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内 アプリを 作る人 アプリを 使う人 設定を 変更する
データクラスの可読性の向上 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の追加など) 不要なプロパティに依存 しなくなった アプリを 作る人 アプリを 使う人 設定を 変更する
サービスクラスの可読性の向上 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 設定を 変更する
サービスクラスの可読性の向上 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を返しているので、共通化できそう にすら思える - 事前のアクセス権チェックの有無などが違うため、 実は大きく異なる アプリを 作る人 アプリを 使う人 設定を 変更する
コードを複雑にしていたもの ▌使う側と作る側で同じサービスクラス、データクラスを使用していたこと ▌APIトークン以外でも同様の現象が見られた ApiTokenService - getToken - list() - update()
ApiToken - long appId - bool canView, canAdd - String memo レコードを 取得する 作る人のための情報、 使う人には不要 実行するのが使う人か作る人かで、 必要な処理や情報が異なる → serviceの実装がちぐはぐに見える 38 アプリを 作る人 アプリを 使う人 設定を 変更する
使う側と作る側で分けると、素直な構成になりそう ▌使う側から呼ばれるか、作る側からかで、必要とされる情報や処理が変わる ⚫パッケージやクラス分割することで、可読性の向上が期待できる ▌APIトークンの分割では効果を実感 ⚫不要なプロパティのロード、ちぐはぐな実装の問題の解消 ⚫機能と実装の対応をつけやすくなる ▌機能追加もしやすくなる ⚫作る人がより作りやすくする機能追加は、使う側に関係なく生じる ⚫分けておくことで、影響範囲を抑えるのが簡単になる 39
使う側と作る側で分けるための実装 ▌使う側と作る側でどう依存をコントロールするかは、やり方はいろいろありそう ⚫今回はDIを利用したが、もっとよい分け方もあるはず ▌機能的な要素だけでなく、技術的な要素も関わってきそう ⚫DIが使えるか ⚫DBまで分割したらどうか ▌技術的に何を選べば機能的にはどう変わるか、など、まだまだ未知 ⚫いろいろと探求しがいがありそう 40
今後の課題とまとめ 41
今後の課題 ▌コードを分割しきる ⚫現在、参加していたメンバは自チームに戻って分割を開始 ⚫共通部分の分析、整理 ▌さらなる分割 ⚫DB分割、サービス分割 ※ ユーザーやPMのメンタルモデルを気に留めるのは忘れない 42
まとめ ▌コードの複雑化と開発効率の低下が課題 ▌コード分割の仕組みを整えた ⚫分割の基準はPMのメンタルモデル ▌コード分割の学習のため、アプリ設定を分割するプロジェクトに挑戦 ⚫プロジェクト終了後、参加メンバは分割を開始 ⚫可読性の向上を実感、複雑化の要因らしきものも発見 ▌今後は更なる分割を目指す 43