Abordar el hecho de que las claves principales no son parte de su dominio comercial

25

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 ( UserNamepara usuarios o OrderNumberpara 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:

  1. Ingenuamente, los objetos de transferencia de datos (DTO) que se derivan exclusivamente de combinaciones de modelos de dominio no tendrán claves primarias
  2. 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?

  1. Ser capaz de identificar los objetos con los que necesita lidiar por otros atributos
  2. 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 VoIPProviderque incluye campos como Name, Description, URL, así como referencias como ProviderType, PhysicalAddressy Transactions.

Ahora supongamos que quiero crear un servicio web que permita a los usuarios privilegiados administrar VoIPProviders.

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 VoIPProviderestá completamente determinado por (Name, URL). Así que ahora digamos que necesito un método PUT api/providers/voippara que los usuarios privilegiados puedan actualizar VoIPproveedores. 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:

  1. Incluir una clave principal o alternativa en mi modelo de dominio y enviarla al DTO, y viceversa
  2. Identifique el proveedor que nos interesa a través del índice único, como (Name, Url)
  3. 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,
tacos_tacos_tacos
fuente
1
Punto para reflexionar: a menudo la comunicación con expertos en dominios se empobrece cuando se utiliza una PK sustituta cuando existe una buena clave comercial. Parece que terminamos trabajando para el marco ORM, en lugar de al revés.
Tulains Córdova
@ user61852, independientemente de ORM, incluso si realmente tiene un nivel bajo, aún necesita una clave principal en la implementación de la capa de base de datos. Por lo tanto, estoy de acuerdo en que el PK sustituto le brinda ventajas sobre el PK real utilizado por un mecanismo de persistencia específico, pero si ese PK realmente representa un objeto comercial que es significativo, es necesariamente único y, por lo tanto, tiene al menos una propiedad única relacionada con el negocio que define ¿que no?
tacos_tacos_tacos
1
Todas las ventajas de los sustitutos están relacionadas con la computadora y ninguna relacionada con los humanos.
Tulains Córdova
2
@ user61852: estoy de acuerdo al 100% (¿escribí algo diferente?). Para los medios de comunicación, use una "clave comercial". Agregue restricciones únicas para cualquier clave comercial también. Pero evite el uso de claves comerciales para implementar realmente las referencias de su base de datos.
Doc Brown
2
las claves de negocios son eternamente únicas, hasta que no lo sean. Si está utilizando las claves comerciales como principal cuando esto sucede, el cambio en las reglas comerciales rompe más cosas.
psr

Respuestas:

31

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"):

  • al asignar el modelo de dominio a una implementación de base de datos o un modelo de clase en un lenguaje de programación específico, tiene una regla simple y consistente como "para cada objeto de dominio asignado a una tabla relacional, la clave principal es" TablenameID ".
  • esta clave primaria es completamente artificial, siempre tiene el mismo tipo y no tiene significado comercial, solo una clave sustituta
  • la "versión gráfica" de su modelo de dominio (la que usa para hablar con sus expertos en dominio) no contiene claves primarias. No los expones directamente a los expertos (pero los expones a cualquiera que realmente esté implementando código para el sistema).

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.

Doc Brown
fuente
¿Qué pasa con el paso de tomar el objeto de dominio del objeto de persistencia devuelto (entidad, o modelo de fila de tabla, o lo que sea), ir a DTO, volver a entrar y volver a la persistencia? ¿Se hace esto mediante una clave sustituta (es decir, una definición de unicidad orientada a los negocios) que requiere una resolución en cada operación de persistencia?
tacos_tacos_tacos
1
@tacos_tacos_tacos: sigamos con su ejemplo de VoIPProvider. En realidad, agregaría un "VoIPProviderID" a su DTO, al menos en el "lado de la implementación" (si también tiene una versión gráfica para sus expertos en dominios, probablemente no la muestre allí). Para fines de actualización, la forma estándar de identificar un VoIPProvider específico debe ser el "VoIPProviderID" que recuperó al extraer los datos de la base de datos. Si los usuarios de su API prefieren la identificación por (nombre, URL), proporciónela adicionalmente. ...
Doc Brown
... y si el rendimiento parece convertirse en un problema real y medible, también puede considerar guardar en caché la asignación (nombre, url) a VoIPProviderID en algún lugar. Pero no recomendaría implementar dicha optimización de antemano, prematuramente.
Doc Brown
1
Las claves naturales a veces parecen realmente convincentes, pero es demasiado fácil quemarse (por ejemplo, "¡Vaya! Ahora tengo varios inquilinos y eso ya no es único").
Casey
1
@corsiKa: en el contexto de lo que solicitó el OP, recomendaría encarecidamente tener una clave "OrderID" puramente autogenerada (que no se imprime en ningún recibo, sino que solo se usa para elementos internos como referencias de bases de datos) y una clave comercial separada "OrderNumber" (que puede, por ejemplo, contener algo como el año actual, que se puede usar para ordenar y filtrar, que se puede cambiar / corregir después y que se puede imprimir en los recibos). OP solicitó "Diseño controlado por dominio", el "OrderNumber" es parte del modelo de dominio, mientras que el "OrderID" es solo un detalle de implementación.
Doc Brown
4

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í?)

gbjbaanb
fuente
Cuando quiero hacer una operación que involucra una instancia específica de un objeto de dominio que es persistente, necesito poder incluir en el DTO ya sea explícitamente (a través de algún tipo de clave, ya sea primaria o una identificación alternativa fácil de usar que sea única) a esa tabla y también podría ser un índice en la tabla) o implícitamente (a través de una combinación de campos cuyos valores identifican de forma única un registro específico) ... y, además, en un sentido práctico, necesitaría enviar en el DTO alguna forma de seguimiento de cambios si tuviera que hacerlo de otra manera (OriginalVal vs NewVal para todos los campos que identifican el registro), ¿no?
tacos_tacos_tacos
¿No es la pregunta explícita v implícita la misma diferencia? Puede tener un PK que abarque varias columnas, al igual que un índice único. No veo que haya ninguna diferencia entre ellos para sus propósitos.
gbjbaanb
Claro, podríamos tener un PK en varias columnas, por ejemplo. Pero para mí está filtrando algo sobre el databaswe (el almacenamiento) que no debería tener NADA que ver con el corazón y el alma, la entidad comercial. Si alguna tupla de campos de entidades comerciales abarca la PK de la base de datos, entonces genial. Pero no necesariamente debe ser al revés, ¿verdad?
tacos_tacos_tacos
Lo estás pensando demasiado. Un índice único es tanto un artefacto del esquema DB como una PK. Piénselo de esta manera: un PK es solo el primer índice (o primario) único. es 'especial' porque generalmente solo necesita 1 índice de este tipo.
gbjbaanb
Es cierto, pero cualquier objeto de dominio significativo debe ser identificable estrictamente desde al menos uno de sus campos relacionados con el negocio, ¿no? El hecho de que esto defina un índice en la base de datos es más por razones de rendimiento que para ser utilizado para consultar la base de datos fácilmente ... Preferiría una PK de una columna sobre un índice único de 6 columnas, y realmente sirven para diferentes propósitos: un PK (o índice con un pequeño número de campos) también está disponible para la conveniencia del DBA / DBD, ¿verdad?
tacos_tacos_tacos
4

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í.

Johan
fuente
Así, el mensaje es un ejemplo extremo, pero incluso entonces conoceríamos MessageSender, MessageRecipient, TimeSent- que debe ser único.
tacos_tacos_tacos
1
@tacos_tacos_tacos, entonces, ¿cómo se crean FK en las otras tablas? Debería ser MessageSenderId, que probablemente se asigna a una tabla de usuarios en UserId. No querrá usar el nombre de usuario como clave entre las tablas, ya que eso podría cambiar y convertirse en una pesadilla de mantenimiento. Esta es la razón por la que generalmente solo une tablas usando Claves primarias, y no otra columna (ciertamente hay excepciones). Esta estructura de base de datos todavía debe hacerse cumplir. Ahora siempre puede ir a un modelo CQRS para su aplicación ... en cuyo caso las reglas cambian. Especialmente si también usas Event Sourcing.
CaffGeek
4

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.

Nzall
fuente
1

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.como PUT /3F2504E0-4F89-41D3-9A0C-0305E82C3301y 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 a PUT <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.

Lie Ryan
fuente
0

Voy a tener que enfocarme en esta afirmación extremadamente inexacta e ingenua:

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.

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:

class VoipProvider(models.Model):
    name=fields.TextField()
    address=fields.TextField()

class Customer(models.Model):
    name=fields.TextField()
    address=fields.TextField()
    voipProvider=fields.ForeignKeyField(VoipProvider)

De esta manera, se puede acceder a los detalles de un proveedor de un cliente en código utilizando:

myCustomer.voipProvider.name #returns the name of the customers VOIP Provider.

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
Tiene toda la razón, pero supongo que quise decir que "en algunos dominios, tal vez haya una tupla natural de valores que siempre son únicos" Si cambian, pero siguen siendo únicos, aún identifican un único registro.
tacos_tacos_tacos
0

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.

Gredoso
fuente