Después de un par de respuestas útiles sobre si debería usar un objeto de dominio o una identificación única como método / parámetro de función aquí Identificador frente al objeto de dominio como parámetro de método , tengo una pregunta similar sobre miembros: la discusión de preguntas anteriores no logró cubrir esto). ¿Cuáles son las ventajas y desventajas de usar ID únicos como miembro frente a objeto como miembro? Pregunto en referencia a lenguajes fuertemente tipados, como Scala / C # / Java. Debería tener (1)
User( id: Int, CurrentlyReadingBooksId: List[Int])
Book( id: Int, LoanedToId: Int )
o (2), preferido a (1) Después de pasar: ¿Deberíamos definir tipos para todo?
User( id: UserId, CurrentlyReadingBooksId: List[ BookId] )
Book( id: BookId, LoanedToId: UserId )
o (3)
User( id: Int, CurrentlyReadingBooks: List[Book])
Book( id: Int, LoanedTo: User)
Si bien no puedo pensar en los beneficios de tener el objeto (3), uno de los beneficios de tener ID (2) y (1) es que cuando estoy creando el objeto Usuario desde la base de datos, no tengo que crear el objeto Libro, que a su vez, puede depender del objeto Usuario en sí mismo, creando una cadena sin fin. ¿Existe una solución genérica para este problema tanto para RDBMS como para No-SQL (si son diferentes)?
Basado en algunas respuestas hasta ahora, reformulando mi pregunta: (con el uso de ID que se supone que están en tipos envueltos) 1) ¿Siempre usa ID? 2) ¿Usar siempre objetos? 3) ¿Usa IDs cuando existe riesgo de recurrencia en la serialización y deserialización, pero usa objetos de otra manera? 4) ¿Algo más?
EDITAR: si responde que los objetos deben usarse siempre o en algunos casos, asegúrese de responder la mayor preocupación que otros respondedores han publicado => Cómo obtener datos de DB
fuente
Respuestas:
Los objetos de dominio como identificadores crean algunos problemas complejos / sutiles:
Serialización / Deserialización
Si almacena objetos como claves, la serialización del gráfico de objetos será extremadamente complicada. Obtendrá
stackoverflow
errores cuando realice una serialización ingenua a JSON o XML debido a la recursividad. Luego tendrá que escribir un serializador personalizado que convierta los objetos reales para usar sus identificadores en lugar de serializar la instancia del objeto y crear la recursividad.Pase objetos por seguridad de tipo pero solo almacene identificadores, luego puede tener un método de acceso que perezosamente carga la entidad relacionada cuando se llama. El almacenamiento en caché de segundo nivel atenderá las llamadas posteriores.
Fugas de referencia sutiles:
Si usa objetos de dominio en constructores como los que tiene allí, creará referencias circulares que serán muy difíciles de permitir que se recupere la memoria para objetos que no se usan activamente.
Situación ideal:
ID opacos vs int / long:
Un
id
debe ser un identificador completamente opaco que no contenga información sobre lo que identifica. Pero debería ofrecer alguna verificación de que es un identificador válido en su sistema.Los tipos sin procesar rompen esto:
int
,long
yString
son los tipos sin formato más utilizados para identificadores en el sistema RDBMS. Hay una larga historia de razones prácticas que se remontan a décadas y todas son compromisos que encajan en el ahorrospace
o el ahorrotime
o en ambos.Los identificadores secuenciales son los peores delincuentes:
Cuando utiliza una identificación secuencial, está empaquetando información semántica temporal en la identificación de forma predeterminada. Que no esta mal hasta que se usa. Cuando las personas comienzan a escribir una lógica comercial que clasifica o filtra la calidad semántica de la identificación, están creando un mundo de dolor para los futuros mantenedores.
String
los campos son problemáticos porque los diseñadores ingenuos empaquetarán información en los contenidos, generalmente también la semántica temporal.Esto hace que sea imposible crear un sistema de datos distribuidos también, porque no
12437379123
es único a nivel mundial. Las posibilidades de que otro nodo en un sistema distribuido cree un registro con el mismo número está prácticamente garantizado cuando obtiene suficientes datos en un sistema.Luego, los hacks comienzan a solucionarlo y todo se convierte en un montón de vapor.
Ignorando los grandes sistemas distribuidos ( clusters ), se convierte en una pesadilla completa cuando comienza a tratar de compartir los datos con otros sistemas también. Especialmente cuando el otro sistema no está bajo su control.
Termina con exactamente el mismo problema, cómo hacer que su identificación sea globalmente única.
UUID fue creado y estandarizado por una razón:
UUID
puede sufrir todos los problemas enumerados anteriormente dependiendo de cuálVersion
use.Version 1
usa una dirección MAC y tiempo para crear una identificación única. Esto es malo porque lleva información semántica sobre la ubicación y la hora. Eso no es en sí un problema, es cuando los desarrolladores ingenuos comienzan a confiar en esa información para la lógica empresarial. Esto también filtra información que podría ser explotada en cualquier intento de intrusión.Version 2
el uso de un usuarioUID
o unGID
domianUID
oGUI
en lugar del tiempo a partir deVersion 1
esto es tan malo comoVersion 1
para la fuga de datos y el riesgo de que esta información se use en la lógica empresarial.Version 3
es similar pero reemplaza la dirección MAC y el tiempo con unMD5
hash de una serie debyte[]
algo que definitivamente tiene un significado semántico. No hay pérdida de datos de la que preocuparse,byte[]
no se puede recuperar delUUID
. Esto le brinda una buena forma de crear deUUID
forma determinista instancias de forma y clave externa de algún tipo.Version 4
se basa solo en números aleatorios, lo cual es una buena solución, no contiene absolutamente ninguna información semántica, pero no es reproducible determinísticamente.Version 5
es igualVersion 4
pero usa ensha1
lugar demd5
.Claves de dominio y claves de datos transaccionales
Mi preferencia por los identificadores de objeto de dominio es usar
Version 5
o,Version 3
si está restringido,Version 5
por alguna razón técnica.Version 3
es excelente para los datos de transacciones que pueden distribuirse en muchas máquinasA menos que esté limitado por el espacio, use un UUID:
Están garantizados como únicos, volcando datos de una base de datos y volviendo a cargarlos en otra, nunca tuvo que preocuparse por identificadores duplicados que realmente hacen referencia a diferentes datos de dominio.
Version 3,4,5
son completamente opacos y así es como deberían ser.Puede tener una sola columna como clave principal con ay
UUID
luego puede tener índices únicos compuestos para lo que habría sido una clave primaria compuesta natural.El almacenamiento no tiene que ser
CHAR(36)
tampoco. Puede almacenarloUUID
en un campo byte / bit / número nativo para una base de datos determinada siempre que sea indexable.Legado
Si tiene tipos sin procesar y no puede cambiarlos, aún puede abstraerlos en su código.
El uso de uno
Version 3/5
deUUID
ustedes puede pasar elClass.getName()
+String.valueOf(int)
como abyte[]
y tener una clave de referencia opaca que sea recreable y determinista.fuente
C-> A -> B -> A
yB
se pone en unCollection
entoncesA
y todos sus hijos siguen siendo accesibles, estas cosas no son completamente obvias y pueden conducir a fugas sutiles .GC
Es el menor de los problemas, la serialización y deserialización del gráfico es una pesadilla de complejidad.Sí, hay beneficios en ambos sentidos, y también hay un compromiso.
List<int>
:User
Users
tablaList<Book>
:Si no tiene problemas de memoria o CPU con los que iría
List<Book>
, el código que usa lasUser
instancias será más limpio.Compromiso:
Al usar Linq2SQL, el código generado para la entidad Usuario tendrá una
EntitySet<Book>
carga lenta cuando acceda a él. Esto debería mantener su código limpio y la instancia de Usuario pequeña (huella de memoria inteligente).fuente
Breve y simple regla de oro:
Las ID se utilizan en DTO s.
Las referencias a objetos generalmente se usan en la lógica de dominio / lógica de negocios y los objetos de capa de la interfaz de usuario.
Esa es la arquitectura común en proyectos más grandes y suficientemente empresariales. Tendrá mapeadores que se traducirán de un lado a otro de estos dos tipos de objetos.
fuente
BookRepository
y aUserRepository
. Siempre llamarámyRepository.GetById(...)
o similar, y el repositorio creará el objeto y cargará sus valores desde un almacén de datos, o lo obtendrá de un caché. Además, los objetos secundarios son en su mayoría de carga lenta, lo que también evita tener que lidiar con referencias circulares directas en el momento de la construcción.