メンバー:一意のIDとドメインオブジェクトを使用する


10

ここで、メソッド/関数パラメーターとしてドメインオブジェクトまたは一意のIDを使用する必要があるかどうかに関するいくつかの有用な回答の後、IDとドメインオブジェクトをメソッドパラメーターとして使用します。これをカバーします)。一意のIDをメンバーとして使用するか、オブジェクトをメンバーとして使用することの長所と短所は何ですか。私はScala / C#/ Javaのような強く型付けされた言語を参照して尋ねています。持っておくべき(1)

User( id: Int, CurrentlyReadingBooksId: List[Int])
Book( id: Int, LoanedToId: Int )

または(2)、(1)より優先:通過後:すべてのタイプを定義する必要がありますか?

User( id: UserId, CurrentlyReadingBooksId: List[ BookId] )
Book( id: BookId, LoanedToId: UserId )

または(3)

User( id: Int, CurrentlyReadingBooks: List[Book]) 
Book( id: Int, LoanedTo: User)

オブジェクト(3)を持つメリットは考えられませんが、ID(2)および(1)を持つメリットの1つは、DBからUserオブジェクトを作成するときにBookオブジェクトを作成する必要がないことです。次に、Userオブジェクト自体に依存し、無限のチェーンを作成します。RDBMSとNo-SQLの両方でこの問題に対する一般的な解決策はありますか(それらが異なる場合)?

これまでのいくつかの回答に基づいて、私の質問を言い換えると:(ラップされたタイプであるはずのIDを使用して)1)常にIDを使用しますか?2)常にオブジェクトを使用しますか?3)シリアライズとデシリアライズで再帰のリスクがある場合はIDを使用しますが、それ以外の場合はオブジェクトを使用しますか?4)他に何かありますか?

編集:オブジェクトを常にまたは一部のケースで使用する必要があると答える場合は、他の回答者が投稿した最大の懸念に必ず答えてください=> DBからデータを取得する方法


1
良い質問をありがとう、興味を持ってこれをフォローするのを楽しみにしています。あなたのユーザー名が "user18151"であるのは少し残念です、この種のユーザー名を持つ人々はいくつかによって無視されます:)
bjfletcher

@bjfletcherありがとうございます。私はそのしつこい知覚を私自身も持っていましたが、なぜそれが私に起こったのではありません!
2015年

回答:


7

IDとしてのドメインオブジェクトは、いくつかの複雑で微妙な問題を引き起こします。

シリアライゼーション/デシリアライゼーション

オブジェクトをキーとして保存すると、オブジェクトグラフのシリアル化が非常に複雑になります。stackoverflow再帰のために、JSONまたはXMLへの単純なシリアル化を実行すると、エラーが発生します。次に、オブジェクトインスタンスをシリアル化して再帰を作成する代わりに、実際のオブジェクトをIDを使用するように変換するカスタムシリアライザーを作成する必要があります。

型の安全性のためにオブジェクトを渡しますが、IDのみを保存します。これにより、呼び出されたときに関連エンティティを遅延ロードするアクセサーメソッドを使用できます。2番目のレベルのキャッシュは、後続の呼び出しを処理します。

微妙な参照リーク:

存在するようなコンストラクターでドメインオブジェクトを使用すると、アクティブに使用されていないオブジェクトのメモリを回収することを非常に困難にする循環参照が作成されます。

理想的な状況:

不透明なIDとint / long:

idそれは識別するものについて何の情報も運ばない完全に不透明識別子でなければなりません。ただし、システムで有効な識別子であることを確認する必要があります。

生の型はこれを壊します:

intlongおよびStringRDBMSシステムで識別子に最も一般的に使用されるrawタイプです。数十年前の実用的な理由には長い歴史があり、それらはすべて、節約spaceまたは節約、timeあるいはその両方に当てはまる妥協案です。

シーケンシャルIDは最悪の違反者です。

シーケンシャルIDを使用すると、デフォルトで一時的な意味情報がIDにパックされます。それは使用されるまで悪くありません。IDのセマンティック品質をソートまたはフィルタリングするビジネスロジックの作成を始めると、将来のメンテナーに苦痛の世界をもたらします。

String 素朴なデザイナーは情報をコンテンツにパックするため、フィールドには問題があります。通常は時間的セマンティクスも含まれます。

これらはグローバルに一意で12437379123ないため、分散データシステムを作成することも不可能です。分散システムの別のノードが同じ番号のレコードを作成する可能性は、システムで十分なデータを取得したときにほぼ保証されます。

その後、ハックがそれを回避し始め、すべてが蒸し混乱の山に展開します。

巨大な分散システム(クラスター)を無視すると、他のシステムとデータを共有しようとするときに完全な悪夢になります。特に、他のシステムが管理できない場合。

あなたはまったく同じ問題、つまりあなたのIDをグローバルにユニークにする方法で終わります。

UUIDが作成され、次の理由で標準化されました:

UUIDVersion使用する方法によっては、上記のすべての問題が発生する可能性があります。

Version 1MACアドレスと時間を使用して、一意のIDを作成します。これは、場所と時間に関するセマンティック情報を含んでいるため、悪いです。それ自体は問題ではなく、単純な開発者がビジネスロジックのためにその情報に依存し始めるときです。これにより、侵入の試みで悪用される可能性のある情報も漏洩します。

Version 2ユーザーUIDまたはGIDand domianを使用するか、UIDまたはこれGUIからの時間の代わりにこれを使用すると、データ漏洩とVersion 1同じくらい悪いVersion 1情報であり、この情報がビジネスロジックで使用されるリスクがあります。

Version 3似ていますが、MACアドレスと時間を、意味的に意味のあるものからのMD5配列のハッシュに置き換えbyte[]ます。気になるデータ漏えいはなく、byte[]からの復旧はできませんUUID。これにより、UUIDインスタンスフォームとある種の外部キーを確定的に作成することができます。

Version 4 良い解決策である乱数のみに基づいており、意味論的な情報はまったくありませんが、確定的に再作成することはできません。

Version 5と同じですVersion 4が、のsha1代わりに使用しますmd5

ドメインキーとトランザクションデータキー

ドメインオブジェクトIDの私の好みは、使用するVersion 5Version 3Version 5技術的な理由で使用が制限されている場合です。

Version 3 多くのマシンに分散する可能性のあるトランザクションデータに最適です。

スペースの制約がない限り、UUIDを使用します。

それらは一意であることが保証されており、1つのデータベースからデータをダンプし、別のデータベースにリロードすると、実際に異なるドメインデータを参照する重複IDについて心配する必要がありませんでした。

Version 3,4,5 完全に不透明であり、本来あるべき姿です。

1つの列を主キーとしてa UUIDを使用して、自然な複合主キーとなるものに対して複合一意インデックスを作成できます。

ストレージもそうである必要はありませCHAR(36)。あなたは、保存することができUUID、長いそれはまだインデックス可能な限り与えられたデータベースのネイティブのバイト/ビット/番号フィールドに。

レガシー

raw型があり、それらを変更できない場合でも、コード内で抽象化できます。

a Version 3/5を使用するとUUIDClass.getName()+ String.valueOf(int)をaとして渡し、再作成byte[]可能で確定的な不透明な参照キーを持つことができます。


私の質問が明確でなかった場合は非常に残念です。これは非常によく考えられた回答であり、あなたが明らかにそれに時間を費やしたので、私はすべてがより悪い(または実際には良い)と感じます。残念ながらそれは私の質問には合いません、おそらくそれ自体の質問に値するでしょうか?「ドメインオブジェクトのIDフィールドを作成するとき、何に注意すべきですか?」
2015

明確な説明を追加しました。

了解しました。答えに時間を割いていただきありがとうございます。
2015

1
ところで、AFAIK世代別ガベージコレクター(最近のGCシステムの主流はこれだと思います)は、循環参照のGCにそれほど困難を伴うことはないはずです。
2015

1
場合C-> A -> B -> ABに入れてCollection、その後A、そのすべての子供たちがまだ到達可能で、これらのものは完全に明白ではなく、微妙につながることができます漏れますGCは問題が最も少ないため、グラフのシリアライズとデシリアライズは複雑さの悪夢です。

2

はい、どちらの方法にもメリットがあり、妥協点もあります。

List<int>

  • メモリを節約する
  • タイプの初期化の高速化 User
  • データがリレーショナルデータベース(SQL)からのものである場合、ユーザーを取得するために2つのテーブルにアクセスする必要はなく、Usersテーブルのみにアクセスします。

List<Book>

  • 本へのアクセスはユーザーからより速く、本はメモリにプリロードされています。これは、後続の操作を高速化するために、起動を長くする余裕がある場合に便利です。
  • データがHBaseやCassandraなどのドキュメントストアデータベースからのものである場合、読み取られた書籍の値はユーザーレコードにある可能性が高いため、「ユーザーがいる間に」簡単に書籍を入手できます。

私がList<Book>使用するメモリやCPUの懸念がない場合、Userインスタンスを使用するコードはよりクリーンになります。

妥協:

Linq2SQLを使用する場合、エンティティUserに対して生成されたコードには、EntitySet<Book>アクセス時に遅延ロードされるものが含まれます。これにより、コードがクリーンになり、Userインスタンスが小さくなります(メモリフットプリントに関して)。


なんらかのキャッシュを想定すると、プリロードのメリットはnullになります。私はCassandra / HBaseを使用していないのでそれらについて話すことはできませんが、Linq2SQLは非常に特殊なケースです(ただし、この特定のケースでも、一般的なケースでも、レイジーロードが無限連鎖のケースを防ぐ方法はわかりません)
2015年

Linq2SQLの例では、実際にはパフォーマンスの向上は得られず、コードがよりクリーンになります。Cassandra / HBaseのようなドキュメントストアから1対多のエンティティを取得する場合、処理時間の大部分はレコードの検索に費やされるため、そこにいる間に多くのエンティティすべて(書籍、この例)。
ytoledano 2015年

本気ですか?ブックとユーザーを別々に正規化して保存しても、私にとっては、ネットワーク遅延の追加コストのみであるように見えます。いずれにせよ、RDBMSのケースを一般的にどのように処理しますか?(私は質問を編集して明確に言及しました)
0fnt

1

短くて簡単な経験則:

IDはDTOで使用されます。
オブジェクト参照は通常、ドメインロジック/ビジネスロジックおよびUIレイヤーオブジェクトで使用されます。

これは、大規模で企業向けに十分なプロジェクトで一般的なアーキテクチャです。これらの2種類のオブジェクトを前後に変換するマッパーがあります。


よろしくお願いします。残念ながら、私はwikiリンクのおかげでその違いを理解していますが、実際にこれを見たことはありません(大規模な長期プロジェクトで作業したことがないのは確かです)。同じオブジェクトが2つの異なる目的で2つの方法で表現された例はありますか?
2015年

ここでのマッピングに関する実際の質問です:stackoverflow.com/questions/9770041/dto-to-entity-mapping-toolは -と重要な記事は次のようにありますrogeralsing.com/2013/12/01/...
herzmeister

本当に助かります、ありがとう。残念ながら、循環参照を使用したデータのロードがどのように機能するのか、まだわかりませんか?たとえば、ユーザーがブックを参照し、ブックが同じユーザーを参照する場合、このオブジェクトをどのように作成しますか?
2015年

リポジトリパターン。あなたは持っているだろうBookRepositoryUserRepository。常にを呼び出すmyRepository.GetById(...)か、同様にすると、リポジトリがオブジェクトを作成してその値をデータストアからロードするか、キャッシュから取得します。また、子オブジェクトはほとんど遅延読み込みされるため、構築時に直接循環参照を処理する必要もありません。
herzmeister 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.