¿Cuáles son los patrones de diseño para admitir campos personalizados en una aplicación?

Desarrollamos una aplicación comercial. Nuestros clientes están solicitando soporte de campos personalizados. Por ejemplo, quieren agregar un campo al formulario de Cliente.

¿Cuáles son los patrones de diseño conocidos para almacenar los valores de campo y los metadatos sobre los campos?

Veo estas opciones por ahora:

Opción 1 : Agregue las columnas Field1, Field2, Field3, Field4 de tipo varchar a mi tabla de clientes.

Opción 2 : Agregue una sola columna de tipo XML en la tabla de clientes y almacene los valores de los campos personalizados en xml.

Opción 3 : agregue una tabla CustomerCustomFieldValue con una columna de tipo varchar y almacene valores en esa columna. Esa tabla también tendría un CustomerID, un CustomFieldID.

CustomerID, CustomFieldID, Value 10001, 1001, '02/12/2009 8:00 AM' 10001, 1002, '18.26' 10002, 1001, '01/12/2009 8:00 AM' 10002, 1002, '50.26' 

CustomFieldID sería una ID de otra tabla llamada CustomField con estas columnas: CustomFieldID, FieldName, FieldValueTypeID.

Opción 4 : agregue una tabla CustomerCustomFieldValue con una columna de cada tipo de valor posible y almacene valores en la columna derecha. Similar a # 3, pero los valores de campo se almacenan usando una columna de tipo fuerte.

 CustomerID, CustomFieldID, DateValue, StringValue, NumericValue 10001, 1001, 02/12/2009 8:00 AM, null, null 10001, 1002, null, null, 18.26 10002, 1001, 01/12/2009 8:00 AM, null, null 10002, 1002, null, null, 50.26 

Opción 5 : Las opciones 3 y 4 usan una tabla específica para un solo concepto (Cliente). Nuestros clientes también están solicitando campos personalizados en otras formas. ¿Deberíamos tener un sistema de almacenamiento de campo personalizado en todo el sistema? Entonces, en lugar de tener varias tablas como CustomerCustomFieldValue, EmployeeCustomFieldValue, InvoiceCustomFieldValue, tendríamos una sola tabla llamada CustomFieldValue? Aunque me parece más elegante, ¿no causaría eso un cuello de botella de rendimiento?

¿Has usado alguno de esos enfoques? ¿Tuviste éxito? ¿Qué enfoque seleccionarías? ¿Conoces algún otro enfoque que deba considerar?

Además, mis clientes quieren que el campo personalizado pueda referirse a datos en otras tablas. Por ejemplo, un cliente puede querer agregar un campo “Método de pago favorito” al Cliente. Los métodos de pago están definidos en otra parte del sistema. Eso trae el tema de “claves foráneas” en la imagen. ¿Debería intentar crear restricciones para garantizar que los valores almacenados en las tablas de campos personalizados sean valores válidos?

Gracias

======================

EDITAR 27-07-2009:

Gracias por sus respuestas. Parece que la lista de enfoques ahora es bastante completa. He seleccionado la opción 2 (una sola columna XML). Fue el más fácil de implementar por ahora. Probablemente tenga que refractar a un enfoque más definido ya que mis requisitos se volverán más complejos y la cantidad de campos personalizados que se respaldarán se hará más grande.

Estoy de acuerdo con los pósters a continuación en que las opciones 3, 4 o 5 son más apropiadas. Sin embargo, cada una de las implementaciones sugeridas tiene sus beneficios y costos. Sugeriría que elijas uno coincidiendo con tus requisitos específicos. Por ejemplo:

  1. Pros de la Opción 1: Rápido de implementar. Permite acciones de DB en campos personalizados (búsqueda, clasificación).
    Opción 1 contras: los campos personalizados son generics, por lo que no hay campos fuertemente tipados. La tabla de la base de datos es ineficiente, en cuanto al tamaño, con muchos campos extraños que nunca se usarán. Se debe anticipar el número de campos personalizados permitidos.
  2. Pros de la opción 2: Rápido de implementar. Flexible, lo que permite el número arbitrario y el tipo de campos personalizados.
    Opción 2 contras: No es posible realizar acciones de DB en campos personalizados. Esto es mejor si todo lo que necesita hacer es mostrar los campos personalizados, más tarde, o hacer pequeñas manipulaciones de los datos solo por cliente.
  3. Pros de la opción 3: ambos flexibles y eficientes. Las acciones de DB se pueden realizar, pero los datos se normalizan un poco para reducir el espacio desperdiciado. Estoy de acuerdo con la sugerencia desconocida (Google) de que agregue una columna adicional que se puede usar para especificar el tipo o la información de origen. Opción 3 contras: un ligero aumento en el tiempo de desarrollo y la complejidad de sus consultas, pero realmente no hay demasiados inconvenientes, aquí.
  4. La opción 4 es la misma que la opción 3, excepto que sus datos escritos pueden ser operados en el nivel DB. La adición de información de tipo a la tabla de enlace en la Opción 3 le permite realizar más operaciones en nuestro nivel de aplicación, pero el DB no podrá hacer comparaciones u ordenar, por ejemplo. La elección entre 3 y 4 depende de este requisito.
  5. La opción 5 es lo mismo que 3 o 4, pero con aún más flexibilidad para aplicar la solución a muchas tablas diferentes. El costo en este caso será que el tamaño de esta tabla crecerá mucho más. Si realiza muchas operaciones costosas de combinación para llegar a sus campos personalizados, esta solución puede no escalarse bien.

PD Como se observa a continuación, el término “patrón de diseño” generalmente se refiere a la progtwigción orientada a objetos. Está buscando una solución para un problema de diseño de la base de datos, lo que significa que la mayoría de los consejos sobre los patrones de diseño no serán aplicables.

En lo que respecta al código de la aplicación, no estoy seguro. Sé que los campos personalizados se benefician enormemente de un modelo EAV en la base de datos.

Según los comentarios a continuación, el error más importante que puede cometer con este modelo es colocar claves foráneas en él. Nunca coloque algo como FriendID o TypeID en este modelo. Utilice este modelo junto con el modelo relacional típico y mantenga los campos clave externos en las columnas de la tabla como deberían.

Un segundo error importante es colocar datos en este modelo que deben informarse con cada elemento. Por ejemplo, poner algo como nombre de usuario en este modelo significaría que cada vez que quiera acceder a un usuario y necesite saber su nombre de usuario, se habrá comprometido a una unión en el mejor de los casos o 2 consultas donde n es el número de usuarios que está viendo. . Cuando considera que normalmente va a necesitar la propiedad Nombre de usuario para cada elemento Usuario, resulta obvio que esto también debería permanecer en las columnas de la tabla.

Sin embargo, si solo está utilizando este modelo con campos de usuario personalizados, estará bien. No puedo imaginar muchas situaciones en las que un usuario ingrese datos relacionales y el modelo EAV no es demasiado perjudicial para las búsquedas.

Por último, no intente unir datos de esto y obtenga un bonito conjunto de registros bonito. Toma el registro original y luego toma el conjunto de registros para la entidad. Si te sientes tentado de unirte a las mesas probablemente hayas cometido el segundo error como se mencionó anteriormente.

Si está desarrollando un lenguaje orientado a objetos, aquí estamos hablando de modelos de objetos adaptables . Hay bastantes artículos escritos sobre cómo puede implementarlos en oo-idiomas, pero no tanto sobre cómo diseñar el lado de la tienda de datos.

En la empresa donde trabajo, hemos resuelto el problema usando una base de datos relacional para almacenar datos de AOM. Tenemos una tabla central de entidades para presentar todas las diferentes “entidades” en el dominio, como personas, dispositivos de red, empresas, etc. Almacenamos los “campos de formulario” reales en tablas de datos que se escriben, así que tenemos una tabla para cadenas, una para fechas, etc. Todas las tablas de datos tienen una clave externa que apunta a la tabla de entidades. También necesitamos tablas para presentar el lado del tipo, es decir, qué tipo de atributos (campos de formulario) puede tener determinada entidad y esta información se utiliza para interpretar los datos en las tablas de datos.

Las ventajas de nuestra solución son que cualquier cosa se puede modelar sin cambios de código, incluidas las referencias entre entidades, multivalos, etc. También es posible agregar reglas comerciales y validaciones a los campos y pueden reutilizarse en todas sus formas. Contras son que el modelo de progtwigción no es muy fácil de entender y el rendimiento de las consultas será peor que con un diseño de base de datos más típico. Alguna otra solución que la base de datos relacional podría haber sido mejor y más fácil para AOM.

Crear un buen AOM con una tienda de datos funcional es mucho trabajo y no lo recomendaría si no tienes desarrolladores altamente capacitados. Tal vez algún día habrá una solución de sistema operativo para este tipo de requisitos.

Los campos personalizados se han discutido anteriormente en SO:

  • ¿Cómo crearía y almacenaría campos personalizados definidos por el usuario en una base de datos SQL?
  • ¿Agregar o no los campos personalizados / definidos por el usuario?
  • ¿Qué architecture puedo usar para manejar un carrito de compras donde cada producto requiere diferentes atributos para ser guardado?

Algo así como la Opción 3 es el camino a seguir y he usado este método anteriormente. Cree una sola tabla para definir propiedades adicionales y sus valores correspondientes. Esta sería una relación 1-N entre su cliente y la tabla CustomerCustomField (respectivamente). Su segunda pregunta sobre la definición de relaciones con propiedades personalizadas sería algo en lo que pensar. Lo primero que viene a la mente es agregar un campo de fuente de datos, que contendría la tabla a la que está vinculado el valor de la propiedad. Entonces, esencialmente, su CustomerCustomField se vería así:

  1. Identificación del cliente
  2. Propiedad
  3. Valor
  4. ValueDataSource (nullable)

Esto debería permitirle vincularse a una estructura de datos específica o simplemente permitirle especificar valores libres. Puedes normalizar aún más este modelo, pero algo así podría funcionar y debería ser lo suficientemente fácil de manejar en código.

La opción 4 o 5 sería mi elección. Si sus datos son importantes, no iría tirando la información de su tipo con la Opción 3. (Podría intentar implementar la verificación de tipos completa usted mismo, pero es un trabajo bastante grande, y el motor de la base de datos ya lo hace por usted).

Algunos pensamientos:

  • Asegúrese de que sus CustomFields tengan una columna DataType .
    • Use una restricción de verificación basada en CustomFieldValues en CustomFieldValues para asegurarse de que la columna especificada por CustomFields.DataType no sea nula.
    • También querrá una restricción de verificación estándar para asegurarse de tener exactamente un valor no nulo.
  • En cuanto a las claves externas, las DataType como un tipo de DataType separado.
    • Cada posible referencia de tabla cruzada requeriría su propia columna. Esto es bueno, porque mantiene la integridad referencial.
    • Debería admitir estas relaciones en el código de la aplicación de todos modos, por lo que el hecho de que estén codificadas en la base de datos no limita la funcionalidad.
    • Esto también funcionará bien con su ORM, si está usando uno.
  • Para la Opción 5, use tablas intermedias para modelar las relaciones.
    • Aún tendría un CustomerCustomFieldValue , pero en su lugar solo con las columnas CustomerID y CustomFieldValueID .
  • Piensa detenidamente sobre tus limitaciones en cada paso del camino. Esto es algo complicado, y un paso en falso puede causar un completo havok en la línea.

Estoy usando esto en una aplicación actualmente en desarrollo. Todavía no ha habido ningún problema, pero los diseños de EAV aún me asustan. Sólo sé cuidadoso.

Como un aparte, XML también puede ser una buena opción. No sé mucho al respecto por experiencia directa, pero fue una de las opciones que consideré al comenzar el diseño de datos, y parecía bastante prometedor.

si esos campos “adicionales” son incidentales y no les interesa realizar búsquedas en ellos, generalmente voy por la opción 2 (pero como JSON mejor que XML). Si van a haber búsquedas en campos personalizados, la opción 3 no es difícil de hacer, y generalmente el optimizador de SQL puede obtener un rendimiento razonable.

Actualmente estoy trabajando en un proyecto con este mismo problema, y ​​he elegido usar la opción 3, pero agregué un campo FieldType y un campo ListSource en caso de que FieldType = “list”. El campo ListSource podría ser una consulta, una vista sql, un nombre de función o algo que resulte en una lista de opciones para la lista. El mayor problema al tratar de almacenar campos como este en mi situación es que esta lista de campos puede cambiar y los usuarios pueden editar los datos más adelante. Entonces, ¿qué hacer si la lista de campos ha cambiado y van a editar? Mi solución a ese escenario fue permitir la edición solo si la lista no ha cambiado y mostrar datos de solo lectura si lo ha hecho.