はじめに
最初の記事では、示されたプラクティスの範囲、それらを適用できるプロジェクト、および適用すべきでないプロジェクトを特定しました。
この記事では、DDDの基本原理の簡単な概要を説明するとともに、個人的な経験をアプリケーションで共有したいと思います。 コミュニケーションと構造的アプローチについて、実装の例とともに詳しく説明します。
次の記事では、実装を考慮して、適用された設計パターンの可能な組み合わせを書き留め、最終的に、1つの小さなマイクロサービスの特定の実装の例を示します。
DDD
以前の定義を思い出してください:
ドメイン駆動設計(DDD)は、継続的な開発の過程にある主要なビジネスモデルと実装を緊密にリンクすることにより、ニーズを包括的に満たすためのソフトウェア開発へのアプローチです。
複雑なシステムを構築する方法を説明するリファレンスブックは、Eric EvansのDomain Driven Dedign (Big Blue Book)です。 このトピックに関するレビュー記事を読んでいれば、すでにそれについて知っています。 実際にDDDを使用するまでに、DDDを読む必要があります。 これは読みやすい本ではありません。
DDDの標準的なソースはEric Evansの本です。 これはソフトウェアの文献で最も簡単に読むことはできませんが、相当な投資を十分に返済する本の1つです。
マーティンファウラー:2014年1月15日
本の内容をスクロールすると、完全に構造化されていないように見えます。 しかし、地図は私たちを助けてくれます。
マップには、今日検討するプラクティスがあります。
この本で扱っている実践の範囲は膨大です。 この本の外部で適用できるプラクティスの範囲はさらに大きくなります。 それらの少なくとも一部を使用する前に、目標を特定してください。 例として自分のものを挙げます。
- 生産性を向上させます。
- わかりやすいコードを書く。
- ソフトウェア開発レベルでのスケーリング。
単一言語
ソフトウェア開発によって新しいものが作成されることはめったにありません。通常は、既存のもののシミュレーションです。
モデルは、必要なプロパティと機能のみを含む実際のオブジェクトの表現です。
サブジェクト領域全体をカバーするソフトウェア製品を作成することはできません。 必要な機能を再現する部分のみを再現することが可能です。
モデルの良い例は、地形図です。 彼女は地形モデルです。 このマップには、草原や川の草原は含まれていません。実際のオブジェクトの相対的な位置のみを反映しています。
誰にとっても明確なモデルを構築するには、同じ言語を話す必要があります。 エリック・エバンスだけでなく、常識も教えてくれます。 プログラマーが自分の用語を使用し、独自の「スラング」を扱う場合、前者は単に何をする必要があるかを理解しません。 この場合のビジネスは、いずれかの「機能」を開発するための実際のコストを実現することはできません。 「はい、ボタンを追加するだけです」と聞いたことがありますか?
システム設計者としての目標は、チーム全体からお互いを最大限に理解することです。 これを達成する方法は? 話し始めましょう。 人々が近いグループでコミュニケーションを始めた場合、一般的に受け入れられている特定の用語のセットがあります。 異なる企業では、共通言語を導入するプロセスは異なる可能性があります。 これは、強い意思決定または民主的な手順のいずれかです。 言語は明示的に示すことができ、明示的に入力されることはありません。その場合、彼らは単に話し始めます。 共通言語を導入するための優れたテクニックは、一般的なドキュメントです。
プロジェクト文書の保管方法
- ビジネスと開発の間のコミュニケーションは、モデルを改善するはずです。
- 会議の後、結果をドキュメント(スクラムアーティファクト)の形式で記録し、このドキュメントを開発プロセスのすべての参加者に見せます。
- ドキュメントでは単一の言語を使用します。
- 最も重要なこと:ドキュメントに時間を無駄にしないでください。 あなたはまだコードを書かなければならず、ドキュメントは何度も書き直され、リソースを使うことは高価です。 UMLチャートアプリケーションを長時間いじる代わりに、携帯電話でナプキン、ペン、カメラを使用します。
- ドキュメンテーションには規律が必要です;時々それを書くことはできません。
- ドキュメントを分離します。
- コード内のコメント-不可解な瞬間をコード内に直接記述し、
#ODO:
ままにし#ODO:
(コードをマスターにマージするときに削除します)。 コメントで意見を述べてください。たとえば、レガシコードを操作するときは、松葉杖を使用する必要があります。 - プロジェクトのルートディレクトリにある
README.md
プロジェクトに関するコメントには、プロジェクトの開始方法、テストの実行方法などの技術情報を含める必要があります。 また、すべてのプロジェクトがあり、どのサーバーで実行されているかを示す地図を取得することをお勧めします。 受け入れられたすべての契約を個別に記録します。 - そして最も重要なことは、知識ベースです。 ビジネスプロセスを説明するドキュメントのコレクション。これは、ドキュメントの一部であり、ユーザーとビジネスの両方が利用できます。
- コード内のコメント-不可解な瞬間をコード内に直接記述し、
- ドキュメントを書く人の主な間違いは冗長性です。 すべてを網羅しようとしないでください。一般的な意味だけを伝えてください。 ドキュメントはプロジェクトを補完するものである必要がありますが、決してそれを置き換えるものではありません。 曖昧なだけの用語をすべて書き留めないでください。 定義が3つ以上の文をとる場合、それは不十分な定義です。
ドキュメントの例:
# . # : : - - email - ## : ### , email , 1 ( email ). ### . email . ### email email . , email. . ### , , . . ### email, , . 2 . ### . , , .
この例では、明示的な辞書を指定しなかったにもかかわらず、 User 、 Authorization 、 Registrationの概念を修正したことに注意してください。 このようなドキュメントを作成するのに、専門家は20分以上かかりません。
サブジェクト領域の専門家ではない人にとって、ドキュメントを書くプロセスは複雑なものとして認識されます。 知識の収集と収集された知識の記録を分離する必要があります。 != + .
「あなたが宇宙と呼ぶものは、実際、弓の皮のように、互いの上にあり、徐々に互いから離れている世界の蓄積です」
-異常に明確に述べられている! -アブデライトを賞賛しました。 -驚くほど明確です! 「彼らは、タマネギが何であるかを非常によく知っていたので、彼らは哲学者を理解したと思った。」
アバディーン物語、クリストフ・マーティン・ウィーランド
限られたコンテキストとドメイン
私たちがプログレッシブスタートアップのデザイナーとして行動したと想像してください。 私たちは皆、冷却ピザ、宅配便でのろい、サイト上のフォームへの記入を何時間も大好きです。 そのため、素晴らしいスタートアップ「4匹のカメと1匹のネズミ」を思い付きました。
- ピッツェリアが登録され、実際の料理を投稿するサイトがあります。
- これらのピザ屋には、独自の宅配便サービスはありません。
- ピッツェリアにアクセスできない顧客もいますが、サイトまたはモバイルアプリケーションから注文する準備ができています。
- キラーの「機能」:宅配便業者は特別に雇用された人員ではなく、モバイルアプリケーションを介して登録した普通の人
- クーリエは注文を受け取り、実行後、完了した作業に対する支払いを受け取ります。
- そのような宅配便を待つのに時間がかかりますが、配達、したがってピザは安くなります。
前の章で説明したドキュメントを思い出しましょう。 そこで、単一の辞書では、 登録という用語が使用されました 。 しかし、このプロジェクトにはそれらのいくつかがあります。
- 顧客登録
- ピッツェリア登録
- 宅配便登録
- 注文登録
統一言語は限られたコンテキストに属します。 上記のドキュメントのドメインは「承認システム」です。 スタートアップにドメインを割り当ててみましょう。
しかし、始める前に、ドメインとは何か、限られたコンテキストとは何かについて、少し用語を見てみましょう。
ドメインは、特定の問題を解決する実際のビジネス構造の表現です。
例:物流システム、支払いシステム、承認システム、注文管理システム。
ドメインは、より小さな構造を記述するサブドメインに分割されます。たとえば、注文のバスケット、ルートを構築するためのシステムです。
各ドメインの責任範囲は限られています-機能が制限されています。
境界のあるコンテキスト-ドメインが最適なソリューションを得るために1つのタスクのみに集中できるようにする一連のドメイン制限。
私はこの用語をそのような抽象化として提示したいです。 ドメインは円です。 制限されたコンテキストは円です。
DDDの用語でも、カーネルは割り当てられています。
コア(コアドメイン)-ビジネスを最も特徴付ける最も重要なドメイン。
したがって、プロジェクトのドメイン「4匹のカメと1匹のネズミ」:
ピッツェリアで働く ( ピッツァリアス)
コンテキスト :ピッツェリアに関連するすべてのB2B
サブドメイン :
- 新しいピッツェリアの登録
- 品揃えの追加
- 製品の可用性のステータスを更新する
クライアントと連携する (クライアント)
コンテキスト :B2C、ピザの顧客との仕事に関連するすべて
サブドメイン :
- 品揃えを見る
- 情報資料
宅配業者と連携する (配送システム)
コンテキスト :b2e、宅配便業者との連携に関連するすべて
サブドメイン :
- 宅配便登録
- タスクの割り当て
- 宅配業者が獲得した資金の引き出しの申請の登録。
注文システム
コンテキスト :カーネル。 すべての個々のドメインを調整して、注文の受け取りからユーザーへのピザの配達までの完全なサイクルを提供できます。 パフォーマーではありませんが、指揮者の役割を果たします。
サブドメイン :
- 注文受付
- 注文執行
- 注文状況の追跡
決済システム (請求)
コンテキスト :すべての金融取引が含まれます。 処理センターとの対話を提供します。
サブドメイン :
- 注文のためのお金を受け入れる
- 行われた仕事のために宅配便業者にお金を与える
統計システム
コンテキスト :分析情報の収集と処理(発行ではない)。
サブドメイン :
- 資金に関する統計
- アプリケーション統計
管理システム (管理パネル)
コンテキスト :分析情報の発行。 管理決定のツールキット。
- 収集された統計に基づく分析
- 宅配業者への支払いの事前調整
ドメインに基づいて、それをマッピングしましょう。
ドメインマップ(コンテキストマップ)は、個々のドメイン間の関係を記述することができるグラフィカルツールです。
マップには、ドメイン間のリンクが表示されます。 このマップは非常に表面的ですが、サブジェクトエリアはよく理解されていません。 これは最初のスケッチであり、書き直して期待される結果を得ることができます。
マップ上の最も重要なことは、ドメイン間の接続が表示されることです。 このような構造は、マイクロサービスアーキテクチャに非常に適しています。
マイクロサービスアーキテクチャの主な原理:弱い接続性と強い付着。
この原則は、Sam Newman- The Creation of Microservicesの本に記載されています。これは、この記事で説明したアプローチの実用化を始めるために読む必要のある2番目の本です。 意味:ドメインは緩やかに接続されている必要がありますが、内部的に密接にリンクされている必要があります。
これらの用語の翻訳は、ロシアの公式翻訳から取られたものであり、おそらく伝達された意味をほとんど反映していない。 元の用語では、低結合(結合性、結合、グリップ、共役)、高凝集性(結合性、強度)です。
ドメイン共有の実装プラクティス
個人的な経験、つまり情報に基づいた一連の決定を共有したいと思います。 これらのソリューションを使用することをお勧めしません。 しかし、どこから始めればよいかわからない場合、それらは良い選択かもしれません。 個人的な経験により、ツールキットはさらにニーズに合わせて調整されます。
私たちを導いた主な原則:
- ソリューションのシンプルさ。 単純な複雑なものではなく、複雑なものを単純にします。
- 実用主義。 常に状況を確認する必要があり、既存のソリューションがない場合は新しいソリューションを開発します。 すべてを類型化しようとしますが、独断を避けます。
- Code!=ドキュメント。 コードはマシンの指示、ドキュメントは人の指示です。 それらを混同して次々に配る必要はありません。
- 固い
ドメインを実装する方法は?
ドメインを個別のマイクロサービスとして選択すると非常に便利です。
マイクロサービスは、1つのドメインのロジックを実装する別個のアプリケーションです。
DDD開発では、マイクロサービスを別のアプリケーションに割り当てる原則は、コンテキストが制限されます。 これは、サービスの分離の技術原則を無効にするものではありません(これが高性能を確保する必要があるためである場合)。 しかし、文脈上の原則は支配的で拘束力があります。
ドメイン間の関係を強調するには?
ドメイン間の関係は常にAPIです。 RESTful json api、gRPC、AMPQを使用できます。 この記事のフレームワーク内では、あるプロトコルを別のプロトコルと比較することはせず、それぞれの利点と欠点を強調しますが、それぞれに独自の応用分野があります。 それでも、一般的な推奨事項について説明しましょう。
プロトコルを柔軟に選択し、その実装の均一性を厳格にします。
ドメインのペアごとに個別にプロトコルを選択し、どこでもhttpを使用しようとしないでください。非同期キューが必要になる場合があり、AMPQの利点が明らかになります。 どこにでもRESTfulがあるため、この機会を無視しないでください。
一方、RESTful jsonを実装する場合は、1つのデータ構造標準を使用します。 jsonapiやopenapiなどの準備ができています。 何らかの理由で既製のソリューションがあなたに合わず、あなたがあなたの標準を開発できると感じたら、それを記述して使用してください。 しかし、どこでもそれを使用し、「動物園」の基準を育てないでください。 標準について何も知らない外部システムと通信する必要がある場合は、マイクロサービスアダプタを記述します。
サブドメインを実装する方法は?
マイクロサービス内の個別のモジュールとして。
モジュールは、単一のマイクロサービス内の別のネームスペース(ネームスペース)にロジックを配置することにより、サブドメインの実装です。
それはすべてどのように見えますか? 例を見てみましょう。 覚えているように、配信システムドメインがあります。このドメインには3つのサブドメインがあります。
- 宅配便登録
- タスクの発行(タスク)
- クーリエが獲得した資金の引き出しの申請の登録(引き出し)
- マイクロサービスが機能していることを確認する、補助的な、技術的なツール(healt_checker)
これをすべてフォルダー構造の形で想像してください。
$ tree --dirsfirst delivery_system delivery_system ├── app/ │ ├── health_checker/ │ │ └── endpoints.rb │ ├── registrations/ │ │ ├── entities/ │ │ ├── forms/ │ │ ├── repositories/ │ │ ├── interactor/ │ │ ├── services/ │ │ ├── validations/ │ │ ├── endpoints.rb │ │ └── helpers.rb │ ├── tasks │ │ ├── entities/ │ │ ├── queries/ │ │ ├── repositories/ │ │ ├── endpoints.rb │ │ └── helpers.rb │ └── withdrawals │ ├── entities/ │ ├── forms/ │ ├── repositories/ │ ├── interactor/ │ ├── services/ │ ├── validations/ │ ├── endpoints.rb │ └── helpers.rb ├── config/ ├── db/ ├── docs/ ├── lib/ │ ├── schemas/ │ └── values/ ├── public ├── specs ├── config.ru ├── Gemfile ├── Gemfile.lock ├── Rakefile └── README.md
apps/
ディレクトリの各フォルダは、1つまたは別のサブドメインを実装します。各ドメイン内にforms
、 entities
、 forms
、 services
など、さまざまなパターンがありservices
。適用されるパターンについては、今後の記事で詳しく説明します。
このような各パターンは、対応するネームスペース(ネームスペース)に実装されます。 たとえば、宅配便業者への支払い用のアプリケーションを作成するためのフォーム:
module Withdrawal # module Forms # class Create end end end
サブドメイン間の通信を実装する方法は?
特定の例を見てみましょう。 宅配便のアカウント: Registrations::Entities::Account
ます。 Registrations
サブドメインを指します-このドメインは登録プロセスではなく、ビジネスドキュメントに示されているように、アカウントテーブルと登録簿と見なされるためです。
このアカウントにアクセスする2つのプロセスが実行されています。
- アカウントを作成する(登録)
- 宅配業者が獲得した資金の引き出しの申請の作成(Wihtdrawal)
ご覧のとおり、これら2つのプロセスは異なるサブドメイン-登録とWihtdrawalに属します。
module Registrations module Serivices class CreateAccount def call account = Entities::Account.new end end end end module Withdrwals module Serivices class CreateOrder def call account = Registrations::Entities::Account.new end end end end
最初のケースでは、クラスへの呼び出しはEntities::Account
への呼び出しを通じて実装されます。 2番目のケースでは、 Registrations::Entities::Account
への明示的な呼び出しを通じて。 つまり サブドメインを明示的に指定すると、クラスは別のサブドメインからのものであるため、接続を明確に示します。
クラスがサブドメインのいずれにも明示的に適用されない場合、それをlib/
フォルダーに移動することは理にかなっています。 原則として、これらは「ValueObject」パターンを実装するクラスです。 このパターンについては、次のいずれかの記事で詳しく説明します。
モデルによる実装。
エリック・エヴァンスを引用するには:
プログラムのアーキテクチャ、または少なくともその中心部分がドメインモデルの構造に対応していない場合、そのようなモデルは実際には役に立たないので、プログラムの正しい動作も問題になります。 同時に、ソフトウェアアーキテクチャのモデルと機能の間の複雑すぎる関係は理解するのが難しく、実際には、アーキテクチャの変更に応じて維持することは困難です。
この記事の冒頭ですでに引用した優れたモデルの例-地形図を思い出してみましょう。 私たちの目標は、2つの集落間の距離をすばやく見つけることができるようにすることです。 都市間の2つのポイントを示す参照テーブルを使用できます。 そして、カードを使用できます。 そことそこの両方で、ほぼ同じ時間に同じ結果が得られます。 しかし、マップはよりコンパクトで、主題領域をより正確に表示し、より普遍的です。 モデルとしての地図は非常に表現力豊かです。 そして、このタスクのフレームワーク内でそれを考慮すると、それが反映する領域自体よりも地図上の距離を測定する方が便利です。 サブジェクト領域を反映するモデルは、一部のプロパティでそれを上回ることができます。 本当に素晴らしいです。
モデルの実装は、常に予測不可能な結果を伴う創造的なプロセスです。 コードの品質は、パフォーマンスや複雑さではなく、シンプルさと表現力です。 絶え間ないリファクタリングを通じて改善し、柔軟性を高め、不要なものをすべてカットします。 モデルのビジネスロジックを担当するレイヤーを、技術的な実装が必要なレイヤーから分離します。 これをどうやって管理したかについては、後で説明します。
- 問題指向設計、エリックJ.エヴァンス
- マイクロサービスの作成、サムニューマン
- http://gorodinski.com/blog/2013/04/29/sub-domains-and-bounded-contexts-in-domain-driven-design-ddd/
- https://martinfowler.com/bliki/BoundedContext.html
- https://habr.com/post/316438/
- https://dotnetcodr.com/2015/08/06/domain-driven-design-with-web-api-revisited-part-1-introduction/
- アバディーンの歴史、クリストフ・マーティン・ウィーランド