Après quelques réponses utiles pour savoir si je devrais utiliser un objet de domaine ou un identifiant unique comme paramètre de méthode / fonction ici Identifiant vs objet de domaine comme paramètre de méthode , j'ai une question similaire concernant les membres (la discussion des questions précédentes n'a pas réussi à couvrir cela). Quels sont les avantages et les inconvénients de l'utilisation d'identifiants uniques en tant que membre par rapport à l'objet en tant que membre. Je demande en référence aux langages fortement typés, comme Scala / C # / Java. Dois-je avoir (1)
User( id: Int, CurrentlyReadingBooksId: List[Int])
Book( id: Int, LoanedToId: Int )
ou (2), préféré à (1) Après avoir traversé: Faut-il définir des types pour tout?
User( id: UserId, CurrentlyReadingBooksId: List[ BookId] )
Book( id: BookId, LoanedToId: UserId )
ou (3)
User( id: Int, CurrentlyReadingBooks: List[Book])
Book( id: Int, LoanedTo: User)
Bien que je ne puisse pas penser aux avantages d'avoir l'objet (3), l'un des avantages d'avoir les ID (2) et (1) est que lorsque je crée l'objet utilisateur à partir de la base de données, je n'ai pas à créer l'objet livre, qui peut à son tour dépendre de l'objet utilisateur lui-même, créant une chaîne sans fin. Existe-t-il une solution générique à ce problème à la fois pour le SGBDR et pour No-SQL (s'ils sont différents)?
Sur la base de certaines réponses jusqu'à présent, reformulant ma question: (avec l'utilisation d'ID censés être dans des types enveloppés) 1) Toujours utiliser des ID? 2) Toujours utiliser des objets? 3) Utiliser des identifiants lorsqu'il existe un risque de récursivité dans la sérialisation et la désérialisation, mais utiliser des objets autrement? 4) Autre chose?
EDIT: Si vous répondez que les objets doivent être utilisés toujours ou dans certains cas, veuillez vous assurer de répondre à la plus grande préoccupation que d'autres répondeurs ont postée => Comment obtenir des données de la base de données
la source
Réponses:
Les objets de domaine en tant qu'identités créent des problèmes complexes / subtils:
Sérialisation / désérialisation
Si vous stockez des objets sous forme de clés, la sérialisation du graphe d'objets sera extrêmement compliquée. Vous obtiendrez des
stackoverflow
erreurs lors d'une sérialisation naïve vers JSON ou XML à cause de la récursivité. Vous devrez ensuite écrire un sérialiseur personnalisé qui convertit les objets réels pour utiliser leurs identifiants au lieu de sérialiser l'instance d'objet et de créer la récursivité.Passez des objets pour la sécurité des types mais ne stockez que les ID, vous pouvez alors avoir une méthode d'accesseur qui charge paresseusement l'entité associée lors de son appel. La mise en cache de deuxième niveau prendra en charge les appels suivants.
Fuites de référence subtiles:
Si vous utilisez des objets de domaine dans des constructeurs comme vous en avez, vous créerez des références circulaires qui seront très difficiles à permettre à la mémoire d'être récupérée pour les objets qui ne sont pas activement utilisés.
Situation idéale:
Identifiants opaques vs int / long:
Un
id
devrait être un identifiant complètement opaque qui ne porte aucune information sur ce qu'il identifie. Mais il devrait permettre de vérifier qu'il s'agit d'un identifiant valide dans son système.Les types bruts brisent ceci:
int
,long
EtString
sont les types d'identifiants bruts les plus couramment utilisés dans le système SGBDR. Il existe une longue histoire de raisons pratiques qui remontent à des décennies et ce sont toutes des compromis qui s'inscrivent dans l'épargnespace
ou l'épargnetime
ou les deux.Les identifiants séquentiels sont les pires contrevenants:
Lorsque vous utilisez un identifiant séquentiel, vous intégrez par défaut des informations sémantiques temporelles dans l'identifiant. Ce qui n'est pas mal tant qu'il n'est pas utilisé. Lorsque les gens commencent à écrire une logique métier qui trie ou filtre la qualité sémantique de l'identifiant, ils mettent en place un monde de douleur pour les futurs responsables.
String
les champs sont problématiques parce que les concepteurs naïfs intégreront des informations dans le contenu, généralement la sémantique temporelle également.Il est donc impossible de créer un système de données distribué également, car il
12437379123
n'est pas unique au monde. Les chances qu'un autre nœud d'un système distribué crée un enregistrement avec le même numéro est à peu près garantie lorsque vous obtenez suffisamment de données dans un système.Ensuite, les hacks commencent à travailler autour de lui et le tout se transforme en un tas de désordre fumant.
Ignorant les énormes systèmes distribués ( clusters ), cela devient un véritable cauchemar lorsque vous essayez également de partager les données avec d'autres systèmes. Surtout lorsque l'autre système n'est pas sous votre contrôle.
Vous vous retrouvez avec exactement le même problème, comment rendre votre identifiant globalement unique.
L'UUID a été créé et normalisé pour une raison:
UUID
peut souffrir de tous les problèmes énumérés ci-dessus en fonction de celui queVersion
vous utilisez.Version 1
utilise une adresse MAC et une heure pour créer un identifiant unique. C'est mauvais car il contient des informations sémantiques sur l'emplacement et l'heure. Ce n'est pas en soi un problème, c'est lorsque des développeurs naïfs commencent à s'appuyer sur ces informations pour la logique métier. Cela fuit également des informations qui pourraient être exploitées lors de toute tentative d'intrusion.Version 2
utilise un utilisateurUID
ouGID
et domianUID
ouGUI
à la place du temps à partir deVersion 1
cela est tout aussi mauvais queVersion 1
pour les fuites de données et risquer que ces informations soient utilisées dans la logique métier.Version 3
est similaire mais remplace l'adresse MAC et l'heure par unMD5
hachage d'un tableau debyte[]
quelque chose qui a définitivement une signification sémantique. Il n'y a aucune fuite de données à craindre, lebyte[]
ne peut pas être récupéré à partir duUUID
. Cela vous donne un bon moyen de créer de manière déterministe desUUID
instances de formulaire et une clé externe quelconque.Version 4
est basé uniquement sur des nombres aléatoires, ce qui est une bonne solution, il ne contient absolument aucune information sémantique, mais il n'est pas recréable de façon déterministe.Version 5
est commeVersion 4
mais utilisesha1
au lieu demd5
.Clés de domaine et clés de données transactionnelles
Ma préférence pour les identifiants d'objet de domaine est d'utiliser
Version 5
ouVersion 3
si l' utilisation est restreinteVersion 5
pour une raison technique.Version 3
est idéal pour les données de transaction qui peuvent être réparties sur de nombreuses machines.Sauf si vous êtes contraint par l'espace, utilisez un UUID:
Ils sont garantis uniques, en vidant les données d'une base de données et en les rechargeant dans une autre, vous n'avez jamais eu à vous soucier des identifiants en double qui référencent réellement différentes données de domaine.
Version 3,4,5
sont complètement opaques et c'est ainsi qu'ils devraient être.Vous pouvez avoir une seule colonne comme clé primaire avec un
UUID
, puis vous pouvez avoir des index uniques composés pour ce qui aurait été une clé primaire composite naturelle.Le stockage ne doit pas nécessairement l'être
CHAR(36)
non plus. Vous pouvez stocker leUUID
dans un champ natif octet / bit / numéro pour une base de données donnée tant qu'il est toujours indexable.Héritage
Si vous avez des types bruts et que vous ne pouvez pas les changer, vous pouvez toujours les résumer dans votre code.
L'utilisation de l'un
Version 3/5
d'entreUUID
vous peut passer leClass.getName()
+ enString.valueOf(int)
tant que abyte[]
et avoir une clé de référence opaque qui est récréative et déterministe.la source
C-> A -> B -> A
etB
est mis dans unCollection
alorsA
et que tous ses enfants sont toujours accessibles, ces choses ne sont pas complètement évidentes et peuvent conduire à des fuites subtiles .GC
est le moindre des problèmes, la sérialisation et la désérialisation du graphe est un cauchemar de complexité.Oui, il y a des avantages dans les deux sens, et il y a aussi un compromis.
List<int>
:User
Users
tableList<Book>
:Si vous n'avez aucun problème de mémoire ou de processeur avec
List<Book>
lequel j'irais , le code qui utilise lesUser
instances sera plus propre.Faire des compromis:
Lorsque vous utilisez Linq2SQL, le code généré pour l'entité User aura un fichier
EntitySet<Book>
qui est chargé paresseusement lorsque vous y accédez. Cela devrait garder votre code propre et l'instance utilisateur petite (en termes d'empreinte mémoire).la source
Règle générale courte et simple:
Les ID sont utilisés dans les DTO .
Les références d'objet sont généralement utilisées dans les objets de couche Logique de domaine / Logique d'entreprise et Interface utilisateur.
C'est l'architecture commune dans les projets plus grands et suffisamment entreprenants. Vous aurez des mappeurs qui vont et viennent ces deux types d'objets.
la source
BookRepository
et unUserRepository
. Vous appellerez toujoursmyRepository.GetById(...)
ou similaire, et le référentiel créera l'objet et chargera ses valeurs à partir d'un magasin de données, ou le récupérera à partir d'un cache. De plus, les objets enfants sont principalement chargés paresseusement, ce qui évite également d'avoir à traiter avec des références circulaires directes au moment de la construction.