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

Learning Domain-Driven Design Round-reading session 6

Learning Domain-Driven Design Round-reading session 6

『Learning Domain-Driven Design』 輪読会 No.6 の資料です。

## イベント
https://showcase-gig.connpass.com/event/250332/

Daisuke Sato

June 16, 2022
Tweet

More Decks by Daisuke Sato

Other Decks in Technology

Transcript

  1. Learning Domain-Driven Design 輪読会 No.6 Chapter 5. Implementing Simple Business

    Logic https://showcase-gig.connpass.com/event/250332/ 2022/04/21 daisuke.sato @dskst9
  2. 注意 本資料は、書籍『Learning Domain-Driven Design』のChapter5 を意訳・要約したものです。 資料に記載の内容は、 @dskst9 が解 釈した内容であり、原文の表現や内容と は異なるものも含まれています。

  3. Chapter 5. Implementing Simple Business Logic 5-1. Transaction Script/トランザクションスクリプト 5-2.

    Active Record/アクティブレコード 5-3. Be Pragmatic/現実的である 5-4. Conclusion/結論 5-5. Exercise/エクササイズ
  4. aaa この章では、ビジネスロジックのコードをモデル化し、実装するためのさまざま な方法について検討する。 単純なビジネスロジックに適した2つのパターンから始める。 • トランザクションスクリプト • アクティブレコード Overview

  5. 5-1. Transaction Script

  6. Organizes business logic by procedures where each procedure handles a

    single request from the presentation. -Martin Fowler ビジネスロジックをプロシージャで整理し、各プロシージャがプレゼンテーションからの1 つの要求を処理する。
  7. システムの公開インターフェースは、 ビジネストランザクションの集合体と 見なせる。 トランザクションは、情報を取得/変 更をする。 5-1. Transaction Script 出典: Learning

    Domain-Driven Design
  8. トランザクションスクリプトは: • ビジネスロジックをプロシージャに基づいて組織化する • 各プロシージャは、利用者がパブリックインターフェースを介して行う操作 を実装する システムのパブリックオペレーションは、カプセル化の境界として使用される。 5-1. Transaction Script

  9. 5-1-1. Implementation

  10. 5-1-1. Implementation 各操作は成功するか失敗するかのどちらかであるべ き。たとえ、トランザクションが失敗したとして も、変更をロールバックするか、補償するアクショ ンを実行して、システムは一貫性を保たなければな らない。 トランザクション的な振る舞いは、パターン名であ る「トランザクションスクリプト」にも反映されて いる。

    各プロシージャは、単純で分か りやすい手続き型スクリプトと して実装される。データベース にアクセスすることも自由であ る。
  11. JSONファイルのバッチをXMLファイ ルに変換するトランザクションスクリプ トの例。 DB.StartTransactions(); var job = DB.LoadNextJob(); var json

    = LoadFile(job.Source); var xml = ConbertJsonToXml(json); WriteFile(job.Destination, xml.ToString()); DB.MarkJobAsCompleted(job); DB.Commit(); 5-1-1. Implementation
  12. 5-1-2. It’s Not That Easy!

  13. トランザクションスクリプトパターンは、高度なビジネスロジックの実装パターン の基礎となる。 一見単純そうに見えるがもっとも間違えやすいパターンである。プロダクトの問 題は、ビジネスロジックのトランザクション動作の誤実装に帰結することが多 い。 5-1-2. It’s Not That Easy!

  14. トランザクションの実装に失敗する例: 包括的なトランザクションを使用せず に複数の更新をした場合。 public class LogVisit { public void Execute(Guid

    userId, DateTime visitedOn) { // Usersテーブルのレコードを更新 _db.Execute("UPDATE Users SET lats_visit=@p1 WHERE user_id=@p2", visitedOn, userId); // VisitsLogテーブルにレコードを挿入する _db.Execute(@"INSERT INTO VisitsLog(user_id, visit_date) VALUES (@p1, @p2)", userId, visitedOn); } } Lack of transactional behavior
  15. Usersテーブルのレコードが更新され た後、ログレコードの追加に成功する 前に何らかの問題が発生した場合、 システムは矛盾した状態に陥る。 public class LogVisit { … public

    void Execute(Guid userId, DateTime visitedOn) { // Usersテーブルのレコードは更新される _db.Execute(“"UPDATE Users SET lats_visit=@p1 WHERE user_id=@p2", visitedOn, userId); // VisitsLogテーブルには対応するレコードが書き込まれない _db.Execute(@"INSERT INTO VisitsLog(user_id, visit_date) VALUES (@p1, @p2)", userId, visitedOn); } } Lack of transactional behavior
  16. この問題は、ネットワーク障害、データベースのタイムアウトやデッドロック、あ るいはプロセスを実行するサーバーのクラッシュなど、あらゆる原因によって発 生する。 これは、両方のデータ変更を包含する適切なトランザクションを導入することで 修正できる。 Lack of transactional behavior

  17. RDBは、複数のレコードにまたがるトランザ クションをサポートしているため、この修正 は簡単。 しかし、複数レコードのトランザクションをサ ポートしていないデータベースや、複数の ストレージ機構を扱う場合は複雑になる。 public class LogVisit {

    public void Execute(Guid userId, DateTime visitedOn) { try { _db.startTransactions(); _db.Execute(@"UPDATE Users SET last_visit=@p1 WHERE user_id=@p2", visitedOn, userId); _db.Execute(@"INSERT INTO VisitsLog(user_id, visit_date) VALUES (@p1, @p2)", userId, visitedOn); _db.Commit(); } catch { _db.Rollback(); throw; } } } Lack of transactional behavior
  18. 分散システムでは、データベースのデータに変更を加え、その変更をシステム の他のコンポーネントに通知するために、メッセージバスにメッセージを発行す るのが一般的な方法になる。 Distributed transactions

  19. 先ほどの例で、テーブルへの訪問をログに 記録する代わりに、メッセージバスに発行 するとどうなるか。 前の例と同様に、処理途中で発生した障害 はシステムの状態を破壊できる。 public class LogVisit { public

    void Execute(Guid userId, DateTime visitedOn) { // Usersテーブルのレコードは更新される _db.Execute("UPDATE Users SET last_visit=@p1 WHERE user_id=@p2", visitedOn, userId); // メッセージバスへの発行に失敗すると、 // 他のコンポーネントには通知されない _messageBus.Publish(“VISITS_TOPIC”, new { UserId = userId, VisitDate = visitedOn }); } } Distributed transactions
  20. この問題を解決するのは、簡単ではない。複数のストレージ機構にまたがる分 散トランザクションは、複雑で拡張しにくく、エラーが発生しやすいので、通常は 避けられる。 • 第8章では、CQRSアーキテクチャパターンを使用して、複数のストレージ 機構を投入する方法を学ぶ。 • 第9章では、別のデータベースに変更をコミットした後に、メッセージの信 頼性の高い公開を可能にするアウトボックスパターンを紹介する。 Distributed

    transactions
  21. 最終訪問日を追跡する代わりに、訪問回 数を保持するケースにするとどうなるか。 しかし、これはまだ分散トランザクションで あり、潜在的に状態の不整合につながる可 能性がある。 public class LogVisit { //

    メソッドを呼び出すと、対応するカウンターの値が 1増える public void Execute(Guid userId) { // 1つのデータベース内の 1つのテーブルの 1つの値を更新するだけ _db.Execute("UPDATE Users SET visits=visits+1 WHERE user_id=@p1", userId); } } Implicit distributed transactions
  22. データベースとメソッドを呼び出した外 部プロセスに情報を伝達するため、分 散トランザクションを構成している。 Implicit distributed transactions 出典: Learning Domain-Driven Design

  23. データベースを更新するメソッドがvoid 型でも、操作が成功したか失敗したか は伝えられるので、失敗したら呼び出し 元は例外を受け取る。 もしメソッドが成功しても、呼び出し側へ の結果の伝達が失敗したらどうなるの か。 Implicit distributed transactions

    • LogVisit が REST サービスの一 部であり、ネットワークに障害が発 生した場合。 • LogVisit と呼び出し元の両方が 同じプロセスで実行されていて、呼 び出し元が LogVisit アクションの 正常な実行を追跡する前に、プロ セスが失敗した場合。
  24. どちらの場合も、クライアントは失敗と判断して、LogVisit を再度呼び出そうと する。 LogVisit ロジックを再度実行すると、カウンターの値が正しく増加しない。この コードもトランザクションスクリプトパターンを正しく実装しておらず、システムの 状態を破壊することにつながる。 Implicit distributed transactions

  25. この問題に対する単純な解決策はなく、すべてはビジネスドメインとその必要 性に依存する。 トランザクションの動作を保証する一つの方法として、操作を冪等とすることが ある。 Implicit distributed transactions

  26. カウンタの値を渡すようクライアントに依頼 する。カウンタの値を渡すには、呼び出し 側がまず現在の値を読み取り、ローカルで 値を増やし、更新後の値をパラメータとして 提供する必要がある。 たとえこの操作が複数回実行されても、最 終的な結果は変わらない。 public class LogVisit

    { public void Execute(Guid userId, long visits) { _db.Execute("UPDATE Users SET visits=@p1 WHERE user_id=@p2", visits, userId) } } Implicit distributed transactions
  27. 別の方法は、楽観的同時実行制御を使用 する。 LogVisit 操作を呼び出す前に、呼び出し 元はカウンターの現在値を読み取り、それ をパラメータとして LogVisit に渡す。 LogVisit は、カウンタの値が呼び出し元に

    よって最初に読み取られたものと等しい場 合にのみ、カウンタの値を更新する。 public class LogVisit { … public void Execute(Guid userId, long expectedVisits) { _db.Execute(@"UPDATE Users SET visits=visits+1 WHERE user_id=@p1 and visits=@p2", userId, visits); } } Implicit distributed transactions
  28. 5-1-3. When to Use Transaction Script

  29. トランザクションスクリプトパターンは、ビ ジネスロジックが単純な手続き的操作に 似ており、最も単純な問題領域によく適 応している。 例えば、Extract/Transform/Load (ETL)処理では、各処理がソースから データを抽出し、変換ロジックを適用し て別の形式に変換し、その結果を宛先 ストアにロードする。 5-1-3.

    When to Use Transaction Script 出典: Learning Domain-Driven Design
  30. トランザクションスクリプトパターンは: • 定義上ビジネスロジックが単純であるサブドメインをサポートするのに適し ている。 • 外部システムとの統合のためのアダプターとして、例えば一般的なサブド メインや、不正防止レイヤーの一部として使用することもできる。 5-1-3. When to

    Use Transaction Script
  31. トランザクションスクリプトパターンの主 な利点は、そのシンプルさ。 最小限の抽象化を導入し、実行時のパ フォーマンスとビジネスロジックを理解 するためのオーバーヘッドを最小限に 抑える。 5-1-3. When to Use

    Transaction Script しかし、このシンプルさは、このパターン の欠点。 ビジネスロジックが複雑になればなるほ ど、トランザクション間でビジネスロジッ クが重複しやすくなり、その結果、重複 したコードが同期しなくなり、動作に一貫 性がなくなる。
  32. トランザクションスクリプトは、コアサブドメインのビジネスロジックの高い複雑 性に対応できないため、コアサブドメインに使用すべきではない。 複雑なビジネスロジックをトランザクションスクリプトとして実装すると、遅かれ 早かれ、メンテナンス不能な大きな泥団子になってしまう。 5-1-3. When to Use Transaction Script

  33. 5-2. Active Record

  34. An object that wraps a row in a database table

    or view, encapsulates the database access, and adds domain logic on that data. -Martin Fowler データベースのテーブルやビューの行をラップし、データベースへのアクセスをカプセル 化し、そのデータにドメインロジックを追加するオブジェクト。
  35. トランザクションスクリプトパターンと同様に、アクティブレコードはビジネスロ ジックが単純なケースをサポートする。 しかし、実際にはビジネスロジックはより複雑なデータ構造で動作することがあ る。 5-2. Active Record

  36. フラットレコードの代わりに、より複雑な オブジェクトツリーや階層を持つことが できる。 このようなデータ構造を単純なトランザ クションスクリプトで操作すると、多くの コードが繰り返されることになる。インメ モリ表現へのデータのマッピングは、す べて重複して行われることになる。 5-2. Active

    Record 出典: Learning Domain-Driven Design
  37. 5-2-1. Implementation

  38. アクティブレコードと呼ばれる専用のオブジェクトを使用して、複雑なデータ構 造を表現する。データ構造とは別に、レコードの作成、読み込み、更新、削除と いったデータアクセスのためのメソッド、いわゆるCRUDオペレーションも実装 している。 その結果、アクティブレコードオブジェクトはORMや他のデータアクセスフレー ムワークに結合される。このパターンの名前は、各データ構造が「アクティブ」 であること、つまり、データアクセスロジックを実装していることに由来してい る。 5-2-1. Implementation

  39. システムのビジネス・ロジックはトランザクショ ン・スクリプトで構成される。 2つのパターンの違いは、データベースに直 接アクセスするのではなく、トランザクションス クリプトがアクティブなレコードオブジェクトを 操作すること。 それが完了すると、操作はアトミックトランザ クションとして完了するか失敗するかのどちら かにならなければならない。 public

    class CreateUser { try { _db.StartTransaction(); var user = new User(); user.Name = userDetails.Name; user.Email = userDetails.Email; user.Save(); _db.Commit(); } catch { _db.Rollback(); throw; } } 5-2-1. Implementation
  40. アクティブレコードの: • 目的は、インメモリオブジェクトをデータベースのスキーマにマッピングす る際の複雑さをカプセル化すること。 • 特徴は、データ構造と動作(ビジネスロジック)を分離していること。 アクティブレコードのフィールドにはパブリックゲッターとセッターがあり、外部プ ロシージャがその状態を変更できるようになっている。 5-2-1. Implementation

  41. 5-2-2. When to Use Active Record

  42. アクティブレコードパターンは: • データベースへのアクセスを最適化するトランザクションスクリプトである ため、このパターンではCRUD操作など、比較的単純なビジネスロジックし かサポートできない。 • サブドメインのサポート、汎用サブドメイン用の外部ソリューションの統合、 またはモデル変換タスクに適している。 • 複雑なデータ構造をデータベースのスキーマにマッピングする複雑さに対

    処している。 5-2-2. When to Use Active Record
  43. アクティブレコードパター ンはツールである 5-2-2. When to Use Active Record アクティブレコードは、デザインパター ンを指すのであって、アクティブレコー

    ドフレームワークを指すのではない。他 のツールと同様に、問題を解決するこ とができるが、間違った文脈で適用さ れると、良いことよりも悪いことをもたら す可能性がある。 ビジネスロジックが単純であれば、アク ティブレコードを使うことに何の問題も ない。
  44. 5-3. Be Progmatic

  45. ビジネスデータは重要であり、設計・構築するコードはその完全性を保護すべ きだが、実用的なアプローチがより望ましいケースもある。 特に規模が大きくなると、データの一貫性保証が緩和されるケースがある。 100万件のレコードのうち1件の状態を破損することが、本当にビジネスの ショーストッパーになるのか、ビジネスのパフォーマンスや収益性に悪影響を 及ぼすことがないかをチェックする。 5-3. Be Progmatic

  46. 可能な限り「手抜き」をし ても構わない 5-3. Be Progmatic ただ、そのリスクとビジネスへの影響を 必ず評価すること。 いつものように、普遍的な法則はない。 すべては、あなたが働いているビジネ ス領域に依存する。

  47. 5-4. Conclusion

  48. Transaction Script • システムの操作を単純明快な手続き型スクリプトとして組織化したもの。 • 各操作がトランザクション的であることを保証する。 • ビジネスロジックが単純なETLのような、サブドメインをサポートするのに 適している。 5-4.

    Conclusion
  49. Active Record • ビジネスロジックは単純だが、複雑なデータ構造で動作する場合、それら のデータ構造をアクティブレコードとして実装する。 • アクティブレコードオブジェクトは、簡単なCRUDデータアクセスメソッドを提 供するデータ構造である。 5-4. Conclusion

  50. 5-5. Exercise

  51. Exercises コアサブドメインのビジネスロジックを実装するために使用すべきパターンはどれ か。 a. トランザクションスクリプト b. アクティブレコード c. どちらのパターンも、コアサブドメインの実装には使用できない d.

    どちらのパターンも、コアサブドメインの実装に使用することができる
  52. Exercises コアサブドメインのビジネスロジックを実装するために使用すべきパターンはどれ か。 a. トランザクションスクリプト b. アクティブレコード c. どちらのパターンも、コアサブドメインの実装には使用できない d.

    どちらのパターンも、コアサブドメインの実装に使用することができる
  53. Exercises 高レベルのトランザクションメカニズムがないと仮定 すると、潜在的なデータの一貫性の問題を見つけるこ とができますか? a. 新しいチケットを受け取ると、割り当てられた エージェントのアクティブチケットのカウンターを 1 以上増やすことができる b.

    エージェントのアクティブチケットのカウンター を1つ増やすことができるが、そのエージェントには 新しいチケットが割り当てられない c. エージェントが新しいチケットを取得しても、そ れについて通知されない d. 上記のような問題はすべて考えられる public void CreateTicket(TicketDate data) { var agent = FindLeastBusyAgent(); agent.ActiveTickets = agent.ActiveTickets + 1; agent.Save(); var ticket = new Ticket(); ticket.Id = Guid.New(); ticket.Data = data; ticket.AssinedAgent = agent; ticket.Save(); _alerts.Send(agent, "You have a new ticket!"); }
  54. Exercises 高レベルのトランザクションメカニズムがないと仮定 すると、潜在的なデータの一貫性の問題を見つけるこ とができますか? a. 新しいチケットを受け取ると、割り当てられた エージェントのアクティブチケットのカウンターを 1 以上増やすことができる b.

    エージェントのアクティブチケットのカウンター を1つ増やすことができるが、そのエージェントには 新しいチケットが割り当てられない c. エージェントが新しいチケットを取得しても、そ れについて通知されない d. 上記のような問題はすべて考えられる public void CreateTicket(TicketDate data) { var agent = FindLeastBusyAgent(); agent.ActiveTickets = agent.ActiveTickets + 1; agent.Save(); var ticket = new Ticket(); ticket.Id = Guid.New(); ticket.Data = data; ticket.AssinedAgent = agent; ticket.Save(); _alerts.Send(agent, "You have a new ticket!"); }
  55. Exercises このコードには、システムの状態を破壊する可能性の あるエッジケースが少なくとも、もう一つ存在しま す。発見することができますか? public void CreateTicket(TicketDate data) { var

    agent = FindLeastBusyAgent(); agent.ActiveTickets = agent.ActiveTickets + 1; agent.Save(); var ticket = new Ticket(); ticket.Id = Guid.New(); ticket.Data = data; ticket.AssinedAgent = agent; ticket.Save(); _alerts.Send(agent, "You have a new ticket!"); }
  56. Exercises この本の序文にあるWolfDeskの例に戻ると、システムのどの部分がトランザク ションスクリプトやアクティブレコードとして実装される可能性があるのでしょう か?

  57. Thank you for listening