Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ドメイン駆動設計の実践により事業の成長スピードと保守性を両立するショッピングクーポン

 ドメイン駆動設計の実践により事業の成長スピードと保守性を両立するショッピングクーポン

ドメイン駆動設計の実践により事業の成長スピードと保守性を両立するショッピングクーポン

More Decks by LINEヤフーTech (LY Corporation Tech)

Other Decks in Technology

Transcript

  1. © LY Corporation Public 山口 直人 LINEヤフー株式会社 コマースカンパニー ショッピング統括本部 プロダクション2本部

    デジタルマーケティング技術部 部長 3 2013 ヤフー株式会社新卒入社 様々なBEシステムの刷新を担当 2016 オフショア開発(ベトナム駐在) 2018 クーポンシステムチームのリーダーへ昇格 (ドメインエキスパート) 2022 デジタルマーケティング技術部長へ昇格
  2. © LY Corporation Public 塩川 賢太郎 LINEヤフー株式会社 コマースカンパニー ショッピング統括本 部

    プロダクション2本部 デジタルマーケティング技術部 インセ ンティブ基盤技術1チーム 4 2019〜 SIer系の企業に新卒入社 自社サービスのアプリ基盤(frontend,backend)を担当 2022〜 ヤフー株式会社に中途入社 ヤフーショッピングのインセンティブ基盤を担当 重要案件のTLを担当
  3. © LY Corporation Public 6 クーポンの自由度 クーポン 注文個 数条件 注文金

    額条件 スタン プカー ド 購入完 了画面 決済方 法指定 タイム セール 併用可 併用不 可 商品指定 ユーザ指 定 モール クーポン ストア クーポン 顧客ス テージ モール 内全品 ストア 内全品 商品タ グ指定 商品 コード 指定 JAN コード 指定 ブラン ド指定 カテゴ リー指 定 プレミ アム会 員 新規 ユーザ 通常 モール CRMクー ポン (パーソナ ライズ) STORE ’s Roo (パーソ ナライ ズ) ストア クリエ イター Pro 新規プ レミア ム会員 ユーザ ストア さまざまな購入体験 多様な購買施策
  4. © LY Corporation Public 7 クーポンの自由度 ① モールクーポン ② カテゴリ指定

    ③ 対象ストア指定 ④ 注文金額条件 ① モールクーポン ② モール全品指定 ③ 新規ユーザ ④ 注文金額条件 ① ストアクーポン ② ストア全品指定 ③ プレミアム会員
  5. © LY Corporation Public 10 コミュニケーション面 何がコストになっていたか • コミュニケーション面 •

    言葉の違いから認識の違い、コミュニケーションコストの増 • マーケ⇔技術で話すときに言葉の変換したり、、 • システム面 • 1クラスに全ての処理が書いてある • 処理のシーケンスが複雑で見通しが悪い • 仕様の複雑性 = システムの複雑性でない • 仕様の複雑性 < システムの複雑性となっている
  6. © LY Corporation Public 12 レガシーシステムからの脱却 当時の背景 言語 PHP 5.7

    FW 独自FW アーキテクト モノリシック コンピューティング IaaS ライブラリ 社内製 言語 java FW Spring Boot アーキテクト マイクロサービス コンピューティング PaaS(CaaS) ライブラリ OSS
  7. © LY Corporation Public 13 事前にやったこと コード設計について • 既存コードの可視化 •

    各アプリケーションでどのような開発が多いかの理解 • DDDについての学習 • 講師を招いてのハンズオンセミナー
  8. © LY Corporation Public 14 コンセプトや意識したこと コード設計について • 配属直後の新卒がすぐに理解できる容易性 •

    単純明瞭な処理フローの設計 • クラス名、変数名などを業務用語に合わせる
  9. © LY Corporation Public 15 当時の体制 コード開発について • ペアプロ(ドメインエキスパートと若手エンジニア) •

    一旦書いてみる • 書いては捨て、書いては捨てを繰り返す ドメインエキスパート 若手エンジニア
  10. © LY Corporation Public 17 参考文献 • 現場で役立つシステム設計の原則 変更を楽で安全にするオブジェクト指向の 実践技法

    増田亨/著 • エリック・エヴァンスのドメイン駆動設計 ソフトウェアの核心にある複雑さ に立ち向かう (IT Architects’ Archive ソフトウェア 開発の実践) エリック・エヴァンス/著 今関剛/監訳 和智右桂/訳 牧野 祐子/訳 • 実践ドメイン駆動設計 エリック・エヴァンスが確立した理論を実際の設計に 応用する (Object Oriented SELECTION) Vaug hn Vernon/著 高木正弘/訳
  11. © LY Corporation Public 20 表示における関心事 を扱う • ルーティング •

    HTTPリクエスト レスポンス • バリデーション • UI ドメインオブ ジェクトを利用 してソフトウェ アが行うべき仕 事を表現する 業務ロジックや業務 ルールを表現する データの永続化処理や外部 サービスとのやりとりを行う • DB • REST API • MQ • gRPC レイヤードアーキテクチャによる関心の分離でドメインロジックを疎結合にする Domain(業務ロジック)は 他のどの層にも依存していない
  12. © LY Corporation Public 21 ドメインモデルを部品として成熟させることで高凝集性を獲得する ドメインモデルにロジックを寄せることでデータとロジックを一体化させる DDD実践前 • ドメインエンティティ的なものは存在してい

    たが、あくまでロジックがないデータの入れ 物(DTO)だった • サービスクラスは存在していたが、業務ロ ジックはそこに凝集し、肥大化していた 実装方針 1. 分析モデルと対応するドメインモデルクラスをつくる i. 業務知識とソースコードを結びつける 2. そのモデルのデータを使って、ユースケースを満たすために必要なロジックを実装する i. データとロジックがモデルの中で共存する 3. 実装したロジックをできるだけ小さく分解する i. 部品が再利用性を獲得する 4. 実装できなかったドメインロジックはドメインサービスへ実装することで妥協する 5. 再びコードを眺めてみて、ドメインモデルに寄せられるロジックを探して移してみる 6. 3.~5.を繰り返す ドメインモデルのあるべき姿 ✓ データとロジックを一体にするべき ✓ 業務ロジックと扱うデータは同じクラ スにあるほうがよい ✓ ドメインモデルを部品として成熟させるべき ✓ ドメインモデルに業務ロジックが凝集 されるべき メリット ✓ 単体テストが実装しやすくなる ✓ メソッドあたりの責務が小さくなるため、テストすべき関心の対象も小さくなる ✓ ロジックの凝集性があがる ✓ データとロジックが一体になる ✓ 実装者は「今作ろうとしている部品はどのモデルに実装するべきか」ということ を考えるようになる ✓ 実装スピードが上がる ✓ 小さな部品を作るという意識は、エンジニアの実装タスクを分解する作業 ✓ 人間はタスクが分解されると作業の見通しが良くなる
  13. © LY Corporation Public 22 ドメインモデルを部品として成熟させることで高凝集性を獲得する 副作用のない関数にすることで 「状態」という悩みを払拭する import lombok.Builder;

    @Builder public class Coupons { List<Coupon> couponList; /** * 変更不可なコレクションを返す * @return List<Line> */ public List<Coupon> asList() { if(couponList == null) { return Collections.emptyList(); } // addやremoveが利用できなくなる return Collections.unmodifiableList(couponList); } /** * クーポンの追加(副作用なし) * @param coupon * @return Coupons */ public Coupons addCoupon(Coupon coupon)) { // Listオブジェクトを新たに生成し直す List<Coupon> couponList = new ArrayList<>(this.couponList); couponList.add(coupon); // Couponsモデルも新たに生成し直す return Coupons.builder().couponList(couponList).build(); } } 例: コレクションに要素を追加する Point 1. コレクションを操作するメソッドを Couponsに実装する 2. コレクションの参照をそのまま外部に提供 するのではなく変更不可にして返す 3. コレクションの状態を変える場合は新たに オブジェクトを生成し直す
  14. © LY Corporation Public 23 ドメインモデルを部品として成熟させることで高凝集性を獲得する 副作用のない関数にすることで 「状態」という悩みを払拭する import lombok.Builder;

    // toBuilderを使えるようにする @Builder(toBuilder = true) public class Coupon { CouponId couponId; String title; String description; ... Integer useCount; ... public Coupon updateUseCount(int useCount) { // toBuilderによってオブジェクトからBuilderを生成する return this.toBuilder() .useCount(useCount) .build(); } } 例: あるオブジェクトの一部の状態を 変更する Point 1. オブジェクトの一部の状態を変更したい場 合は、その部分を再指定したオブジェクト を新たに生成する
  15. © LY Corporation Public 24 ドメインモデルを部品として成熟させることで高凝集性を獲得する 閉じた操作にすることで 部品を組み立てやすくする public class

    Lines { ...... public Lines simulate(Coupons storeAllCoupons, Coupons targetItemCoupons, Coupons postageCoupons) { List<Line> lineList = this.asList() // 各商品ラインを数量1の商品ラインに分割する .splitToSingleQuantityLines() // 計算の前処理として商品単価でソートする .sortByItemPrice() // ストア全品クーポンの適用をシミュレートする .tryToApplyStoreAllCoupons(storeAllCoupons) // 商品指定クーポンの適用をシミュレートする .tryToApplyTargetItemCoupons(targetItemCoupons) // 分割したラインをまとめる .summarize() // クーポン適用されたラインを優先的にソートする .sortByCouponApplyState() // 送料値引きクーポンの適用をシミュレートする .tryToApplyPostageCoupon(basket, postageCoupons); return Lines.builder().lineList(lineList).build(); } } 例: 商品ラインにクーポンの組み合わせ を適用させ計算を行う Point 1. 「閉じた操作」 i. あるドメインモデルのメソッドの戻り値 がそのドメインモデル自身になること 2. メソッドチェーンによって流れるように実装 i. コードが仕様書のように流暢に読める
  16. © LY Corporation Public 25 その他に採用したデザインパターン シナリオクラスによって ユースケースの流れを整理する public class

    CandidateScenario { ...... public Coupons candidate(User user, Basket basket) { // 1. ドメインサービスの呼び出し CompletableFuture<Coupons> futureCoupons = couponService.fetch(user, basket); CompletableFuture<Basket> futureBasket = basketService.complete(basket); CompletableFuture<User> futureUser = userService.complete(user); // 2. 三者のドメインモデルがそろうまで待機 CompletableFuture.allOf(futureCoupons, futureUser, futureBasket).join(); // 3. ドメインモデルを取得 Coupons coupons = futureCoupons.join(); Basket completedBasket = futureBasket.join(); User completedUser = futureUser.join(); // 4. パーソナライズ判定による判定を行う(マイクロサービスへ) Judgements judgements = judgementService.judge(coupons, completedBasket, completedUser); // 5. 「適用可能なクーポン一覧」という集約を生成する CandidatesAggregate aggregate = CandidatesAggregateFactroy.create(coupons, completedBasket, completedUser, judgements); return aggregate; } } Point 1. 複数ドメインサービスやモデルファクトリーの 呼び出し処理 2. 非同期処理によるフロー制御 メリット ✓ Controllerクラスがビジネスロジックの呼び出し処 理でファットになることを防げる ✓ ビジネスロジックの順序性が明確化され、実装の 見通しが良くなる ✓ 新しいビジネスロジックをどこに挿入すれ ばよいか ✓ パフォーマンス由来でビジネスロジックの 順序変更をしたい
  17. © LY Corporation Public 26 その他に採用したデザインパターン Enumの多態性によって ロジックの分岐を整理する // 1.

    商品ターゲティング種別を表すEnumを定義する public enum ItemTargetStrategy { // 2. ストア内全品対象 STORE_ALL { @Override public List<Line> getTargetLineList(Coupon coupon, Basket basket) { return basket.asLineList(); } }, // 3. 商品コード指定 ITEM_CODE { @Override public List<Line> getTargetLineList(Coupon coupon, Basket basket) { // 商品コードリストに含まれているものを抽出 return basket.asLineList() .stream() .filter(line -> coupon.getTargetItemCodeList() .contains(line.getItem().getItemCode())) .collect(Collectors.toList()); } }, ...... // 5. 「適用対象となる商品一覧を取得する」という処理の抽象メソッドを定義 public abstract List<Line> getTargetLineList(Coupon coupon, Basket basket); ...... } Point 1. 各列挙子では、5.で定義した抽象メソッドを オーバーライドすることで「分岐」を表現する メリット ✓ ネストされたif/switch分岐の排除による可読性の 向上 ✓ 分岐一つ一つのロジックに対するテストコードの 書きやすさ ✓ 分岐の追加・修正・削除に対する変更容易性の向 上
  18. © LY Corporation Public 27 結果:DDDによってビジネス要求への追従スピードは爆速になった • チームの誰もがコア部分への改修に取り組めるようになった ソースコードと業務知識が一体化 •

    開発要望に対して、受け入れテストなど含めて約1カ月程度で行えるように なった 開発スピードの向上 • ソースコードの見通しがよくなり、バグの混入が減少した 事故発生率の改善
  19. © LY Corporation Public 29 クーポンの自由度 クーポン 注文個 数条件 注文金

    額条件 スタン プカー ド 購入完 了画面 決済方 法指定 タイム セール 併用可 併用不 可 商品指定 ユーザ指 定 モール クーポン ストア クーポン 顧客ス テージ モール 内全品 ストア 内全品 商品タ グ指定 商品 コード 指定 JAN コード 指定 ブラン ド指定 カテゴ リー指 定 プレミ アム会 員 新規 ユーザ 通常 モール CRMクー ポン (パーソナ ライズ) STORE ’s Roo (パーソ ナライ ズ) ストア クリエ イター Pro 新規プ レミア ム会員
  20. © LY Corporation Public 30 クーポンの自由度 クーポン 注文個 数条件 注文金

    額条件 スタン プカー ド 購入完 了画面 決済方 法指定 タイム セール 併用可 併用不 可 商品指定 ユーザ指 定 モール クーポン ストア クーポン 顧客ス テージ モール 内全品 ストア 内全品 商品タ グ指定 商品 コード 指定 JAN コード 指定 ブラン ド指定 カテゴ リー指 定 プレミ アム会 員 新規 ユーザ 通常 モール CRMクー ポン (パーソナ ライズ) STORE ’s Roo (パーソ ナライ ズ) ストア クリエ イター Pro 新規プ レミア ム会員 決済方 法指定 モール 商品タ グ指定 SMID 指定 Y!マー ト 出前館 まとめ 割 メー カー 案件種別 プロ モー ション パッ ケージ インテ リジェ ント 出前館 ユーザ Y!マー トユー ザ PayPa yカー ド会員 Y!モバ イル会 員 Y!モバ イルプ レミア ムバン ドル会 員 スマー トログ イン設 定 PayPa yID連 携 レ ビュー 対象 アプリ ダウン ロード LINE ユーザ LINE- YID連 携 LINEO A友達 登録 LINEO A友達 登録 購入回 数上限 下限 購入回 数開始 終了 可変 Enjoy パック 日替わ り 週替わ り PayPa y日用 品ミニ アプリ アプリ 新規向 け ルー レット くじ LYP入 会イン せ カート Wボタ ン 定期購 入 CRMラ イト育 成 デイ リー ボーナ ス SHPミ ニアプ リ LYPプ レミア ム会員 CRM初 期稼働 サービ ス登録 OA TOP訴 求 デ ビュー カウン トダウ ン ストア ラリー ゾロ目 優良配 送
  21. © LY Corporation Public 34 Point:ドメインモデルとレイヤードアーキテクチャの恩恵 • 新機能の追加がしやすい • レビューがしやすい

    • バグの発生が少ない 開発メンバーがどこに手を入れれば良いか共通認識を持つことができる • コードが読みやすい • コードを読むことで自然とドメイン知識が身についていく感覚が得られる • コードの与える影響がわかりやすい 効率の良いキャッチアップで早く戦力になれる
  22. © LY Corporation Public 37 Multirepoの運用コストが高い マイクロサービス: ユースケース単位 リポジトリ:マイクロサービス単位 •

    速い開発が可能(開発とデプロイの独立) • スケーラビリティ • 技術スタックの多様性 • 分離されたコードベース • ユースケースごとにドメインモデルを最適化できる メリット • 依存関係(ライブラリ・セキュリティ)の更新コストが高い デメリット Coupon ..40repo 適用可能クーポン取得 repository 利用候補取得 repository 検索 repository 詳細取得 repository Team Other ..30repo Other ..8repo Other ..4repo Other ..1repo ..83repo ・ ・ マイクロサービスの影響 Multi Repoの影響
  23. © LY Corporation Public 38 Monorepoの導入 • 複数のリポジトリ • 複数のマイクロサービス

    Multirepo Coupon 適用可能クーポン取得 repository 利用候補取得 repository 検索 repository 詳細取得 repository Coupon 適用可能クーポン取得 repository 利用候補取得 検索 詳細取得 ・ ・ ・ ・ • 単一のリポジトリ • 複数のマイクロサービス Monorepo 依存関係の 一元管理 Parent • 独立した開発とデプロイによる開発の 速さ • スケーラビリティ • 分離されたコードベース • ユースケースごとにドメインモデルを 最適化できる • 依存関係の管理が容易 メリット • CI/CD性能の低下 • ビルド/デプロイラインをマイクロ サービス単位のままにすることで解決 • 技術スタックの多様性 • Monorepoが複数あってもいい デメリット