Teclas compuestas en una aplicación multiempresa para acciones administrativas

7

He diseñado una base de datos de instancia única para una aplicación multiusuario que utiliza claves compuestas para imponer la segregación del arrendatario en la capa de base de datos (evitando así relaciones incorrectas de tercer grado).

Entonces el esquema se ve así:

TABLE Tenants (
    TenantId int           NOT NULL IDENTITY(1,1)
    Name     nvarchar(100) NOT NULL
    IsAdmin  bit           NOT NULL

    PRIMARY KEY( TenantId )
)

TABLE Users (
    TenantId int NOT NULL
    UserId   int NOT NULL IDENTITY(1,1)
    UserName nvarchar(100) NOT NULL

    PRIMARY KEY( TenantId, UserId )

    CONSTRAINT FK_Tenants_Users FOREIGN KEY ( TenantId ) REFERENCES Tenants ( TenantId )
)

TABLE Documents (
    TenantId   int NOT NULL
    DocumentId int NOT NULL IDENTITY(1,1)

    CreatedByUserId  int NOT NULL
    ModifiedByUserId int NOT NULL

    PRIMARY KEY ( TenantId, DocumentId )

    CONSTRAINT FK_Tenants_Documents   FOREIGN KEY ( TenantId )             REFERENCES Tenants ( TenantId )
    CONSTRAINT FK_Documents_Creators  FOREIGN KEY ( TenantId, CreatedBy )  REFERENCES Users   ( TenantId, CreatedBy )
    CONSTRAINT FK_Documents_Modifiers FOREIGN KEY ( TenantId, ModifiedBy ) REFERENCES Users   ( TenantId, ModifiedBy )
)

Hay muchas otras tablas en el sistema, pero todas comparten el mismo concepto donde si una entidad "pertenece" a un inquilino, entonces la clave principal de esa entidad es compuesta e incluye el TenantId .

... que ayuda a prevenir situaciones en las que Document ' ModifiedByUserId podría referirse a UserId en otro Inquilino. Como los inquilinos deben estar completamente separados, esta aplicación es ideal.

Excepto ... ¿cómo deberían ocurrir las acciones administrativas? Tenga en cuenta que en este sistema, un Inquilino puede marcarse como IsAdmin , que está destinado a darles la capacidad no solo de acceder a los recursos de cada Inquilino, sino también a editar y crear recursos, lo cual es un problema porque un TenantId del Administrador sería diferente - por lo tanto, un usuario administrador no puede crear un recurso en otro arrendamiento marcado como proveniente de ese usuario.

... al menos para el escenario donde un administrador modifica un recurso existente, el valor ModifiedByUserId podría permanecer sin cambios.

Entonces, para habilitar este escenario (creando nuevos recursos en otro inquilino) tengo algunas opciones:

  • Dé a cada inquilino una entrada de "usuario fantasma" que represente acciones administrativas. La desventaja es que confunde el conjunto de entidades Users al tener una entidad que en realidad no representa a un usuario humano (¿qué se establece para cosas como nombre, correo electrónico, etc.?). Una ligera ventaja de este enfoque en comparación con la opción 2 (a continuación) es que mantiene una completa segregación de inquilinos, incluso con inquilinos administrativos, de modo que la tenencia puede ser fragmentada en otra instancia de DB sin conflictos de identidad o referencias rotas.
  • Elimine TenantId de las claves primarias compuestas de User . La desventaja es que debilita la segregación del inquilino.
  • Robar el Id. de usuario de un usuario existente en el inquilino, tal vez la cuenta de usuario asociada con el propietario del arrendamiento, pero esto daría lugar a un mal seguimiento de auditoría, ya que no sería "ellos" quienes realizaron el cambio.

¿Hay otras opciones para lidiar con esta situación?

Me inclino por la opción 1 (usuarios fantasma) pero no se siente bien, y también agrega complejidad, ya que la lógica de casos especiales tendría que establecer el valor de la clave externa no para el usuario actual, sino para el fantasma -usuario, si el usuario actual pertenece a un administrador de inquilinos:

resource.CreatedByUserId = currentUser.TenantId == resource.TenantId ? currentUser.UserId ? getGhostUserId( resource.TenantId )
    
pregunta Dai 17.11.2016 - 01:13

2 respuestas

2

En nuestra aplicación para múltiples inquilinos, separamos a los usuarios de las otras cosas basadas en inquilinos, y en su lugar agregamos una administración de permisos separada que se basa en inquilinos. Esto le permite otorgar acceso a varios inquilinos a un usuario (o grupo de usuarios), similar a aquí donde tiene una cuenta que funciona en todos los sitios SE.

Con esa configuración, no tendría un inquilino "administrativo" sino un rol administrativo para cada inquilino y podrá elegir quién es miembro de este rol. Al agregar el soporte de grupos y un grupo de administradores, que es implícitamente miembro de la función administrativa de cada uno de sus inquilinos, también puede implementar administradores globales.

La otra cosa que hicimos de manera muy diferente es que no elegimos claves compuestas, sino GUID como claves primarias. Siempre hay un debate sobre los pros y los contras, pero para nosotros era importante que no haya colisión (piense en fusionar / sincronizar / transferir datos del inquilino) y que la clave no resulte complicada. Esto, junto con los permisos que mencioné, permite incluso implementar datos que se comparten entre los inquilinos y similares, si lo desea (o necesita).

Para la segregación de inquilinos, en realidad tenemos una base de datos separada para cada inquilino, por lo que podemos hacer copias de seguridad, restaurar o transferir solo un inquilino a la vez.

    
respondido por el Lucero 17.11.2016 - 02:31
1

Suponiendo que desea mantener las claves compuestas y mantener a los inquilinos en la misma base de datos, hay otras opciones dentro del alcance que su pregunta parece implicar, como ...

1. Elimine el concepto de inquilino de administrador y dé a cada usuario administrador una identidad de usuario real en la Tabla de usuarios.

Esto significa que sus usuarios administradores tendrían varias filas de usuarios en la tabla Usuarios. Su identidad como usuarios de administrador probablemente estaría en una tabla separada, digamos AdminUser, como ...

create table AdminUser(AdminUserID int, etc...)

La tabla de usuarios podría tener una clave foránea anulable que se refiera a AdminUser, si su base de datos y sus políticas lo permiten.

2. O crea otra tabla como ...

create table TenantUser (TenantID int not null, UserID int not null);

Haga que sus claves externas se refieran a la tabla TenantUser, donde solo deberían aparecer usuarios válidos.

Entonces, cada usuario pertenece a un Inquilino a través de la relación Usuarios- > Inquilinos. El inquilino del administrador aún existe y es propietario de los usuarios del administrador.

Pero a cada usuario se le pueden otorgar derechos a varios inquilinos, a través de TenantUser.

Con cualquiera de las dos opciones, puede registrar la ID de usuario real para fines de seguimiento de auditoría, etc. y mantener el código bastante estandarizado sin usuarios Ghost.

Usted pidió alternativas, y yo interpreto que esto significa alternativas que no son radicalmente diferentes. No estoy tratando de recomendar estas alternativas.

Por supuesto, también hay alternativas radicalmente diferentes, como dar a cada cliente una base de datos separada.

La respuesta correcta realmente depende de su negocio.

    
respondido por el joshp 17.11.2016 - 03:14

Lea otras preguntas en las etiquetas