Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

注意 本資料は、書籍『Learning Domain-Driven Design』のChapter5 を意訳・要約したものです。 資料に記載の内容は、 @dskst9 が解 釈した内容であり、原文の表現や内容と は異なるものも含まれています。

Slide 3

Slide 3 text

Chapter 5. Implementing Simple Business Logic 5-1. Transaction Script/トランザクションスクリプト 5-2. Active Record/アクティブレコード 5-3. Be Pragmatic/現実的である 5-4. Conclusion/結論 5-5. Exercise/エクササイズ

Slide 4

Slide 4 text

aaa この章では、ビジネスロジックのコードをモデル化し、実装するためのさまざま な方法について検討する。 単純なビジネスロジックに適した2つのパターンから始める。 ● トランザクションスクリプト ● アクティブレコード Overview

Slide 5

Slide 5 text

5-1. Transaction Script

Slide 6

Slide 6 text

Organizes business logic by procedures where each procedure handles a single request from the presentation. -Martin Fowler ビジネスロジックをプロシージャで整理し、各プロシージャがプレゼンテーションからの1 つの要求を処理する。

Slide 7

Slide 7 text

システムの公開インターフェースは、 ビジネストランザクションの集合体と 見なせる。 トランザクションは、情報を取得/変 更をする。 5-1. Transaction Script 出典: Learning Domain-Driven Design

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

5-1-1. Implementation

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

5-1-2. It’s Not That Easy!

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

トランザクションの実装に失敗する例: 包括的なトランザクションを使用せず に複数の更新をした場合。 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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

この問題は、ネットワーク障害、データベースのタイムアウトやデッドロック、あ るいはプロセスを実行するサーバーのクラッシュなど、あらゆる原因によって発 生する。 これは、両方のデータ変更を包含する適切なトランザクションを導入することで 修正できる。 Lack of transactional behavior

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

分散システムでは、データベースのデータに変更を加え、その変更をシステム の他のコンポーネントに通知するために、メッセージバスにメッセージを発行す るのが一般的な方法になる。 Distributed transactions

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

この問題を解決するのは、簡単ではない。複数のストレージ機構にまたがる分 散トランザクションは、複雑で拡張しにくく、エラーが発生しやすいので、通常は 避けられる。 ● 第8章では、CQRSアーキテクチャパターンを使用して、複数のストレージ 機構を投入する方法を学ぶ。 ● 第9章では、別のデータベースに変更をコミットした後に、メッセージの信 頼性の高い公開を可能にするアウトボックスパターンを紹介する。 Distributed transactions

Slide 21

Slide 21 text

最終訪問日を追跡する代わりに、訪問回 数を保持するケースにするとどうなるか。 しかし、これはまだ分散トランザクションで あり、潜在的に状態の不整合につながる可 能性がある。 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

Slide 22

Slide 22 text

データベースとメソッドを呼び出した外 部プロセスに情報を伝達するため、分 散トランザクションを構成している。 Implicit distributed transactions 出典: Learning Domain-Driven Design

Slide 23

Slide 23 text

データベースを更新するメソッドがvoid 型でも、操作が成功したか失敗したか は伝えられるので、失敗したら呼び出し 元は例外を受け取る。 もしメソッドが成功しても、呼び出し側へ の結果の伝達が失敗したらどうなるの か。 Implicit distributed transactions ● LogVisit が REST サービスの一 部であり、ネットワークに障害が発 生した場合。 ● LogVisit と呼び出し元の両方が 同じプロセスで実行されていて、呼 び出し元が LogVisit アクションの 正常な実行を追跡する前に、プロ セスが失敗した場合。

Slide 24

Slide 24 text

どちらの場合も、クライアントは失敗と判断して、LogVisit を再度呼び出そうと する。 LogVisit ロジックを再度実行すると、カウンターの値が正しく増加しない。この コードもトランザクションスクリプトパターンを正しく実装しておらず、システムの 状態を破壊することにつながる。 Implicit distributed transactions

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

別の方法は、楽観的同時実行制御を使用 する。 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

Slide 28

Slide 28 text

5-1-3. When to Use Transaction Script

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

トランザクションスクリプトパターンの主 な利点は、そのシンプルさ。 最小限の抽象化を導入し、実行時のパ フォーマンスとビジネスロジックを理解 するためのオーバーヘッドを最小限に 抑える。 5-1-3. When to Use Transaction Script しかし、このシンプルさは、このパターン の欠点。 ビジネスロジックが複雑になればなるほ ど、トランザクション間でビジネスロジッ クが重複しやすくなり、その結果、重複 したコードが同期しなくなり、動作に一貫 性がなくなる。

Slide 32

Slide 32 text

トランザクションスクリプトは、コアサブドメインのビジネスロジックの高い複雑 性に対応できないため、コアサブドメインに使用すべきではない。 複雑なビジネスロジックをトランザクションスクリプトとして実装すると、遅かれ 早かれ、メンテナンス不能な大きな泥団子になってしまう。 5-1-3. When to Use Transaction Script

Slide 33

Slide 33 text

5-2. Active Record

Slide 34

Slide 34 text

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 データベースのテーブルやビューの行をラップし、データベースへのアクセスをカプセル 化し、そのデータにドメインロジックを追加するオブジェクト。

Slide 35

Slide 35 text

トランザクションスクリプトパターンと同様に、アクティブレコードはビジネスロ ジックが単純なケースをサポートする。 しかし、実際にはビジネスロジックはより複雑なデータ構造で動作することがあ る。 5-2. Active Record

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

5-2-1. Implementation

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

システムのビジネス・ロジックはトランザクショ ン・スクリプトで構成される。 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

Slide 40

Slide 40 text

アクティブレコードの: ● 目的は、インメモリオブジェクトをデータベースのスキーマにマッピングす る際の複雑さをカプセル化すること。 ● 特徴は、データ構造と動作(ビジネスロジック)を分離していること。 アクティブレコードのフィールドにはパブリックゲッターとセッターがあり、外部プ ロシージャがその状態を変更できるようになっている。 5-2-1. Implementation

Slide 41

Slide 41 text

5-2-2. When to Use Active Record

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

5-3. Be Progmatic

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

5-4. Conclusion

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

5-5. Exercise

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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!"); }

Slide 54

Slide 54 text

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!"); }

Slide 55

Slide 55 text

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!"); }

Slide 56

Slide 56 text

Exercises この本の序文にあるWolfDeskの例に戻ると、システムのどの部分がトランザク ションスクリプトやアクティブレコードとして実装される可能性があるのでしょう か?

Slide 57

Slide 57 text

Thank you for listening