En casi todas las circunstancias, las claves principales no son parte de su dominio comercial. Claro, es posible que tenga algunos objetos importantes para el usuario con índices únicos ( UserName
para usuarios o OrderNumber
para pedidos), pero en la mayoría de los casos, no es necesario identificar abiertamente los objetos de dominio por un solo valor o conjunto de valores, para cualquier persona que no sea un usuario administrativo Incluso en esos casos excepcionales, especialmente si está utilizando identificadores únicos globales (GUID) , le gustará o querrá emplear una clave alternativa en lugar de exponer la clave primaria en sí.
Entonces, si mi comprensión del diseño impulsado por el dominio es precisa, las claves primarias no necesitan y, por lo tanto , no deben exponerse, y una buena solución. Son feos y obstaculizan mi estilo. Pero si elegimos no incluir claves primarias en el modelo de dominio, hay consecuencias:
- Ingenuamente, los objetos de transferencia de datos (DTO) que se derivan exclusivamente de combinaciones de modelos de dominio no tendrán claves primarias
- Los DTO entrantes no tendrán una clave principal
Entonces, ¿es seguro decir que si realmente va a permanecer puro y eliminar las claves primarias en su modelo de dominio, debe estar preparado para poder manejar cada solicitud en términos de índices únicos en esa clave primaria?
Dicho de otra manera, ¿cuál de las siguientes soluciones es el enfoque correcto para tratar de identificar objetos particulares después de eliminar PK en modelos de dominio?
- Ser capaz de identificar los objetos con los que necesita lidiar por otros atributos
- Recuperar la clave principal en el DTO; es decir, ¿eliminar la PK al mapear desde la persistencia al dominio, luego recombinar la PK al mapear del dominio a DTO?
EDITAR: Hagamos esto concreto.
Di mi modelo de dominio es VoIPProvider
que incluye campos como Name
, Description
, URL
, así como referencias como ProviderType
, PhysicalAddress
y Transactions
.
Ahora supongamos que quiero crear un servicio web que permita a los usuarios privilegiados administrar VoIPProvider
s.
Quizás una identificación fácil de usar es inútil en este caso; después de todo, los proveedores de VoIP son empresas cuyos nombres tienden a ser distintos en el sentido de la computadora e incluso lo suficientemente distintos en el sentido humano por razones comerciales. Por lo tanto, puede ser suficiente decir que un único VoIPProvider
está completamente determinado por (Name, URL)
. Así que ahora digamos que necesito un método PUT api/providers/voip
para que los usuarios privilegiados puedan actualizar VoIP
proveedores. Envían un VoIPProviderDTO
, que incluye muchos pero no todos los campos del VoIPProvider
, incluido un posible aplanamiento. Sin embargo, no puedo leer sus mentes, y todavía necesitan decirme de qué proveedor estamos hablando.
Parece que tengo 2 (quizás 3) opciones:
- Incluir una clave principal o alternativa en mi modelo de dominio y enviarla al DTO, y viceversa
- Identifique el proveedor que nos interesa a través del índice único, como
(Name, Url)
- Introduzca algún tipo de objeto intermedio que siempre pueda mapearse entre la capa de persistencia, el dominio y el DTO de una manera que no exponga los detalles de implementación sobre la capa de persistencia, por ejemplo, al introducir un identificador temporal en memoria al pasar del dominio a DTO y viceversa,
fuente
Respuestas:
Así es como resolvemos esto (desde hace más de 15 años, cuando ni siquiera se inventó el término "diseño impulsado por dominio"):
Por lo tanto, siempre que necesite una clave principal para fines técnicos (como el mapeo de relaciones con una base de datos), tiene una disponible, pero siempre que no quiera "verla", cambie su nivel de abstracción al "modelo de expertos de dominio". ". Y no tiene que mantener "dos modelos" (uno con PK y otro sin); en su lugar, mantenga solo un modelo sin PK y use un generador de código para crear el DDL para su DB, que agrega el PK automáticamente de acuerdo con las reglas de mapeo.
Tenga en cuenta que esto no prohíbe agregar ninguna "clave comercial" como un "Número de pedido" adicional, además del sustituto
OrderID
. Técnicamente, estas claves comerciales se convierten en claves alternativas cuando se asignan a su base de datos. Simplemente evite usar estos para crear referencias a otras tablas, siempre prefiera usar las claves sustitutas si es posible, esto hará las cosas mucho más fáciles.Para su comentario: el uso de una clave sustituta para identificar registros no es una operación relacionada con el negocio, es una operación puramente técnica. Para aclarar esto, observe su ejemplo: siempre y cuando no defina restricciones únicas adicionales, sería posible tener dos objetos VoIPProvider con la misma combinación de (nombre, url), pero diferentes VoIPProviderID.
fuente
Debe poder identificar muchos objetos por algún índice único, y no es eso lo que es una clave primaria (o al menos implica que hay una presente).
Hay índices únicos disponibles para que pueda restringir aún más su esquema de base de datos, no como un reemplazo total para PK. Si no está exponiendo PKs porque son feos, pero en cambio están exponiendo una clave única ... en realidad no está haciendo nada diferente. (¿Asumo que no estás obteniendo una PK y una columna de identidad mezcladas aquí?)
fuente
Sin las claves principales en la interfaz no hay una manera fácil para que el servidor sepa lo que está enviando. Para solucionarlo, necesitaría un montón de trabajo adicional analizando los datos, lo que perjudicaría el rendimiento y probablemente tomaría más tiempo y sería más feo que adjuntar una clave a cada elemento.
Como ejemplo, digamos que quiero editar un mensaje en una aplicación; ¿Cómo sabría la aplicación qué mensaje quiero editar sin una clave principal adjunta? La edición de objetos ocurre todo el tiempo y hacerlo sin teclas es casi imposible. Pero si tiene objetos que se supone que no deben editarse, omita la clave si cree que está distrayendo, pero tener claves principales puede mejorar el rendimiento aquí.
fuente
MessageSender
,MessageRecipient
,TimeSent
- que debe ser único.La razón por la que usamos un PK no relacionado con el negocio es para asegurarnos de que nuestro sistema tenga un método fácil y consistente para determinar lo que el usuario quiere.
Veo que respondió con un comentario: MessageSender, MessageRecipient, TimeSent (para un mensaje). Todavía puede tener ambigüedad de esta manera (por ejemplo, con mensajes generados por el sistema que se activan en algo que sucede a menudo). ¿Y cómo va a validar MessageSender y MessageRecipient aquí? Supongamos que los valida con FirstName, Lastname, DateOfBirth, eventualmente se encontrará con una situación en la que tiene 2 personas nacidas el mismo día con el mismo nombre. Sin mencionar que te encontrarás con una situación en la que tienes un mensaje llamado
tacostacostacos-America-1980-Doc Brown-France-1965-23/5/2014-11:43:54.003UTC+200
. Es un nombre monstruoso, y todavía no tienes garantía de que solo tendrás 1 de esos.la razón por la que usamos una clave primaria es porque SABEMOS que será única durante toda la vida útil del software, sin importar qué datos se ingresen, y SABEMOS que será un formato predecible (¿qué sucede si la clave anterior tiene un guión? en un nombre de usuario? todo su sistema va a la mierda).
No necesita mostrar su identificación a su usuario. Puede ocultar eso (a simple vista si es necesario, a través de la URL).
Otra razón por la que un PK es tan útil es algo que puede deducir de lo anterior: un PK lo hace para que no tenga que obligar a la computadora a interpretar el código generado por el usuario. Si un usuario chino usa su código e ingresa un montón de caracteres chinos, su código de repente no necesita poder trabajar con ellos internamente, pero simplemente puede usar el Guid que generó el sistema. Si tiene un usuario árabe que ingresa la escritura árabe, su sistema no tiene que lidiar con eso internamente, pero básicamente puede ignorar que están allí.
Como otros han dicho, un Guid es algo que se puede almacenar internamente en un tamaño fijo. Sabes con qué trabajas y es algo que se puede usar universalmente. No necesita crear reglas de diseño sobre cómo crear un determinado identificador y guardarlo. Si su sistema solo toma las primeras 10 letras de un nombre, no ve ninguna diferencia entre Michael Guggenheimer y Michael Gugstein, y confundirá estos 2. Si lo corta en cualquier longitud arbitraria, puede entrar en confusión. Si limita la entrada del usuario, puede encontrarse con problemas con las limitaciones del usuario.
Cuando miro los sistemas existentes como Dynamics CRM, también usan la clave interna (la PK) para que el usuario invoque un solo registro. Si un usuario tiene una consulta que no involucra la ID, devuelve una serie de posibles respuestas y permite al usuario elegir entre ellas. Si hay alguna posibilidad de ambigüedad, le darán la opción al usuario.
Finalmente, también es un poco de seguridad a través de la oscuridad. Si no conoce el ID de registro, su única opción es adivinarlo. Si la identificación es fácil de adivinar (porque la información de la que está hecha está disponible públicamente), cualquiera puede cambiarla. Incluso puede incitar al usuario a cambiarlo a través de los métodos clásicos CSRF o XSS. Ahora, obviamente, su seguridad ya debería contar con ellos y mitigarlos antes de publicar la versión en vivo, pero aún así debe hacer que sea más difícil que ocurra un posible abuso.
fuente
Al emitir un identificador para un sistema externo, solo debe proporcionar URI, o alternativamente una clave o un conjunto de claves que tengan las mismas propiedades que un URI, en lugar de exponer la clave primaria de la base de datos directamente (de aquí en adelante me referiré a tanto URI o una clave o un conjunto de claves que tienen las mismas propiedades que URI como solo URI, en otras palabras, un URI a continuación no necesariamente significa RFC 3986 URI).
Un URI puede contener o no la clave principal del objeto y puede o no estar compuesto de claves alternativas. Realmente no importa. Lo importante es que solo el sistema que genera el URI puede dividir o combinar el URI para comprender qué es el objeto referido. Los sistemas externos siempre deben usar el URI como un identificador opaco. No importa si un usuario humano puede identificar que una parte del URI es en realidad una clave sustituta de la base de datos o que consiste en varias claves comerciales agrupadas, o que en realidad es una base64 de esos valores. Estos son irrelevantes. Lo que importa es que el sistema externo no debería necesitar ser requerido para comprender qué significa el identificador para usar el identificador. Los sistemas externos nunca deberían ser necesarios para analizar los componentes dentro del identificador o combinar un identificador con otros identificadores para referirse a algo en su sistema.
El uso de GUID cumple algunos de estos criterios, sin embargo, un identificador como GUID puede ser difícil de desreferenciar nuevamente dentro del objeto, incluso dentro de su sistema, por lo que las claves que son opacas incluso para su sistema como un GUID solo deberían emplearse si el cliente analiza el URI / identificador realmente plantea un riesgo de seguridad.
Volviendo a su ejemplo de VoIP, digamos que un proveedor de VoIP puede determinarse de manera única por (VoIPProviderID) o por (Nombre, URL) o por (GUID). Cuando un sistema externo necesita actualizar el proveedor de VoIP, simplemente puede pasar un PUT / provider / by-id / 1234 o
PUT /provider/foo-voip/bar-domain.com
oPUT /3F2504E0-4F89-41D3-9A0C-0305E82C3301
y su sistema comprenderá que el sistema externo desea actualizar el proveedor de VoIP. Estos URI son generados por su sistema y solo su sistema necesita entender que todos significan lo mismo. El sistema externo debería tratar lo que sea que esté en el URI como básicamente aPUT <whatever>
.Suponga que tiene los datos de diferentes proveedores de VoIP almacenados en diferentes tablas con diferentes esquemas (por lo tanto, un conjunto de claves completamente diferente identifica a cada proveedor de VoIP en función de la tabla en la que están almacenados). Cuando tiene un URI, el sistema externo puede acceder a ellos de manera uniforme, sin tener en cuenta cómo su sistema identifica al proveedor de VoIP en particular. Para el sistema externo, todo es solo un puntero opaco.
Cuando su sistema usa un URI para referirse a los objetos de tal manera, no está filtrando nada con respecto a cómo implementa su sistema. Usted genera el URI y el cliente simplemente se lo devuelve.
fuente
Voy a tener que enfocarme en esta afirmación extremadamente inexacta e ingenua:
Los nombres son terribles como claves, ya que cambian con frecuencia. Una empresa puede tener muchos nombres durante su vida útil, la empresa puede fusionarse, dividirse, fusionarse nuevamente, crear una subsidiaria distinta para fines fiscales específicos que tiene 0 empleados pero todos los clientes que luego contrata empleados de una subsidiaria completamente diferente.
Luego nos adentramos en el hecho de que los nombres de las empresas ni siquiera son remotamente únicos, como lo muestra el hito Apple vs. Apple .
Un buen mapeador o marco relacional de objetos debería abstraer las claves primarias y hacerlas invisibles, pero están allí y, por lo general, serán la única forma de identificar de forma única un objeto en su base de datos.
Como referencia, prefiero cómo django maneja esto:
De esta manera, se puede acceder a los detalles de un proveedor de un cliente en código utilizando:
Si bien las claves principales / externas no son visibles, están allí y se pueden usar para acceder a los elementos, pero se abstraen.
fuente
Creo que a menudo todavía miramos incorrectamente este problema desde la perspectiva de la base de datos: oh, no hay una clave natural, por lo que debemos crear una clave sustituta. Oh no, no podemos exponer la clave sustituta de nuevo a los objetos de dominio, eso es permeable, etc.
Sin embargo, a veces una actitud mejor es la siguiente: si un objeto de negocios (dominio) no tiene una clave natural, entonces tal vez debería recibir una. Este es un problema de dominio comercial doble: en primer lugar, las cosas necesitan una identidad, incluso en ausencia de bases de datos. En segundo lugar, aunque intentamos fingir que la persistencia es una idea abstracta invisible para el dominio, la realidad es que la persistencia sigue siendo un concepto de negocio. Obviamente, hay problemas en los que la clave natural elegida no es compatible con la base de datos como clave principal (por ejemplo, GUID en algunos sistemas); en este caso, deberá agregar una clave sustituta.
Por lo tanto, terminas en un lugar muy similar, por ejemplo, tu cliente tiene una ID entera, pero en lugar de sentirte enfermo porque eso se filtró de la base de datos al dominio, te sientes feliz porque la empresa ha acordado que a todos los clientes se les asigne un ID, y usted está persistiendo en el DB, como debe. Todavía puede presentar un sustituto, por ejemplo, para admitir el cambio de nombre de la identificación del cliente.
Este enfoque también significa que si un objeto de dominio golpea la capa de persistencia y no tiene una ID, entonces probablemente sea algún tipo de objeto de valor, por lo que no necesita una ID.
fuente