Miembro: utilice ID únicos frente a objeto de dominio

10

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

0fnt
fuente
1
Gracias por la buena pregunta, esperamos seguir esto con interés. Un poco de vergüenza de que su nombre de usuario es "user18151", las personas con este tipo de nombre de usuario son ignorados por algunos :)
bjfletcher
@bjfletcher Gracias. Tuve esa persistente percepción, pero nunca se me ocurrió por qué.
0fnt

Respuestas:

7

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á stackoverflowerrores 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 iddebe 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, longyString 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 ahorro spaceo el ahorro timeo 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 no12437379123 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:

UUIDpuede sufrir todos los problemas enumerados anteriormente dependiendo de cuál Versionuse.

Version 1usa 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 2el uso de un usuario UIDo un GIDdomian UIDo GUIen lugar del tiempo a partir de Version 1esto es tan malo como Version 1para la fuga de datos y el riesgo de que esta información se use en la lógica empresarial.

Version 3es similar pero reemplaza la dirección MAC y el tiempo con un MD5hash de una serie de byte[]algo que definitivamente tiene un significado semántico. No hay pérdida de datos de la que preocuparse, byte[]no se puede recuperar del UUID. Esto le brinda una buena forma de crear de UUIDforma 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 5es igual Version 4pero usa en sha1lugar demd5 .

Claves de dominio y claves de datos transaccionales

Mi preferencia por los identificadores de objeto de dominio es usar Version 5o, Version 3si está restringido, Version 5por alguna razón técnica.

Version 3 es excelente para los datos de transacciones que pueden distribuirse en muchas máquinas

A 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 UUIDluego 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 almacenarlo UUIDen 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/5de UUIDustedes puede pasar el Class.getName()+ String.valueOf(int)como a byte[]y tener una clave de referencia opaca que sea recreable y determinista.


fuente
Lamento mucho no haber sido claro en mi pregunta, y me siento peor (o realmente bien) porque esta es una respuesta excelente y bien pensada y claramente pasaste tiempo en ello. Desafortunadamente no se ajusta a mi pregunta, ¿tal vez merece una pregunta propia? "¿Qué debo tener en cuenta al crear un campo de identificación para mi objeto de dominio"?
0fnt
Agregué una explicación explícita.
Ya lo pillo. Gracias por dedicar tiempo a la respuesta.
0fnt
1
Por cierto, los recolectores de basura generacionales de AFAIK (que creo que es el sistema GC dominante en estos días) no deberían tener demasiadas dificultades para hacer referencias circulares GC'ing.
0fnt
1
si C-> A -> B -> Ay Bse pone en un Collectionentonces Ay todos sus hijos siguen siendo accesibles, estas cosas no son completamente obvias y pueden conducir a fugas sutiles . GCEs el menor de los problemas, la serialización y deserialización del gráfico es una pesadilla de complejidad.
2

Sí, hay beneficios en ambos sentidos, y también hay un compromiso.

List<int>:

  • Ahorrar memoria
  • Inicialización de tipo más rápida User
  • Si sus datos provienen de una base de datos relacional (SQL), no tiene que acceder a dos tablas para obtener usuarios, solo la Userstabla

List<Book>:

  • Acceder a un libro es más rápido desde el usuario, el libro ha sido precargado en la memoria. Esto es bueno si puede permitirse tener un inicio más largo para obtener operaciones posteriores más rápidas.
  • Si sus datos provienen de una base de datos del almacén de documentos como HBase o Cassandra, entonces los valores de los libros leídos probablemente estén en el registro del Usuario, por lo que podría haber obtenido fácilmente los libros "mientras estaba allí obteniendo al usuario".

Si no tiene problemas de memoria o CPU con los que iría List<Book>, el código que usa las Userinstancias 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).

ytoledano
fuente
Suponiendo algún tipo de almacenamiento en caché, el beneficio de precarga sería nulo. No he usado Cassandra / HBase, así que no puedo hablar de ellos, pero Linq2SQL es un caso muy específico (aunque no veo cómo la carga diferida evitará el caso de encadenamiento infinito incluso en este caso específico, y en el caso general)
0fnt
En el ejemplo de Linq2SQL, realmente no obtienes ningún beneficio de rendimiento, solo un código más limpio. Cuando se obtienen entidades de uno a muchos de una tienda de documentos como Cassandra / HBase, la gran mayoría del tiempo de procesamiento se dedica a encontrar el registro, por lo que también podría obtener todas las entidades mientras está allí (los libros, en este ejemplo)
ytoledano
¿Estás seguro? ¿Incluso si almaceno Libro y Usuarios normalizados por separado? Para mí, parece que solo debería ser un costo adicional de latencia de red. En cualquier caso, ¿cómo se maneja el caso RDBMS genéricamente? (He editado la pregunta para mencionarlo claramente)
0fnt
1

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.

herzmeister
fuente
Gracias por pasar y responder. Desafortunadamente, si bien entiendo la distinción gracias al enlace wiki, nunca he visto esto en la práctica (dado que nunca he trabajado con grandes proyectos a largo plazo). ¿Tendrías un ejemplo en el que el mismo objeto se representara de dos maneras para dos propósitos diferentes?
0fnt
Aquí hay una pregunta real sobre el mapeo: stackoverflow.com/questions/9770041/dto-to-entity-mapping-tool - y hay artículos críticos como este: rogeralsing.com/2013/12/01/…
herzmeister
Realmente útil, gracias. Lamentablemente, todavía no entiendo cómo funcionaría la carga de datos con referencias circulares. por ejemplo, si un Usuario hace referencia a un Libro y el Libro hace referencia al mismo usuario, ¿cómo crearía este objeto?
0fnt
Mira en el patrón del repositorio . Tendrás a BookRepositoryy a UserRepository. 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.
herzmeister