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
12
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
560
コード分割から始める複雑さの解消に向けたkintoneのアーキテクチャ改善 / アーキテクチャConference 2025
hirokunimaeta
0
16k
Other Decks in Programming
See All in Programming
Agentic UI
manfredsteyer
PRO
0
160
Lessons from Spec-Driven Development
simas
PRO
0
210
AIで効率化できた業務・日常
ochtum
0
140
Signal Forms: Details & Live Coding @enterJS 2026 in Mannheim
manfredsteyer
PRO
0
140
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
170
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
130
TAKTでAI駆動開発の品質を設計する
j5ik2o
7
1.3k
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.6k
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
5.1k
Claspは野良GASの夢をみるか
takter00
0
190
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
270
DynamoDBには集計系のクエリがないけどなんとかしたい
musan
1
140
Featured
See All Featured
Odyssey Design
rkendrick25
PRO
2
700
Six Lessons from altMBA
skipperchong
29
4.3k
A Soul's Torment
seathinner
6
2.9k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
940
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
1
200
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
200
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
270
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
440
So, you think you're a good person
axbom
PRO
2
2.1k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
30k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.9k
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