2010-01-20 16 views
7

Actualmente estamos haciendo una aplicación web cuya funcionalidad es crear Eventos por parte del usuario. Esos eventos pueden ser eliminados posteriormente por el usuario o administrador. Sin embargo, el cliente requiere que el evento no se elimine físicamente de la base de datos, sino que se marque como eliminado. El usuario solo debería ver los eventos no eliminados, pero los administradores deberían poder navegar también a través de los eliminados. Esa es realmente la funcionalidad que hay.Diseño de archivo en la base de datos. Algunos patrones tal vez?

Ahora sugerí que simplemente agreguemos una columna extra llamada "estado", que tendría un par de valores válidos: ACTIVO y ELIMINADO. De esta forma, podemos distinguir entre eventos normales (activos) y eliminados y crear consultas realmente simples (SELECCIONAR * DE EVENTOS DONDE ESTÁ = ACTIVADO). Mi colega, sin embargo, no estuvo de acuerdo. Señaló que independientemente del hecho de que en este momento los eventos activos y los eventos eliminados compartan la misma información (así pueden almacenarse en la misma tabla) en futuros requisitos, mi cambio y el cliente, por ejemplo, necesitarán almacenar información adicional sobre el evento eliminado. (como la fecha de eliminación, quién lo eliminó, por qué lo hizo, una especie de comentario). Dijo que para cumplir esos requisitos en un futuro tendríamos que agregar columnas adicionales en la tabla EVENTOS que contendrían datos específicos para los Eventos eliminados y no para los eventos activos. Propuso una solución, donde se crea una tabla adicional (como DELETED_EVENTS) con el mismo esquema que la tabla EVENTS. Cada evento eliminado se borrará físicamente de la tabla EVENTOS y se moverá a la tabla DELETED_EVENTS.

Estoy en total desacuerdo con su idea. No solo haría que las consultas SQL fueran más complejas y menos eficientes, sino que también está totalmente en contra de YAGNI. Tampoco estaba de acuerdo con él en que mi idea nos llevaría a crear columnas adicionales (no anulables) en la tabla EVENTOS, si los requisitos cambiaban en el futuro. En mi caso, simplemente crearía una nueva tabla como DELETED_EVENTS_DATA (que contendría esos datos adicionales de archivo) y añadiría la clave de referencia en la tabla EVENTS para mantener la relación de uno a uno entre las tablas EVETNS y DELETED_EVENTS_DATA.

Sin embargo, tuve problemas por el hecho de que dos desarrolladores que comúnmente comparten una visión similar del software y el diseño de bases de datos podrían tener opiniones tan radicalmente diferentes sobre cómo deberían diseñarse estos requisitos a nivel de base de datos. Pensé que tal vez ambos estábamos yendo en una dirección equivocada y hay otra (tercera) solución. ¿O hay más de una sola alternativa? ¿Cómo se diseña este tipo de requisitos? ¿Hay algún patrón o guía sobre cómo hacerlo correctamente? Cualquier ayuda será muy apreciada

+0

Puede considerar marcar esto como una wiki de la comunidad. No estoy seguro de que encuentre una respuesta verificablemente correcta. – Mayo

+0

Esta es una gran pregunta y estoy deseando ver algunas de las respuestas que se muestran. La eliminación de filas de la tabla cuando se copia en una tabla DELETED_EVENT puede causarle algunos problemas si hay alguna referencia a su tabla EVENTOS que deba mantener para propósitos históricos.O bien terminará colgando claves foráneas, o claves externas en las que potencialmente podría tener que unirse con ambas tablas. No suena bonito. – rayd09

+0

¿Por qué mi respuesta fue eliminada de ser la respuesta? – anthonyv

Respuesta

0

BIEN la forma en que lo manejamos es la siguiente.

Tenemos una columna adicional en cada tabla llamada 'Eliminado', este es un campo de bit. Entonces, como has dicho correctamente, tus consultas son bastante simples, ya que es solo una cláusula where para filtrarlas o dejarlas allí. Lo único que debes asegurarte es que cualquier informe o estadística que generes elimine los registros eliminados.

Luego, para obtener la información adicional de la que está hablando que desea capturar, esta información adicional irá en una tabla de "auditoría" independiente. En nuestro caso, hemos hecho esta mesa extra muy genérico y que puede contener esta información de auditoría para cualquier tabla ... ver más adelante cómo funciona ...

Event 
EventId EventName ... Deleted 
1   Dinner   0 
2   Supper   1 
3   Lunch    0 
4   Lunch    1 

Audit 
AuditId EntityTypeId  EntityId ActionTypeId ActionDateTime ... etc 
1   1 (Event)  2 (EventId) 1 (Deleted)  2/1/2010 12:00:00 
1   1 (Event)  4 (EventId) 1 (Deleted)  3/1/2010 12:00:00 

Ahora bien, si usted tiene otras entidades que desea capturar (como la ubicación - donde la ubicación es una tabla), así que se vería así ...

Audit 
AuditId EntityTypeId  EntityId ActionTypeId ActionDateTime ... etc 
1   1 (Event)  2 (EventId) 1 (Deleted)  1/1/2010 12:00:00 
1   1 (Event)  4 (EventId) 1 (Deleted)  2/1/2010 12:00:00 
1   2 (Event)  2 (LocationId) 1 (Deleted)  3/1/2010 12:00:00 
1   2 (Event)  8 (LocationId) 1 (Deleted)  4/1/2010 12:00:00 
1   2 (Event)  9 (LocationId) 1 (Deleted)  5/1/2010 12:00:00 

Entonces, cuando usted quiere salir los datos de auditoría adicional que está hablando es bastante simple. La consulta sería algo como esto

SELECT * 
FROM Event E 
     INNER JOIN Audit A 
      ON E.EventId = A.EntityId 
WHERE E.Deleted = 1 
     AND A.EntityTypeId = 1 -- Where 1 stands for events 

También esta tabla de auditoría puede capturar otros eventos y no sólo elimina ... Esto se realiza mediante el uso de la columna de la ActionTypeId. En este momento solo tiene 1 (que es eliminar), pero también podría tener otros.

Esperanza esto ayuda

EDIT:

Además de esto, si tenemos requisitos de auditoría fuertes que hacer lo siguiente ... Ninguno de los cambios anteriores, pero creamos una segunda base de datos llamada 'xyz_Audit', que captura el pre y el post para cada acción que ocurre dentro de la base de datos. Esta segunda base de datos tiene el mismo esquema que la primera base de datos (sin la tabla de auditoría), excepto que cada tabla tiene 2 columnas adicionales.

La primera columna adicional es un PrePostFlag y la segunda columna es el AuditId. Por lo tanto, la clave primaria ahora abarca 3 columnas, 'xyzId', 'PrePostFlag' y 'AuditId'.

Al hacer esto podemos dar a los administradores plena potencia para saber quién hizo qué, los datos que cambiaron y cómo cambiaron y recuperar un registro, solo necesitamos cambiar el indicador eliminado en la base de datos primaria.

Además, al tener esta información en una base de datos diferente, nos permite tener diferentes planes de optimización, almacenamiento y gestión de la base de datos transnacional principal.

+0

A medida que transcurre el tiempo, este esquema será cada vez más lento de consultar ya que los datos de bajo valor se conservan indefinidamente. Además, esto requerirá que se verifique una bandera al crear informes, eliminando así los beneficios de la separación de la preocupación e incluyendo la lógica basada en los registros donde no debería estar. Un mejor enfoque en lugar de este enfoque de "eliminación suave" sería utilizar un enfoque de archivo de base de datos. –

2

Descubrí que tomar instantáneas de un objeto con cada evento (creación, actualización, etc.) y almacenar esas instantáneas (junto con las fechas y la información del usuario) en otra tabla le permite conocer todo tipo de las necesidades de seguimiento histórico durante la vida de una aplicación. A continuación, puede presentar las instantáneas al usuario, presentar cambios cronológicos al usuario, deducir el estado de un objeto en una fecha determinada, etc.

Estoy seguro de que hay patrones de diseño oficiales por ahí, esto es solo uno que he refinado con el tiempo y funciona bien. Sin embargo, no es eficiente con espacio en disco.

EDITAR: Además, cuando el usuario elimina un objeto, marcaría el registro como eliminado y tomará una instantánea final de la tabla de historial. Podría ocultar el objeto de la interfaz indefinidamente o podría optar por mostrarlo, depende de las necesidades de uso.

+0

Estoy completamente de acuerdo con este método. – NotMe

+0

¿Por qué una instantánea y no una transacción? –

+0

@calico: supongo que, por transacción, ¿quiere decir almacenar lo que se cambió y no el estado? Estoy seguro de que ambos métodos funcionan para la auditoría, pero el método de instantánea le permite determinar fácilmente el estado del objeto en un momento dado; hemos tenido este requisito en una serie de proyectos. – Mayo

0

A menudo es una decisión en situaciones como esta. Sin saber nada más de lo que me dijiste, tendería a ir con tu solución, que es solo tener una eliminación virtual. Creo que su aplicación de YAGNI es buena. Si el usuario proporciona en el futuro requisitos para las etapas de registro en la vida de los eventos , es probable que en este momento ustedes no adivinen exactamente cuáles serán esos requisitos.Esto es especialmente cierto si la lógica para tratar los eventos en el DB está bien encapsulada (fácil de cambiar más adelante).

Sin embargo, si conoce bien este cliente y conoce los tipos similares de requisitos históricos que han tenido, y la funcionalidad no estará bien encapsulada, tal vez su colega esté adivinando. La clave aquí es que cualquiera de ustedes es correcto, es no por mucho. Ambas partes tienen mérito.

Por cierto, será mejor tener una columna booleana (sí/no) IsDeleted, con un índice que comience con esa columna. Eso será más rápido, aunque tal vez no suponga una gran diferencia para la materia.

+0

¿En qué parte de la lógica empresarial estaría "isDeleted"? ¿Por qué los datos de bajo valor deben mantenerse en la base de datos de alto rendimiento? –

+0

@Travis. Interesante, un voto negativo. ¿Podría aclarar sus comentarios? El primero con el que no tengo ningún sentido. En cuanto a la 2ª, ¿cree que el cliente del OP está equivocado al querer que se guarden esos datos? Por lo tanto, mi intento de ayudarlo es malo, ¿por qué no debería tratar de hacer eso para empezar? Debería haberle dicho que su cliente está equivocado al querer conservar esa información. –

+0

Debería haberle dicho que estaba mal mantener los datos obsoletos en la base de datos de producción. –

0

Agregaría el campo de bandera por ahora, y solo me molestaría en planear el resto cuando sepa activamente lo que tendrá que hacer, y el sistema también ha acumulado datos del mundo real y experiencias del usuario, por lo que tiene algunos datos para basar sus decisiones de diseño de rendimiento/complejidad.

3

No utilice una columna de estado.

Como mínimo debe tener una fecha eliminada y una columna deleteby. Simplemente saber que se eliminó algo no es útil, incluso si el cliente no lo está solicitando en este momento la primera vez que visita los eventos eliminados, querrá saber quién para poder discernir por qué.

Si es probable que la tabla de eventos crezca bastante en tamaño, es común mover los datos eliminados/archivados a una tabla diferente por completo. Por lo general, asignará esas tablas a un archivo de base de datos diferente. Ese archivo generalmente vive en un disco diferente para mantener el rendimiento. No digo una base de datos completamente nueva, solo un archivo de base de datos diferente.

Si lo mantiene en la misma tabla, todas sus consultas deberían tener una cláusula where en (DateDeleted es nulo). Obviamente, usted no tiene ese requisito si la información se mueve a una mesa diferente ... Por eso recomiendo esa manera de hacer las cosas.

+0

Acepto, el uso de otra base de datos proporciona muchas ventajas claras, como no tener que poner en la nube la lógica en las consultas o tener el rendimiento db nublado con los datos antiguos. –

0

Mucho de esto depende del tamaño de las tablas y si realmente necesita información adicional sobre la eliminación.

En la mayoría de los casos, el campo de indicador borrado es todo lo que necesita. Luego, crea una vista que selecciona los registros donde no se ha eliminado el registro. Use la vista para todas las consultas para los usuarios en lugar de acceder directamente a las tablas.

Si tiene una auditoría, ya sabe quién marcó el registro como eliminado y cuándo.

Si no, debe agregar esos campos a su tabla.

Periódicamente, podría eliminar los registros eliminados en una tabla de archivo para poder mejorar el rendimiento de las consultas en la tabla principal. Digite mover todos los registros borrados que se han eliminado más de 6 meses. Luego, tenga otra vista que combine tanto la tabla normal como la tabla de archivo para que los administradores realicen consultas.

Esta combinación de ambos enfoques en conjunción con el uso de vistas le ofrece lo mejor de ambos mundos, su tabla se mantiene realmente pequeña para consultar, todos pueden ver solo los registros que necesitan ver y es relativamente fácil recuperar algo borrado por accidente, el archivo de registros antiguos puede ocurrir en un período de poco uso del día en lugar de cuando los registros están marcados para su eliminación.

0

Cuando un usuario crea, modifica o elimina un evento, crea un nuevo objeto transaction. Almacene todo sobre el cambio en el evento en la transacción y agréguelo a una tabla con una referencia al evento. De esta forma, tiene un registro de auditoría de todo lo que el usuario ha hecho. Esto agrega una complejidad mínima pero también permite la extensión. Incluso podría agregar una característica de deshacer más tarde con un cambio mínimo, si corresponde, en su modelo de datos.

De modo que si el usuario está viendo los registros, puede recuperar todos los logs sin una transacción DELETE asociada, aunque los administradores podrían ver todo.

+0

Solo para aclarar: utilizaría el patrón de diseño Command para implementar las Transacciones. –