Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
長期運用プロダクトの開発速度を維持し続けるためのリファクタリング実践例
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Wataru
August 27, 2024
Programming
3.6k
8
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
長期運用プロダクトの開発速度を維持し続けるためのリファクタリング実践例
Wataru
August 27, 2024
Other Decks in Programming
See All in Programming
Turning Architecture into Unit Tests in the AI Era (NSSpain XIV)
steliosf
PRO
1
120
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
260
JPUG勉強会 OSSデータベースの内部構造を理解しよう(第2回)
oga5
0
290
技術的負債を組織課題として解く-増えすぎたマイクロサービスとの戦い-
reimaru
1
2.6k
ソニーのクラウド共通基盤の変遷とAI時代の開発スタイルに合わせた進化 / The Journey of Sony’s Common Cloud Platform and Its Evolution for AI-Native Development
kenjiyoneyama
0
220
re:Inventに行く前に知っておきたい現地参加のノウハウ
nokomoro3
0
340
難しいけど、読めた。- OSSの入口に立った話。
sts11142
0
130
見えないものを探る要求要件定義に必要な基本的思考 / invisible-requirement-thinking
minodriven
6
3.5k
C#の現在地 進化の歴史と、AI時代の.NET Everywhere
neuecc
5
4.8k
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
20
20k
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
530
設計次第でAIコードの読む量は減らせる / designing-for-code-reading
minodriven
33
15k
Featured
See All Featured
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
69
66k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Skip the Path - Find Your Career Trail
mkilby
1
240
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.7k
Optimising Largest Contentful Paint
csswizardry
37
4k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
YesSQL, Process and Tooling at Scale
rocio
174
15k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
46k
Being A Developer After 40
akosma
91
590k
What does AI have to do with Human Rights?
axbom
PRO
1
2.4k
The World Runs on Bad Software
bkeepers
PRO
72
12k
Amusing Abliteration
ianozsvald
1
320
Transcript
© LayerX Inc. ⻑期運⽤プロダクトの開発速度を維持し続け るためのリファクタリング実践例 2024/08/27
© LayerX Inc. 2 • バクラク事業部 請求書受取チーム • 2023年8⽉⼊社 •
バックエンドの開発が中⼼ 靴、服 旅⾏ 祭り 過去の経歴 ネイティブゲームのクライ アント開発 ニュースアプリのiOS、広 告配信システムのバックエ ンド開発 趣味 wataru ⾃⼰紹介
© LayerX Inc. 3 ⻑期運⽤しているプロダクトの直⾯している課題と、どのように向き合ったか 話すこと 実際に⾏ったリファクタリングをgolangのコードも交えて紹介 話すこと
⽬次 Agenda • プロダクト紹介 • 課題 • やったこと • まとめ
⽬次 Agenda • プロダクト紹介 • 課題 • やったこと • まとめ
© LayerX Inc. 6 バクラクシリーズの全体像 バクラクは、企業取引の前段となる「稟議の統⼀」と「債権‧債務の⼀元管理」が可能。 従業員‧経理のそれぞれが係る業務領域において、なめらかな業務連携により企業経営を加速させます。 仕訳データ 振込データ ⼊⾦データ
取引先 発注 請求 発注 請求 債権管理 債務管理 従業員 経理 ※ 開発予定の機能を含む 銀⾏ 会計ソフト 請求書 処理 経費 精算 振込 稟議 法⼈ カード 請求書 発⾏ 仕訳 (※) ⼊⾦消込 (※) 仕訳 © LayerX Inc.
⽬次 Agenda • プロダクト紹介 • 課題 • やったこと • まとめ
どんな状況? 結構あるあるな気がする
© LayerX Inc. 9 • initial commitは4年前 • 開発初期は速く出すことが重要、変化も多い •
初期のコードもまだまだ現役 • 変化の代償として負債が残るのは当然 状況 仕様が複雑 • そもそもドメインが複雑、開発者に馴染みがない領域 • 仕訳や源泉税 • 振込データ • 様々な会計ソフトとの連携 • などなど 課題 請求書受取はバクラク最初期のプロダクト
© LayerX Inc. 10 • ⼟台を⼤きくかえずに既存コードの上に実装してきた • 機能開発すると意図してない既存機能が壊れたりする ◦ 売上が⼀定あり、お客様も多くいる状況で壊すのは不味い
• 問い合わせ、インシデント対応のコストが⾼い ◦ 1⽇潰れることも • 正しい仕様がだれもわからない箇所がある • この先も⼤きな機能開発がいくつか控えているが、変更箇所や影響範囲がすぐに分からなかったり ⼤きすぎたり… 課題 課題
リファクタリング前の構造
Presentation Tier Service Tier Repository Tier Repository S3 Repository Imp
xo model REST Handler GraphQL Resolver Connect Usecase proto buf repositoryの実態はトランザクションスクリプト+DAOになっており、 操作ごとにメソッドを作成していたので請求書関連で100以上あった
© LayerX Inc. 13 • 閲覧制限(β版として⼀部のお客様に限定公開している機能) ◦ ユーザーが閲覧できる書類を制限する機能 ◦ 既存のデータフェッチしている箇所ほぼすべてに影響がありそう
◦ repository層のメソッドが多すぎて、全部書くのがキツイ • 外貨請求書対応 ◦ 多通貨対応や、⼩数対応などで様々な箇所を触る必要がありそう ◦ 影響範囲が読めない • mysql 5.7 -> 8 ◦ 安全のためunit testは書いておきたい ◦ repository層のメソッドが多すぎて、全部書くのがキツイ • GORM V1->V2化 ◦ 同上 控えていた⼤きな開発 課題
リファクタリングしたい! みんな思ってはいる
© LayerX Inc. 15 リファクタリングしたいけど • 正しい仕様がわからん ◦ そういう仕様なのか、たまたまそうなってるのか ◦
ドメイン知識も要求されるしなんか壊れそうだから触りたくない 障壁 課題 • 事業優先度の問題 ◦ 機能開発でやりたいことがたくさんある ◦ リファクタリングのビジネス上の価値は算定しづらい
リファクタリングできる?
できる(こともある)
© LayerX Inc. 18 リファクタリングしたいけど • 正しい仕様がわからん ◦ 💡PDMや関係者と相談して仕様整理から始める ◦
💡なければ⾃分で仕様書を書く気概でやる 障壁 課題 • 事業優先度の問題 ◦ 機能開発でやりたいことがたくさんある、余裕がない ◦ リファクタリングのビジネス上の価値は算定しづらい
© LayerX Inc. 19 リファクタリングしたいけど • 正しい仕様がわからん ◦ そういう仕様なのか、たまたまそうなってるのか ◦
ドメイン知識も要求されるしなんか壊れそうだから触りたくない 障壁 課題 • 事業優先度の問題 ◦ 💡 機能開発の速度は落とさなければ問題ない ◦ 💡 機能開発のためのリファクタリング
© LayerX Inc. 20 • フルリプレイスやそれくらいの規模のリファクタリングでは短期的な開発速度は落ちるしその実施 判断は難しいので今回のスコープ外 • 今回は開発期間として最低でも数週間~の状況、あまりにも短いと厳しいかも •
リファクタリングの⽬的が明確にあると良い ◦ ステークホルダーからの理解が得られやすい ▪ やる場合、やらない場合のpros/consを⾔語化して伝える ◦ キレイにしたいからという⾃⼰満⾜にならない • 開発期間の前半にリファクタリングをしておけば、機能開発の効率は⼤幅に上がると判断 ◦ リファクタパート、開発パート合わせて当初の予定通り出せそう • 機能開発と同時にはリファクタリングしない ◦ リファクタリングだけした状態でtestやQAを通しておきたい • 直接今回の機能開発に関係ないことはやらない 機能開発とセット 課題
⽬次 Agenda • プロダクト紹介 • 課題 • やったこと • まとめ
© LayerX Inc. 22 • やりたいことは⼭ほどあるが、全部やるのは厳しい • 開発速度に直結するような費⽤対効果が良いものをやる 対象を決める やったこと
© LayerX Inc. 23 • 閲覧制限(β版として⼀部のお客様に限定公開している機能) ◦ ユーザーが閲覧できる書類を制限する機能 ◦ 既存のデータフェッチしている箇所ほぼすべてに影響がありそう
◦ repository層のメソッドが多すぎて、全部書くのがキツイ • 外貨請求書対応 ◦ 多通貨対応や、⼩数対応などで様々な箇所を触る必要がありそう ◦ 影響範囲が読めない • mysql 5.7 -> 8 ◦ 安全のためunit testは書いておきたい ◦ repository層のメソッドが多すぎて、全部書くのがキツイ • GORM V1->V2化 ◦ 同上 控えていた⼤きな開発(再掲) 課題
© LayerX Inc. 24 repository層を中⼼としたリファクタすることに 複雑なドメインに⽴ち向かうためにDDDも⼀部取り⼊れる • 今後の機能開発を眺めてみると、repository層のリファクタリングが⼀番効果がありそう 対象を絞る やったこと
© LayerX Inc. 25 service層のI/O変更 • handler層から呼ばれるserviceのI/Oは変更しない • serviceレベルで、外から⾒た振る舞いに変更はない •
service層のすでに存在するunit testが通れば安⼼ やらないこと やらないこと 既存テーブルの設計変更 • 影響が⼤きすぎる • 振る舞いを変えずに変更することが難しい
© LayerX Inc. 26 機能開発に影響を与えない部分のリファクタ • 理想は全repository書き換えたいが、今回やりたい機能開発の開発速度が上がるわけではなく、⾃ ⼰満⾜になるかもしれない やらないこと やらないこと
DDDの正しさを追い求めすぎない • DDDのエッセンスは取り⼊れるが、原理主義にならない • 正しいDDDを導⼊する⽬的でリファクタリングするわけではない
© LayerX Inc. 27 repository層のリファクタ • 集約ルート単位でのやり取りに • usecaseごとに作られていたメソッドの削除 具体的には
やったこと domain層の導⼊ • 集約に対するビジネスロジックをまとめる • エンティティ、domain serviceの作成
© LayerX Inc. 28 具体的には やったこと repository層のリファクタ • 集約ルート単位でのやり取りに •
usecaseごとに作られていたメソッドの削除 domain層の導⼊ • 集約に対するビジネスロジックをまとめる • エンティティ、domain serviceの作成
© LayerX Inc. 29 repositoryの設計思想 • 集約内部の変更は必ず集約ルートを経由することで集約内を常に整合性が確保された状態にする • 集約ルートの単位でデータの取得・永続化を行う •
集約ルート:repositoryは1:1 ◦ テーブル単位ではない • 集約をまたいだ検索が必要な場合、 query serviceで書く • ビジネスロジックを持たない ◦ 指示(指定された引数)に従って CRUDするだけ • repository同士で依存しない repository層のリファクタ
© LayerX Inc. 30 repository層のリファクタ • 既存のモデルをすべて書き出し整理した • 良い集約の範囲を決めるのは難しい •
やってみて違和感がないか確認したり、試⾏錯誤が必要 • ドメインエキスパートに相談してもいいかも 集約を定義 請求書 (ルート) 請求書ファイル 請求書タグ
© LayerX Inc. 31 repository層のリファクタ • 集約ルートのエンティティをdomainパッケージに作成した • 集約ルート以外は既存の⾃動⽣成されたmodelを使⽤ 集約を定義
package domain type Invoice struct { *model.Invoice Files []*model.InvoiceFile Tags []*model.InvoiceTag } package model type InvoiceEmbedded struct { Invoice Journals []*Journal Client *Client Files []*Files … }
© LayerX Inc. 32 repository層のリファクタ • 関連テーブルを全部まとめたような巨⼤structをあちこちで使⽤しており、集約単位に分解が必要 だった • 不要な箇所でも巨⼤structを使⽤して関連テーブルをfetchしておりパフォーマンスも良くない
• 画⾯の描画に集約外の要素が必要であればpresentation層でくっつける • 既存の巨⼤モデルでは19モデルembeddingされてる箇所も 既存のstructの分解
© LayerX Inc. 33 repository層のリファクタ • 旧repositoryの⼀部、⼤量のメソッドがある • 微妙に違うusecaseに対して違うメソッドが存在し、I/Oもバラバラ •
⼤きな変更の際など、全て変更するのも、全てtestを書くのもつらい repositoryの再定義 package domain type InvoiceRepository interface { GetByID(ctx Context, id string) (*model.Invoice, error) GetFileByID(ctx Context, id string) (*model.InvoiceFile, error) UpdateForFooUseCase(ctx Context, value string) error } // 集約の一部を操作するようなrepoは削除 type InvoiceHogeRepository interface { UpdateStatus(ctx Context, status string) error }
© LayerX Inc. 34 repository層のリファクタ • 集約ルート単位でデータのやり取りをする • 基本的にはGet,GetMany,Saveのみ提供(例外はあるが) repositoryの再定義
package domain type InvoiceRepository interface { Get(ctx Context, id string) (*Invoice, error) GetMany(ctx Context, ids ...string) (*Invoice, error) Save(ctx Context, id string) error }
© LayerX Inc. 35 repository層のリファクタ • 集約外のテーブルを使⽤したい場合 • 複雑な条件の検索が必要な場合別途query serviceを作る
集約をまたぐ場合 package query type InvoiceQueryService interface { Find(ctx Context, params InvocieFindParams) (domain.Invoices, error) } // 検索条件 type InvocieFindParams { name *string status *model.InvoiceStatus }
© LayerX Inc. 36 具体的には やったこと repository層のリファクタ • 集約ルート単位でのやり取りに •
usecaseごとに作られていたメソッドの削除 domain層の導⼊ • 集約に対するビジネスロジックをまとめる • エンティティ、domain serviceの作成
© LayerX Inc. 37 • サービス層に書かれていたエンティティに関するビジネスロジックの移植 • 内部状態の変更はエンティティのメソッド経由でしか⾏わない エンティティ domain層の導⼊
© LayerX Inc. 38 エンティティ domain層の導⼊ package serivce func (s
Invoice) UpdateStatus(ctx context.Cotext, id string, status model.InvocieStatus) error { invoice := s.repo.GetByID(ctx, id) // statusを直接書き換える invoice.Status = status // 集約ルートを経由しないで書き換える invoice.Files[0].Status = hoge // 専用のメソッド return s.repo.UpdateStatus(id, status) } • 古い実装 (極端な例)
© LayerX Inc. 39 エンティティ domain層の導⼊ package serivce func (s
Invoice) UpdateStatus(ctx context.Cotext, id string, status model.InvocieStatus) error { invoice := s.repo.Get(ctx, id) invoice.UpdateStatus(ctx, status) // 必要ならvalidationとか return s.repo.Save(ctx, invoice) } • リファクタリング後の実装
© LayerX Inc. 40 • 複数のエンティティにまたがる場合や⾃然に表現できない場合 • 多⽤はしない、どうしても必要なときのみ ◦ ドメインモデル貧⾎症にならないように
• 例えば共通の採番ロジックなど、各エンティティに直接持たせるのが不⾃然な場合 domain service domain層の導⼊
© LayerX Inc. 41 • 採番ロジックの例、実際は採番テーブルを使⽤しており、interfaceがdomain層にある • 他にも、重複チェックなどが考えられる(エンティティ⾃⾝が⾃分が重複しているか知らないから) domain service
domain層の導⼊ package domain func (s InvoiceService) CreateInvoice(ctx context.Cotext, …) (*domain.Invoice error) { invoice := NewInvoice(...) num = s.numberGenerator.Generate() // なんか処理 return invoice }
リファクタ後の構造
© LayerX Inc. 43 Before(再掲) domain層の導⼊ Presentation Tier Service Tier
Repository Tier Repository S3 Repository Imp xo model REST Handler GraphQL Resolver Connect Usecase proto buf
© LayerX Inc. 44 domain層の導⼊ Presentation Tier Usecase Tier Domain
Tier Infra Tier Entity Repository Domain Service Repository Imp xo model REST Handler GraphQL Resolver Connect Usecase proto buf S3 usecase model
⽬次 Agenda • プロダクト紹介 • 課題 • やったこと • まとめ
© LayerX Inc. 46 • スコープを絞り、機能開発とセットでリファクタリングをすることで開発速度を落とさずにリファ クタリングできた ◦ 👍その後の開発では恩恵だけただで受けれる •
DDDを⼀部取り⼊れ集約を定義し、⼤量にあったrepositoryのメソッドを整理した ◦ 👍その後のrepositoryに対する変更が容易に ◦ 👍 unit testも楽 • サービス層かかれていたビジネスロジックの移植 ◦ 👍 集約ルートのエンティティ経由でしか内部状態が変更されないことが保証されるため、変 更すべき箇所が明確に。不具合対応も楽に まとめ まとめ
© LayerX Inc. 47 • リファクタリングによって、開発速度が上がったり、開発体験がよくなった実感はあるが、今回は その価値を評価まではしていない • いい⽅法があれば懇親会で教えて下さい! さいごに
まとめ