Hacer referencia a valores de bases de datos en lógica de negocios

43

Supongo que esta es otra pregunta sobre la codificación rígida y las mejores prácticas. Digamos que tengo una lista de valores, digamos fruta, almacenados en la base de datos (debe estar en la base de datos ya que la tabla se usa para otros fines, como los informes SSRS), con una ID:

1 Apple 
2 Banana 
3 Grapes

Puedo presentarlos al usuario, selecciona uno, se almacena en su perfil como FavouriteFruit y la ID almacenada en su registro en la base de datos.

Cuando se trata de reglas de negocio / lógica de dominio, ¿cuáles son las recomendaciones para asignar lógica a valores específicos? Digamos que si el usuario ha seleccionado Grapes, deseo realizar alguna tarea adicional, ¿cuál es la mejor manera de hacer referencia al valor de Grapes?

// Hard coded name
if (user.FavouriteFruit.Name == "Grapes")

// Hard coded ID
if (user.FavoriteFruit.ID == 3) // Grapes

// Duplicate the list of fruits in an enum
if (user.FavouriteFruit.ID == (int)Fruits.Grapes)

¿o algo mas?

Como, por supuesto, FavouriteFruit se utilizará en toda la aplicación, la lista se puede agregar o editar.

Alguien puede decidir que quiere que 'Grapes' cambie su nombre a 'Grape' y, por supuesto, esto rompería la opción de cadena codificada.

Sin embargo, la ID codificada no está completamente clara, como se muestra, podría agregar un comentario para identificar rápidamente qué elemento es.

La opción enum implica la duplicación de datos de la base de datos, lo que parece incorrecto, ya que puede no estar sincronizado.

De todos modos, gracias de antemano por cualquier comentario o sugerencia.

Kate
fuente
1
Gracias a todos: las sugerencias y consejos generales son realmente útiles. @RemcoGerlich su idea de separar las preocupaciones de una cadena utilizada para fines de visualización y una separada como código de búsqueda para un código más legible es muy buena.
Kate
1
Sin embargo, voy a darle una idea a @Mike Nakis de tu idea de objetos precargados, ya que parece ser lo mejor de ambos mundos.
Kate
1
Sugeriría una variación en su primera solución. Haga que su tabla contenga una tercera columna de cómo se procesará, y use ese campo para determinar qué código ejecutar. No es un campo de visualización, y se puede compartir entre varias frutas.
Inicio
1
La opción enum implica la duplicación de datos de la base de datos, lo que parece incorrecto ya que puede no estar sincronizado. Me gusta esto, en realidad. Es como la contabilidad de doble entrada. Si ambos lados del libro mayor no se equilibran, sabrá que algo está mal. Hace que cambiar las cosas sea más deliberado.
radarbob
1
Hmmm ... Si hay una relación 1: 1 de ID con una Cadena, entonces esto es redundante y tener ambas es inútil. Una cadena puede servir como una clave DB tan bien como un número entero. MyApplication.Grape.IDtartamudea, por así decirlo. Una "Apple" no es una "Red_Apple", como tampoco el ID de 3 es 4. Por lo tanto, el potencial para cambiar el nombre de "Apple" a "Red_Apple" no tiene más sentido que declarar que 3 es 4 (y tal vez incluso 3). El objetivo de una enumeración es abstraer su ADN numérico. Entonces, tal vez es hora de desacoplar realmente las claves de base de datos relacionales arbitrarias que literalmente no tienen significado en los modelos de negocio de uno.
radarbob

Respuestas:

31

Evite cadenas y constantes mágicas a toda costa. Están completamente fuera de discusión, ni siquiera deberían considerarse como opciones. Esto parece dejarle solo una opción viable: identificadores, es decir, enumeraciones. Sin embargo, también hay una opción más, que en mi opinión es la mejor. Llamemos a esta opción "Objetos precargados". Con los objetos precargados, puede hacer lo siguiente:

if( user.FavouriteFruit.ID == MyApplication.Grape.ID )

Lo que acaba de suceder aquí es que obviamente he cargado toda la fila Grapeen la memoria, por lo que tengo su ID lista para usar en las comparaciones. Si está utilizando el mapeo relacional de objetos (ORM), se verá aún mejor:

if( user.FavouriteFruit == MyApplication.Grape )

(Por eso lo llamo "Objetos precargados").

Entonces, lo que hago es que durante el inicio cargue todas mis tablas de "enumeración" (tablas pequeñas como días de la semana, meses del año, géneros, etc.) en la clase de dominio de la aplicación principal. Los cargo por nombre, porque obviamente, MyApplication.Grapedebo recibir la fila llamada "Grape", y afirmo que se encuentran todos y cada uno de ellos. De lo contrario, tenemos un error de tiempo de ejecución garantizado durante el inicio, que es el menos maligno de todos los errores de tiempo de ejecución.

Mike Nakis
fuente
17
No estoy en desacuerdo con la respuesta, pero creo que el imperativo de "Evitar cadenas y constantes mágicas a toda costa" no está de acuerdo con el resto de la respuesta, lo que en realidad requiere que tenga al menos un lugar donde se usen constantes o cadenas mágicas. en poblar sus "objetos precargados". Eso es notable, creo, porque no son formas de evitar "cadenas y constantes mágicas" del todo, aunque por lo general es más ofuscar de lo que vale ...
svidgen
2
@svidgen, ¿no estaría de acuerdo con que existe una diferencia fundamental entre la dispersión de enlace por nombre en todo el lugar frente a enlace por nombre solo una vez, para cargar el contenido de un registro que lleva el mismo nombre y hacerlo solo en el inicio, ¿dónde los errores de tiempo de ejecución son casi tan benignos como los errores de compilación? De todos modos, las formas de evitar incluso el más mínimo enlace por nombre siempre son interesantes, a pesar de la ofuscación que mencionaste, por lo que me gustaría saber lo que tienes en mente.
Mike Nakis
Oh, estoy completamente de acuerdo. Y, dada la naturaleza del OP, solo sugeriría que esta respuesta podría beneficiarse al cambiar "a toda costa" a "siempre que sea posible y factible" o algo similar. ... Si tuviera más tiempo, solo en aras de la integridad, escribiría una respuesta que aborde algún tipo de disparate de metaprogramación ... pero, eso no es lo que probablemente necesita el OP (o cualquiera en la mayoría de los casos) . Pero, una solución de metaprogramación se alinearía más con su primera declaración tal como está.
svidgen
1
@ user469104 la diferencia es que los identificadores pueden cambiar, y la aplicación aún cargará todas las filas correctamente y realizará todas las comparaciones correctamente. Además, es libre de refactorizar el código y cambiar el nombre de las filas de la manera que desee, y el único lugar donde tiene que buscar cosas para arreglar es en el inicio de la aplicación, y tiende a ser muy obvio: Grape = fetchRow( Fruit.class, NameColumn, "Grape" ); Y si usted hacer algo incorrectamente, un AssertionErrortestamento te lo hará saber.
Mike Nakis
1
@grahamparks no más de enumlo que hubiera sido una cadena mágica. El punto es la concentración de todos los enlaces por nombre en un solo lugar , la validación de todo durante el inicio y la seguridad de tipo .
Mike Nakis
7

La comprobación de la cadena es la más legible, pero cumple una doble función: se usa tanto como identificador como descripción (que puede cambiar por razones no relacionadas).

Por lo general, divido ambas tareas en campos separados:

id  code    description
 1  grape   Grapes
 2  apple   Apple

Donde la descripción puede cambiar (pero no "Grapes" a "Banana"), pero el código no puede cambiar, nunca.

Aunque esto se debe principalmente a que nuestros identificadores casi siempre se generan automáticamente y, por lo tanto, no encajan bien. Si puede elegir los identificadores libremente, tal vez pueda garantizar que siempre sean correctos y usarlos.

Además, ¿con qué frecuencia alguien realmente edita "Grapes" a "Grape"? Quizás nada de esto sea necesario.

RemcoGerlich
fuente
8
No creo que más redundancia sea la respuesta ...
Robbie Dee
44
También he considerado esta opción y la he probado, pero esto es lo que terminó sucediendo: en algún momento, "apple" tuvo que diferenciarse en "green_apple" y "red_apple". Pero debido a que "apple" ya se usaba en una miríada de lugares en el código, no pude cambiarle el nombre, así que tuve que tener "apple" y "green_apple". Y como resultado, el Sheldon en mí me impidió dormir por varias noches hasta que entré allí y refactoricé todo a "Objetos precargados". (vea mi respuesta.)
Mike Nakis
1
Definitivamente me gustan sus objetos precargados, pero si su "manzana" se diferencia, ¿no tiene que revisar todo de todos modos, sea cual sea el método que elija?
RemcoGerlich
Incluso podría tener una tabla separada para el nombre de la descripción, en apoyo de la internacionalización.
Erik Eidt
1
@MikeNakis y la refactorización es esencialmente una búsqueda y reemplazo en toda su base de código que reemplaza Fruit.Apple con Fruit.GreenApple. Si uso valores de cadena codificados, haría una búsqueda y reemplazo en toda la base de código para reemplazar "apple" con "green_apple", que es casi lo mismo. - La refactorización simplemente se siente mejor, porque el IDE está haciendo el reemplazo.
Falco
4

Lo que espera aquí es que la lógica de programación se adapte automáticamente a los datos cambiantes. Las opciones estáticas simples como Enum no funcionan aquí porque no es posible agregar enumeraciones adicionales en el tiempo de ejecución.

Algunos patrones que he visto:

  • Enums + default para proteger contra una nueva entrada en la base de datos que arruina el día de su programa.
  • Codificación de acciones a realizar (lógica de negocio) en la base de datos. En muchos casos esto es muy posible porque muchas lógicas se reutilizan. La implementación de la lógica debe estar en el programa.
  • Atributos / columnas adicionales en la base de datos para marcar el nuevo valor como 'a ser ignorado' en el programa hasta que el programa se implemente correctamente.
  • Fallar mecanismos rápidos alrededor de la ruta de código que carga / recarga los valores de la base de datos. (Si la acción correspondiente no está en el programa Y no está marcada para ser ignorada, no realice la actualización).

En general, me gusta que los datos se completen en términos de referirse a las acciones que implica, a pesar de que las acciones en sí mismas podrían implementarse en otro lugar. Cualquier código que determine acciones independientes de los datos acaba de fragmentar su representación de datos, lo que probablemente divergerá y conducirá a errores.

Subu Sankara Subramanian
fuente
4

Almacenarlos en ambos lugares (en una tabla y en un ENUM) no es tan malo. El razonamiento es el siguiente:

Al almacenarlos en una tabla de base de datos, podemos imponer integridad referencial en la base de datos a través de claves externas. Entonces, cuando asocia una persona o cualquier entidad a una fruta, solo existe una fruta en la tabla de la base de datos.

Almacenarlos como un ENUM también tiene sentido porque podemos escribir código sin cadenas mágicas y hace que el código sea más legible. Sí, tienen que mantenerse sincronizados, pero en realidad lo difícil que sería agregar una línea al ENUM y una nueva instrucción de inserción a la base de datos.

Una cosa, una vez que se define un ENUM, no cambie su valor. Por ejemplo, si tuvieras:

  • manzana
  • Uva

NO cambie el nombre de Uva a Uvas. Simplemente agregue un nuevo ENUM.

  • manzana
  • Uva
  • Uvas

Si necesita migrar datos, aplique una actualización para mover toda la uva a uvas.

Jon Raynor
fuente
Como otro paso, trabajé en tiendas donde los valores de metadatos tienen un indicador de eliminación en la tabla para indicar que no deben usarse (ya no se utilizan o hay una versión más nueva).
Robbie Dee
1

Tiene razón al hacer esta pregunta, en realidad es una buena pregunta, ya que intenta defenderse contra la evaluación de condiciones inexactas.

Dicho esto, la evaluación (sus ifcondiciones) no necesariamente tiene que ser el foco de cómo evitarla. En su lugar, preste atención a la forma en que propaga los cambios que causarían un problema 'fuera de sincronización'.

Enfoque de cuerda

Si debe usar cadenas, ¿por qué no exponer la funcionalidad de cambiar la lista a través de la interfaz de usuario? Diseñe el sistema de modo que, al cambiar Grapea Grapes, por ejemplo, actualice todos los registros que actualmente hacen referencia Grape.

Enfoque de identificación

Siempre prefiero hacer referencia a una identificación, a pesar del compromiso de cierta legibilidad. The list may be added tonuevamente puede ser algo de lo que se le notificaría si expone dicha función de IU. Si le preocupa el reordenamiento de los elementos que cambian la identificación, vuelva a propagar dicho cambio a todos los registros dependientes. Similar a lo anterior. Otra opción (siguiendo la convención de normalización adecuada, sería tener una columna enum / id, y hacer referencia a una FruitDetailtabla más detallada , que tiene una columna 'Order' que puede buscar).

De cualquier manera, puede ver que estoy sugiriendo controlar la modificación o actualización de su lista. Los detalles específicos de su tecnología determinan si hace esto mediante el uso de un ORM o algún otro acceso a datos. Lo que estás haciendo esencialmente es exigir a las personas que se alejen de la base de datos para tales cambios, lo que creo que está bien. La mayoría de los CRM principales harán el mismo requisito.

JᴀʏMᴇᴇ
fuente
1
En la base de datos , la identificación numérica se almacena para registros secundarios, específicamente para evitar ese problema. Esta pregunta trata sobre cómo interactuar con un lenguaje de programación.
Clockwork-Muse
1
@ Clockwork-Muse: ¿para evitar qué problema? Eso no tiene sentido.
JᴀʏMᴇᴇ
Utilizo bastante el enfoque de ID, pero la ID está bloqueada y no puede cambiar. La cadena adjunta, por supuesto, puede porque las personas a menudo les gusta cambiar el nombre de las cosas "camión" se convierte en "camión", etc., mientras que la cosa en sí (representada por ID) no cambia.
Brian Knoblauch
Si opta por el enfoque de ID, ¿cómo maneja las bases de datos de desarrollo frente a producción? Con las ID de incremento automático, agregar elementos a ambas bases de datos en un orden diferente dará como resultado diferentes ID.
Protector uno
Sin embargo, ¿no tiene que ser un incremento automático? No debería ser en este caso, especialmente si estamos usando el valor entero de la enumeración subyacente.
JᴀʏMᴇᴇ
0

Un problema muy común. Si bien la duplicación del lado del cliente de datos puede parecer que viola los principios DRY , en realidad se debe a la diferencia de paradigma entre las capas.

Tener la enumeración (o lo que sea) fuera de sintonía con la base de datos tampoco es tan infrecuente. Es posible que haya introducido otro valor en una tabla de metadatos para admitir una nueva función de informes que aún no se utiliza en el código del lado del cliente.

A veces sucede a la inversa también. Se agrega un nuevo valor de enumeración al lado del cliente, pero la actualización de la base de datos no puede ocurrir hasta que el DBA pueda aplicar los cambios.

Robbie Dee
fuente
Sí, has descrito el problema. Cual es tu solucion
Protector uno
1
Usted asume @Protectorone no es una solución mágica que es una suposición errónea en mi experiencia. Lo mejor que puede esperar es que alguna entidad comercial sea propietaria del dominio del problema para que al menos pueda ver qué lado está rezagado: el lado del cliente o el lado de la base de datos. La banca y las finanzas suelen ser muy eficientes a este respecto, ya que el sector minorista es notablemente menos ...
Robbie Dee
0

Suponiendo que estamos hablando de lo que es esencialmente una búsqueda estática, la tercera opción, la enumeración, es básicamente la única opción sensata. Es lo que haría si la base de datos no estuviera involucrada, por lo que tiene sentido.

La pregunta se convierte entonces en cómo sincronizar las enumeraciones y las tablas estáticas / de búsqueda en la base de datos y, desafortunadamente, ese no es un problema para el que todavía tengo una respuesta completa.

Por elección, hago todo el mantenimiento de mi esquema en código y, por lo tanto, puedo mantener una relación entre una compilación de la aplicación y una versión de esquema esperada, por lo que es sencillo mantener la búsqueda y la enumeración sincronizadas, pero es algo que uno debe recordar hacer. Sería mejor si fuera más automatizado (y también una prueba de integración automatizada para garantizar que las enumeraciones y las coincidencias de búsqueda ayudarían), pero eso no es algo que haya implementado.

Murph
fuente
1
No creo que estas sean solo búsquedas estáticas, de lo contrario, podrían extraerse de la base de datos y consumirse tal como están. El problema, según tengo entendido, es cuando la lógica de negocios se aplicará dependiendo del valor de búsqueda utilizado. Pero, aparte de eso, sí, las enumeraciones generalmente se emplean para este propósito.
Robbie Dee
Ok, necesito un término mejor "para búsqueda estática". El contexto que describe es lo que quise decir :) La clave es "estática": estos son valores que no cambian el problema: agregar nuevos valores y cambiar la "etiqueta" ( pero no la intención) para los valores existentes.
Murph