主キーがビジネスドメインの一部ではないという事実に対処する


25

ほとんどすべての状況で、主キーはビジネスドメインの一部ではありません。確かに、一意のインデックスを持つユーザー向けの重要なオブジェクト(UserNameユーザーまたはOrderNumber注文用)がいくつかある場合もありますが、ほとんどの場合、1つまたは複数の値によってドメインオブジェクトを明確に識別する必要はありません管理ユーザー。これらの例外的な場合でも、特にグローバル一意識別子(GUID)を使用している場合は、主キー自体を公開するのではなく、代替キーを使用したい、または使用したいでしょう。

したがって、ドメイン駆動型設計の私の理解が正確である場合、主キーを公開する必要はなく、したがって公開すべきではありません。彼らはくて、私のスタイルをcr屈にします。しかし、ドメインモデルに主キーを含めないことを選択した場合、結果が生じます。

  1. 単純に、ドメインモデルの組み合わせから排他的に派生するデータ転送オブジェクト(DTO)には主キーがありません
  2. 着信DTOには主キーはありません

したがって、ドメインモデルで本当に純粋でプライマリキーを削除する場合、そのプライマリキーの一意のインデックスに関してすべての要求を処理できるように準備する必要があると言っても安全ですか?

別の言い方をすれば、ドメインモデルでPKを削除した後に特定のオブジェクトを識別するための正しいアプローチは、次のソリューションのどれですか

  1. 他の属性で処理する必要があるオブジェクトを識別できること
  2. DTOで主キーを取得します。つまり、永続性からドメインへのマッピング時にPKを削除し、ドメインからDTOへのマッピング時にPKを再結合しますか?

編集:これを具体的にしましょう。

私のドメインモデルがあると言うVoIPProviderなどの分野が含まれNameDescriptionURL、などのような参照ProviderTypePhysicalAddressおよびを、Transactions

ここで、特権ユーザーがVoIPProviders を管理できるWebサービスを構築したいとします。

おそらく、この場合、ユーザーフレンドリーなIDは役に立ちません。結局のところ、VoIPプロバイダーは、コンピューターの意味で名前が明確になり、ビジネス上の理由から人間の意味でも十分に区別される傾向がある企業です。それで、ユニークVoIPProviderは完全に決定されると言うことで十分かもしれません(Name, URL)PUT api/providers/voip特権ユーザーがVoIPプロバイダーを更新できるようにする方法が必要だとしましょう。は、を送信しますVoIPProviderDTO。これには、からのすべてのフィールドではなく、多くのフィールドが含まれVoIPProviderます。しかし、私は彼らの心を読むことができず、彼らはまだ私たちが話しているプロバイダーを教えてくれる必要があります。

私は2つ(おそらく3つ)のオプションを持っているようです:

  1. ドメインモデルにプライマリキーまたは代替キーを含めてDTOに送信します。逆も同様です。
  2. 次のような一意のインデックスを使用して、関心のあるプロバイダーを特定します。 (Name, Url)
  3. 永続層に関する実装の詳細を公開しない方法で、永続層、ドメイン、およびDTOの間を常にマッピングできる何らかの中間オブジェクトを導入します。たとえば、ドメインからDTOに移動するときにメモリ内の一時識別子を導入し、

1
熟考:適切なビジネスキーが存在するときにサロゲートPKを使用すると、ドメインの専門家とのコミュニケーションが不十分になることがよくあります。他の方法ではなく、ORMフレームワークで作業することになります。
Tulainsコルドバ

@ user61852はORMに関係なく、たとえあなたが本当に低レベルであっても、データベース層の実装には主キーが必要です。したがって、サロゲートPKは特定の永続化メカニズムで使用される実際のPKよりも利点があることに同意しますが、そのPKが実際に意味のあるビジネスオブジェクトを表している場合、それは必然的に一意であり、したがって、少なくとも1つの一意のビジネス関連プロパティを定義しますいや、いや?
tacos_tacos_tacos 14

1
サロゲートのすべての利点はコンピューター関連であり、人間関連ではありません。
Tulainsコルドバ

2
@ user61852:私は100%に同意します(何か違うことを書きましたか?)。通信手段として、「ビジネスキー」を使用します。ビジネスキーにも独自の制約を追加します。ただし、ビジネスキーを使用してデータベース参照を実際に実装することは避けてください。
Doc Brown 14

2
ビジネスキーは永遠に一意です-そうでない限り。これが発生したときにビジネスキーをプライマリとして使用している場合、ビジネスルールの変更により多くのものが破壊されます。
psr 14

回答:


31

これが、これを解決する方法です(「ドメイン駆動設計」という用語さえ考案されていない15年以上):

  • ドメインモデルを特定のプログラミング言語のデータベース実装またはクラスモデルにマッピングする場合、「リレーショナルテーブルにマッピングされた各ドメインオブジェクトについて、主キーは「TablenameID」のような単純で一貫したルールがあります。
  • この主キーは完全に人工であり、常に同じタイプでありビジネス上の意味はありません -単なる代理キー
  • ドメインモデルの「グラフィカルバージョン」(ドメインの専門家と話すために使用するもの)には主キーが含まれていません。それらを専門家に直接公開しないでください(ただし、実際にシステムのコードを実装している人に公開します)。

したがって、技術的な目的(データベースへのリレーションのマッピングなど)で主キーが必要な場合はいつでも使用できますが、「表示」したくない限り、抽象レベルを「ドメインエキスパートモデル」に変更します「。また、「2つのモデル」(1つはPKを使用し、もう1つは使用しない)を維持する必要はありません。代わりに、PKのないモデルのみを維持し、コードジェネレーターを使用してDBのDDLを作成します。これにより、マッピングルールに従ってPKが自動的に追加されます。

これは、代理以外に、追加の「OrderNumber」のような「ビジネスキー」を追加することを禁止しないことに注意してくださいOrderID。技術的には、これらのビジネスキーは、データベースへのマッピング時に代替キーになります。他のテーブルへの参照を作成するためにこれらを使用することは避けてください。可能な場合は常に代理キーを使用することをお勧めします。これにより、非常に簡単になります。

コメント:代理キーを使用してレコードを識別することはビジネス関連の操作ではなく、純粋に技術的な操作です。これを明確にするため、例を見てください:追加の一意の制約を定義しない限り、(name、url)の同じ組み合わせで異なるVoIPProviderIDを持つ2つのVoIPProviderオブジェクトを持つことができます。


返された永続化オブジェクト(エンティティ、またはテーブル行モデルなど)からドメインオブジェクトを取得し、DTOに戻り、それを取得し、永続化に戻るステップについてはどうでしょうか。これは、各永続化操作の解決を必要とする代理キー(つまり、ビジネス指向の一意性の定義)を介して行われますか?
tacos_tacos_tacos 14

1
@tacos_tacos_tacos:VoIPProviderの例に固執しましょう。少なくとも「実装側」で、実際に「VoIPProviderID」をDTOに追加します(ドメインエキスパート用のグラフィカルバージョンもある場合は、おそらく表示しません)。更新のために、特定のVoIPProviderを識別する標準的な方法は、データベースからデータを取得するときに取得した「VoIPProviderID」によるものです。APIのユーザーが(名前、URL)による識別を希望する場合は、追加で提供します。...
Doc Brown 14

...そして、パフォーマンスが現実的で測定可能な問題になると思われる場合は、VoIPProviderIDへのマッピング(name、url)をどこかにキャッシュすることも検討できます。しかし、そのような最適化を事前に、時期尚早に実装することはお勧めしません。
Doc Brown 14

1
ナチュラルキーは非常に説得力があるように見える場合がありますが、燃やすのは簡単すぎます(たとえば、「おっと、今では複数のテナントがあり、それはもはやユニークではありません」)。
ケーシー14

1
@corsiKa:OPが求めたもののコンテキストでは、純粋に自動生成されたキー「OrderID」(領収書には印刷されませんが、データベース参照などの内部のものにのみ使用されます)、および別のビジネスキーを持つことを強くお勧めします「OrderNumber」(たとえば、現在の年のようなものを含むことができます。これは、並べ替えやフィルタリングに使用でき、後で変更/修正でき、領収書に印刷できます)。OPは「ドメイン駆動設計」を要求し、「OrderNumber」はドメインモデルの一部ですが、「OrderID」は実装の詳細にすぎません。
Doc Brown 14

4

いくつかの一意のインデックスによって多くのオブジェクトを識別できる必要がありますが、それは主キーが何であるか(または、少なくとも1つが存在することを意味します)ではありません。

一意のインデックスを使用できるため、PKの大規模な代替としてではなく、DBスキーマをさらに制約できます。PKを公開していないのでtheyいのですが、代わりに一意のキーを公開している場合...実際には別のことをしているわけではありません。(ここでPKとID列が混ざっていないと思いますか?)


永続化されたドメインオブジェクトの特定のインスタンスを含む操作を実行する場合、DTOに明示的に(一意のプライマリまたは代替のユーザーフレンドリーIDのいずれかのキーを介して)含めることができる必要があります。そのテーブルに、またテーブルのインデックスかもしれません)または暗黙的に(値が特定のレコードを一意に識別するフィールドの組み合わせを介して)...さらに、実際的な意味では、DTOで送信する必要があります他の方法(レコードを識別するすべてのフィールドの元の値と新しい値)を行う場合、何らかの形式の変更追跡はありませんか?
tacos_tacos_tacos 14

明示的v暗黙的質問は同じ違いではありませんか?一意のインデックスのように、複数の列にまたがるPKを持つことができます。あなたの目的のためにそれらの間に違いがあるとは思わない。
gbjbaanb 14

もちろん、複数の列にPKを設定することもできます。しかし、私にとっては、ビジネスエンティティである心と魂とは何の関係もないはずのデータベース(ストレージ)について何かを漏らしています。ビジネスエンティティフィールドのタプルがDBのPKにまたがる場合は、素晴らしいです。しかし、必ずしもその逆ではないはずです。
tacos_tacos_tacos 14

あなたはそれを考え直しています。一意のインデックスは、PKと同様にDBスキーマのアーティファクトです。このように考えてください-PKは、最初(またはプライマリ)の一意のインデックスにすぎません。通常、このようなインデックスは1つだけ必要なので、「特別」です。
gbjbaanb 14

確かに、意味のあるドメインオブジェクトは、ビジネス関連のフィールドの少なくとも1つから厳密に識別可能でなければなりません。これがDBのインデックスを定義するという事実は、DBを簡単にクエリするために使用するよりもパフォーマンス上の理由によります。6列の一意のインデックスよりも1列のPKを好むでしょう。 DBA / DBDの利便性のために、PK(または少数のフィールドを持つインデックス)も使用できますか?
tacos_tacos_tacos 14

4

フロントエンドに主キーがないと、バックエンドが送信内容を知る簡単な方法がありません。これを修正するには、データを解析するための余分な作業が大量に必要になります。これにより、パフォーマンスが低下し、おそらくすべてのアイテムにキーを添付するよりも時間がかかり、見苦しくなります。

例として、アプリでメッセージを編集したいとします。主キーを付けずに編集したいメッセージをアプリはどのように知るのでしょうか?オブジェクトの編集は常に行われ、キーなしで行うことはほとんど不可能です。ただし、編集する必要のないオブジェクトがある場合、それが気を散らすと思われる場合はキーをスキップしますが、主キーがあるとパフォーマンスが向上します。


さて、メッセージは極端な例ですが、それでも私たちは知っているだろうMessageSenderMessageRecipientTimeSent-それは一意である必要があります。
tacos_tacos_tacos 14

1
@tacos_tacos_tacosでは、他のテーブルにFKをどのように作成しますか?おそらくUserIdのUsersテーブルにマップされるMessageSenderIdである必要があります。テーブル間のキーとしてUserNameを使用するのは望ましくありません。これは変更される可能性があり、メンテナンスの悪夢になります。これが、通常、主キーのみを使用してテーブルを結合し、別の列を結合しない理由です(確かに例外があります)。このdb構造は引き続き適用する必要があります。これで、アプリのCQRSモデルにいつでもアクセスできます。この場合、ルールが変更されます。特に、イベントソーシングも使用する場合。
CaffGeek 14

4

非ビジネス関連のPKを使用する理由は、ユーザーの希望を簡単かつ一貫性のある方法で判断できるようにするためです。

あなたがコメントで返信したのを見ます:MessageSender、MessageRecipient、TimeSent(メッセージ用)。この方法では、あいまいさを解消できません(たとえば、頻繁に発生する何かでトリガーされるシステム生成メッセージなど)。そして、ここでMessageSenderとMessageRecipientをどのように検証しますか?FirstName、Lastname、DateOfBirthを使用してそれらを検証すると、最終的には、同じ日に2人の人がまったく同じ名前で生まれるという状況に陥ります。言うまでもなく、という名前のメッセージがある状況に陥りますtacostacostacos-America-1980-Doc Brown-France-1965-23/5/2014-11:43:54.003UTC+200。それは名前の怪物であり、あなたがそれらのうちの1つだけを持っているという保証はまだない。

主キーを使用する理由は、入力されたデータに関係なく、ソフトウェアの存続期間中一意であることを認識し、予測可能な形式になることを知っているためです(上記のキーにダッシュがあるとどうなるかユーザー名で?システム全体がクソになります)。

ユーザーにIDを提示する必要はありません。これを非表示にできます(必要に応じて、URLを使用して簡単に表示できます)。

PKが非常に便利なもう1つの理由は、上記から推測できることです。PKは、ユーザーが生成したコードをコンピューターに強制的に解釈させる必要がないようにします。中国のユーザーがあなたのコードを使用し、たくさんの漢字を入力した場合、コードは突然これらを内部で操作する必要はありませんが、システムが生成したGUIDを使用するだけです。アラビア語の文章を入力するアラビア語のユーザーがいる場合、システムは内部でそれに対処する必要はありませんが、基本的にそこにいることを無視できます。

他の人が言ったように、Guidは固定サイズで内部的に保存できるものです。何を使っているのか知っており、普遍的に使用できるものです。特定の識別子を作成して保存する方法に関する設計ルールを作成する必要はありません。システムが名前の最初の10文字のみを使用する場合、Michael GuggenheimerとMichael Gugsteinの間に違いは見られず、これら2を混乱させます。任意の長さで切断すると、混乱する可能性があります。ユーザー入力を制限すると、ユーザー制限の問題が発生する可能性があります。

Dynamics CRMなどの既存のシステムを見ると、ユーザーが内部レコード(PK)を使用して単一のレコードを呼び出すこともできます。ユーザーがIDを含まないクエリを持っている場合、可能な回答の配列を返し、ユーザーがそれから選択できるようにします。あいまいさが生じる可能性がある場合は、ユーザーに選択肢を提供します。

最後に、それはまた、あいまいさによるセキュリティのビットでもあります。レコードIDがわからない場合は、推測するしかありません。IDが推測しやすい場合(作成された情報が公開されているため)、誰でも変更できます。従来のCSRFまたはXSSメソッドを使用してユーザーを変更することもできます。現在、セキュリティでは、ライブバージョンを公開する前にセキュリティを考慮して軽減する必要がありますが、悪用の可能性をさらに低くする必要があります。


1

外部システムの識別子を発行するときは、データベースの主キーを直接公開するのではなく、URI、または代わりにURIと同じプロパティを持つキーまたはキーのセットのみを指定する必要があります(以降は参照します) URIと同じプロパティを持つURIまたはキーまたはキーのセットの両方、つまり、以下のURIは必ずしもRFC 3986 URIを意味するわけではありません)。

URIにはオブジェクトの主キーが含まれる場合と含まれない場合があり、実際には代替キーで構成される場合とそうでない場合があります。どうでもいい。重要なのは、URIを生成するシステムのみがURIを分割または結合して、参照されるオブジェクトが何であるかを理解できることです。外部システムは常にURIを不透明な識別子として使用する必要があります。人間のユーザーが、URIの一部が実際にデータベースの代理キーであること、複数のビジネスキーがまとまって構成されていること、または実際にそれらの値のbase64であることを識別できるかどうかは関係ありません。これらは無関係です。重要なのは、識別子を使用するために識別子が何を意味するかを理解するために外部システムを必要とするべきではないということです。外部システムは、識別子内のコンポーネントを解析したり、システム内の何かを参照するために識別子を他の識別子と組み合わせたりする必要はありません。

GUIDを使用するとこの基準の一部が満たされますが、GUIDのような識別子は、システム内であってもオブジェクトに逆参照するのが困難な場合があるため、GUIDのようなシステムに対しても不透明なキーは、クライアントが実際にURI /識別子を解析する場合にのみ使用する必要がありますセキュリティリスクをもたらします。

VoIPの例に戻り、VoIPプロバイダーは(VoIPProviderID)または(名前、URL)または(GUID)によって一意に決定できるとしましょう。外部システムがVoIPプロバイダーを更新する必要がある場合、PUT / provider / by-id / 1234を渡すことができます。またはPUT /provider/foo-voip/bar-domain.comまたはPUT /3F2504E0-4F89-41D3-9A0C-0305E82C3301、システムは外部システムがVoIPProviderを更新することを理解します。これらのURIはシステムによって生成され、システムのみがそれらがすべて同じことを意味することを理解する必要があります。外部システムは、URIにあるものを基本的にaとして扱うだけPUT <whatever>です。

異なるスキーマを持つ異なるテーブルに異なるVoIPプロバイダーのデータが格納されているとします(したがって、格納されるテーブルに基づいて完全に異なるキーのセットが各VoIPプロバイダーを識別します)。URIがある場合、システムが特定のVoIPプロバイダーを識別する方法に関係なく、外部システムからURIに均一にアクセスできます。外部システムにとっては、すべて不透明なポインターです。

システムがURIを使用してこのような方法でオブジェクトを参照する場合、システムの実装方法に関して何もリークしていません。URIを生成すると、クライアントは単純にそれを返します。


0

私はこの非常に不正確で素朴な声明をターゲットにしなければなりません:

おそらく、この場合、ユーザーフレンドリーなIDは役に立ちません。結局のところ、VoIPプロバイダーは、コンピューターの意味で名前が明確になり、ビジネス上の理由から人間の意味でも十分に区別される傾向がある企業です。

名前は頻繁に変更されるため、キーとしてひどいです。会社はその存続期間中に多くの名前を持ち、合併、分割、再度合併し、特定の税務目的で従業員0人を含む別個の子会社を作成する場合があります。

それから、ランドマークApple vs. Appleが示すように、会社名は遠隔地でも一意ではないという事実になります。

優れたオブジェクトリレーショナルマッパーまたはフレームワーク、主キー抽象化して非表示にしますが、それらは存在し、通常はデータベース内のオブジェクトを一意に識別する唯一の方法になります。

参考のために、djangoがこれを処理する方法を好みます。

class VoipProvider(models.Model):
    name=fields.TextField()
    address=fields.TextField()

class Customer(models.Model):
    name=fields.TextField()
    address=fields.TextField()
    voipProvider=fields.ForeignKeyField(VoipProvider)

このように、顧客のプロバイダーの詳細には、次のコードを使用してアクセスできます。

myCustomer.voipProvider.name #returns the name of the customers VOIP Provider.

主キー/外部キーは表示されませんが、それらはそこにあり、アイテムにアクセスするために使用できますが、抽象化されています。


あなたは絶対に正しいですが、私は「いくつかのドメインでは、常に一意の値の自然なタプルがあるかもしれない」ことを意味したと思います。
tacos_tacos_tacos 14

0

私たちは、DBの観点からこの問題をまだ間違って見ていることが多いと思います。ああ、自然なキーはないので、代理キーを作成する必要があります。いえいえ、代理キーをドメインオブジェクトに公開することはできません。

ただし、ビジネス(ドメイン)オブジェクトに自然キーがない場合は、与えられるべきかもしれません。これは2つのビジネスドメインの問題です。まず、データベースがなくても物事にはIDが必要です。第二に、永続性はドメインには見えない抽象的な概念であると偽装しようとしますが、現実は永続性は依然としてビジネス概念であるということです。明らかに、選択された自然キーがプライマリキーとしてDBによってサポートされていない問題があります(一部のシステムのGUIDなど)。この場合、代理キーを追加する必要があります。

したがって、顧客は整数IDを持っていますが、DBからドメインにリークしているので気分が悪くなるのではなく、非常によく似た場所になります。 ID、および必要に応じて、それをDBに永続化します。たとえば、顧客IDの名前変更をサポートするために、サロゲートを自由に導入できます。

また、このアプローチは、ドメインオブジェクトが永続層に到達し、IDを持たない場合、おそらく何らかの値オブジェクトであるため、IDを必要としないことを意味します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.