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
長期運用プロダクトの開発速度を維持し続けるためのリファクタリング実践例
Search
Wataru
August 27, 2024
Programming
3.5k
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
Dataformのリポジトリを立ち上げるときにまずやること / dataform-day0-2026
snhryt
0
150
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
570
Java × distroless で 軽量なコンテナイメージを / Java on Distroless
contour_gara
0
530
RTSPクライアントを自作してみた話
simotin13
0
580
メソッドのジェネリクスでGoの夢は広がるか? / Kyoto.go #65
utgwkk
3
690
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
4k
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
0
220
Modding RubyKaigi for Myself
yui_knk
0
920
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
540
Why Laravel apps break—Mastering the fundamentals to keep them maintainable
kentaroutakeda
1
350
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
190
エージェンティックRAGにAWSで入門しよう!
har1101
8
1.4k
Featured
See All Featured
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
2
4k
Information Architects: The Missing Link in Design Systems
soysaucechin
0
970
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
280
Darren the Foodie - Storyboard
khoart
PRO
3
3.4k
We Have a Design System, Now What?
morganepeng
55
8.2k
Principles of Awesome APIs and How to Build Them.
keavy
128
17k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
220
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
How to Think Like a Performance Engineer
csswizardry
28
2.6k
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
370
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.4k
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 • リファクタリングによって、開発速度が上がったり、開発体験がよくなった実感はあるが、今回は その価値を評価まではしていない • いい⽅法があれば懇親会で教えて下さい! さいごに
まとめ