nodejs - Restricciones únicas en couchdb
couchdb tutorial (3)
¿Existen técnicas / propuestas para imponer restricciones únicas? Sí, podemos crear una clave que sea única, pero no podemos cambiarla y, además, este enfoque no es adecuado para una validación complicada (inicio de sesión único separado, correo electrónico único separado, etc.)
Por ejemplo, una cuenta debe tener un inicio de sesión único y un correo electrónico. Derivar una clave de estos campos resultará en inconsistencia:
key1: "[email protected]", { email: "[email protected]", login: "john"}
key2: "[email protected]", { email: "[email protected]", login: "mary"}
Luciendo bien, pero
key1: "[email protected]", { email: "[email protected]", login: "mary"}
key2: "[email protected]", { email: "[email protected]", login: "mary"}
Vaya, ahora tenemos 2 cuentas con inicio de sesión: "mary"
Depende. Considere el caso replicado de múltiples maestros, podría haber entradas en conflicto agregadas allí consistentes dentro de cada maestro, pero no consistentes una vez que se repliquen. Es posible que solo esté utilizando un servidor couchdb, pero en general lo diseñan asumiendo un caso de varios maestros y no ponen ninguna característica que solo funcione correctamente en un solo servidor sin replicar.
Si solo le importa el caso de un solo servidor, posiblemente podría reconstruir su couchjs con soporte de red y realizar una consulta http en su función validate_doc_update()
que realizaría una consulta en la base de datos para ver si la dirección de correo electrónico ya está en uso y falla. la actualización si es así. Consulte here para obtener más detalles sobre el mecanismo de validación. No recomiendo hacerlo, en su lugar, incrustaría toda la singularidad en el campo de identificación (ya sea directamente o mediante hashing) y solo me ocuparía de mover el documento si el usuario cambiaba algo que lo afectara.
Este es uno de los bits menos divertidos de CouchDB. La mejor manera que he encontrado para manejar campos únicos que pueden cambiar (como en su ejemplo de usuario) es crear documentos "punteros" con los valores únicos como un componente de la clave, y luego usarlos para permitirle reclamar valores únicos. La parte importante de hacer esto es tener una clave predecible para el documento principal, luego guardar los documentos de reclamo de campo únicos antes de guardar el principal (y permitir que los conflictos clave eviten que se guarde el documento principal).
Dado un usuario con un nombre de usuario único y un correo electrónico único, sus documentos principales podrían tener este aspecto:
user-1234: { username: "kurt", email: "kurt@localhost" }
user-9876: { username: "petunia", email: "[email protected]" }
Los punteros de campo únicos se verían así:
user-username-kurt: { primary_doc: "user-1234" }
user-email-kurt@localhost: { primary_doc: "user-1234" }
user-username-petunia: { primary_doc: "user-9876" }
[email protected]: { primary_doc: "user-9876" }
Crear o actualizar un usuario tomaría estos pasos:
- Prepare su documento de usuario, genere una clave para él si es necesario
- Guardar un documento "puntero" para cada campo único cambiado
- Si guarda alguno de esos errores, deténgase y corrija los errores.
- Guardar documento de usuario primario
Paso 3 tomará un pensamiento. Por ejemplo, no querrá intentar reclamar valores únicos para campos que no han cambiado. Podría, pero luego tendría que poner un poco de lógica adicional para manejar un caso en el que reclama un valor para un usuario que ya posee ese valor.
El paso 3 sería un buen lugar para permitir que las personas también tomen los valores anteriores. Si un usuario ha "liberado" el nombre de usuario kurt, por ejemplo, puedo actualizar ese documento en particular para que apunte a mi nueva clave después de verificar que ya no está en uso. La alternativa es eliminar los valores exclusivos reclamados cuando cambian. No estoy seguro de cuál sería menos trabajo, de verdad. Dejar a mi lado los antiguos valores reclamados es lo más sensato para mí.
Lo interesante de esta solución es que no es necesario utilizar esos documentos de puntero para nada una vez que se crean. Puede crear vistas como de costumbre en sus documentos de usuario y usarlas para realizar consultas por correo electrónico o nombre de usuario.
Esta configuración también le permite hacer relaciones sin tener que preocuparse por las claves de usuario en cascada. No sé sobre usted, pero todos los demás documentos del sistema hacen referencia a mis documentos de usuario. Cambiar una clave de usuario sería un dolor masivo en el culo.
Respuesta básica
Estructure sus POST / PUT para el documento que tiene el campo que desea mantener único de la siguiente manera:
Crear una view . En la función de mapa, use el campo que desea imponer único como clave . El valor puede ser nada. Utilice la función de reducción para obtener un recuento para cada una de sus teclas. La mejor manera (para el rendimiento) es utilizar la función integrada _count reduce.
Inmediatamente después de PONER / PUBLICAR un documento nuevo en la base de datos, tome el
id
yrev
devueltos y GET / yourdb / _design / yourapp / _view / viewname? Group = true & key = " value-of-your-unique-field-from-step -1 ".Si el resultado del último GET le da un valor de conteo diferente a 1 , entonces acaba de insertar un duplicado. Inmediatamente DELETE / yourdb / id-from-step-2 ? Rev = rev-from-step-2 .
Relájate
Ejemplo tosco
Digamos que está almacenando cuentas de usuario y desea asegurarse de que la dirección de correo electrónico sea única, pero no desea que sea la identificación del documento (por cualquier motivo). Cree una vista para verificar rápidamente la singularidad en la dirección de correo electrónico como se describe anteriormente. Vamos a llamarlo correos electrónicos . Tendría una función de mapa posiblemente similar a esta ...
function(doc) {
if(doc.type === ''account'') {
emit(doc.email, 1);
}
}
Y solo _count
como la función de reducción . Si usted emite un 1 como anteriormente _sum
también funcionará. Tener y verificar un campo de type
en su documento como se muestra arriba es solo una convención, pero a veces me resulta útil. Si todo lo que está almacenando son cuentas de usuario, probablemente no lo necesite.
Ahora digamos que estamos insertando un documento así ...
POST /mydb/
{
"name": "Joe",
"email": "[email protected]"
}
Y CouchDB responderá con algo como ...
{
ok: true,
id: 7c5c249481f311e3ad9ae13f952f2d55,
rev: 1-442a0ec9af691a6b1690379996ccaba6
}
Compruebe si ahora tenemos más de un [email protected] en la base de datos ...
GET /mydb/_design/myapp/_view/emails/?group=true&key="[email protected]"
Y CouchDB responderá con algo como ...
{
rows: [
{
key: "[email protected]",
value: 1
}
]
}
Si el value
es distinto a 1
en esa respuesta, probablemente acaba de insertar un correo electrónico duplicado. Entonces, elimine el documento y devuelva un error en la línea de Dirección de correo electrónico que sea Único , similar a una respuesta típica de la base de datos SQL, o lo que desee.
La eliminación iría algo como esto ...
DELETE /mydb/7c5c249481f311e3ad9ae13f952f2d55?rev=1-442a0ec9af691a6b1690379996ccaba6
Discusión corta (si te importa)
Si viene de un buen fondo de SQL antiguo *, esto va a parecer incorrecto y extraño. Antes de voltear considera estos puntos. Tener una restricción en un campo, como la singularidad o cualquier otra cosa, implica un esquema . CouchDB no tiene esquemas . Proveniente de * SQL significa que tendrá que cambiar su forma de pensar. ACID y los bloqueos no son el único camino. CouchDB viene con mucha flexibilidad y potencia. La forma en que lo use para obtener lo que desea dependerá de los detalles de su caso de uso y de lo bien que pueda escapar de los límites del pensamiento tradicional de la base de datos relacional.
La razón por la que a veces implemento la singularidad de esta manera es porque me parece muy gracioso . Si piensa en la forma en que CouchDB trabaja con el manejo de conflictos de actualización, etc., este enfoque sigue el mismo flujo. Almacenamos el documento que recibimos y luego verificamos si nuestro campo es único. Si no es así, tome el documento que acabamos de insertar con el asa conveniente que todavía tenemos en él y haga algo para resolver la necesidad de singularidad, como eliminarla, por ejemplo.
Puede verse tentado en el ejemplo anterior para verificar la singularidad de la dirección de correo electrónico antes de POST
el documento. ¡¡Ten cuidado!! Si hay varias cosas sucediendo, es posible que se pueda insertar otro documento con la misma dirección de correo electrónico en la base de datos en el instante después de verificar si existe el correo electrónico, ¡pero antes de hacer su POST
! Eso te dejará con un email duplicado.
No hay nada de malo en ejecutar también la comprobación de la dirección de correo electrónico antes de realizar el POST
. Por ejemplo, esta es una buena idea si su usuario está llenando un formulario y puede agregar el valor del campo de correo electrónico a la base de datos. Puede hacer una advertencia previa al usuario de que la dirección de correo electrónico existe o impedir el envío, etc. Sin embargo, en todos los casos, siempre debe verificar la unicidad después de POST
el documento. Luego reacciona según sea necesario. Desde la perspectiva del lado de la interfaz de usuario, los pasos anteriores no serían diferentes al resultado obtenido de una base de datos SQL * tradicional en una restricción de unicidad.
¿Puede algo salir mal? Sí. Considera esto. Supongamos que [email protected] no existe ya en la base de datos. Dos documentos llegan casi al mismo tiempo para ser guardados en la base de datos. El documento A está POSTed
, luego el documento B está POSTed
pero antes de que podamos verificar la singularidad del documento A POST
. Así que ahora, cuando hacemos la comprobación de la singularidad del Doc A POST
, vemos que [email protected] está dos veces en la base de datos. Por lo tanto, lo eliminaremos y le informaremos el problema. Pero digamos que antes de que podamos eliminar el Doc A , también ocurre la verificación de la singularidad del Doc B , obteniendo el mismo valor de conteo para [email protected] . Ahora se rechazarán ambos POSTs
, aunque originalmente [email protected] no estaba en la base de datos. En otras palabras, si dos documentos con un valor coincidente entran en su aplicación casi al mismo tiempo, es posible que puedan ver los POSTs
otros y POSTs
conclusión errónea de que el valor que llevan ya está en la base de datos. Realmente no podemos evitar esto porque no hay un bloqueo de estilo RDB tradicional en CouchDB. Pero a cambio de ese pequeño precio, obtenemos replicación de maestro-maestro y un montón de otras cosas geniales. ¡Me lo llevo! Y si este es un gran problema para su implementación, puede intentar solucionarlo implementando un mecanismo de reintento de algún tipo, etc.
Finalmente, CouchDB realmente es una joya de base de datos, pero no solo tome esto y espere que sea un enfoque a prueba de balas. Mucho realmente dependerá de los detalles de su aplicación específica.