Tasti compositi in applicazioni multi-tenancy per azioni amministrative

7

Ho progettato un database a istanza singola per un'applicazione multi-tenant che utilizza le chiavi composite per imporre la separazione dei tenant al livello del database (evitando così relazioni errate di terzo grado).

Quindi lo schema assomiglia a questo:

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 )
)

Ci sono molte altre tabelle nel sistema, ma tutte condividono lo stesso concetto in cui se un'entità "appartiene" a un tenant, la chiave primaria di quell'entità è composta e include TenantId .

... che aiuta a prevenire situazioni in cui Document di ModifiedByUserId potrebbe fare riferimento a UserId in un altro titolare. Dato che gli inquilini devono essere completamente separati, questa applicazione è l'ideale.

Tranne ... come dovrebbero accadere le azioni amministrative? Nota che in questo sistema un Tenant può essere contrassegnato come IsAdmin che ha lo scopo di dare loro la possibilità non solo di accedere alle risorse di ciascun Tenant, ma anche di modificare e creare risorse - il che è un problema perché una% di amministrazione diTenantId sarebbe diversa - quindi un utente amministratore non può creare una risorsa in un altro tenancy contrassegnato come proveniente da tale utente.

... almeno per lo scenario in cui un amministratore modifica una risorsa esistente, il valore ModifiedByUserId potrebbe rimanere invariato.

Quindi per abilitare questo scenario (creando nuove risorse in un altro tenant) ho alcune opzioni:

  • Dai a ogni inquilino una voce "Utente fantasma" che rappresenta le azioni amministrative. Il rovescio della medaglia è che confonde il set-set Users avendo un'entità che in realtà non rappresenta un utente umano (quindi cosa impostare per cose come nome, email, ecc.). Un lieve vantaggio di questo approccio rispetto all'opzione 2 (sotto) è che mantiene la segregazione completa degli inquilini, anche con i titolari amministrativi, in modo che una locazione possa essere ridotta a un'altra istanza DB senza conflitti di identità o riferimenti interrotti.
  • Rimuovi TenantId dalle chiavi primarie composte di User - lo svantaggio è che indebolisce la segregazione dei tenant.
  • Ruba l'ID utente di un utente esistente nel tenant, forse l'account utente associato al titolare della proprietà, ma ciò comporterebbe una cattiva pista di controllo in quanto non sarebbe "loro" che ha apportato la modifica.

Ci sono altre opzioni per gestire questa situazione?

Mi sto appoggiando all'opzione 1 (utenti Ghost) ma non mi sembra giusto - e aggiunge anche complessità in quanto la logica del caso speciale dovrebbe impostare il valore della chiave esterna non per l'utente corrente, ma per il fantasma -utente, se l'utente corrente appartiene a un titolare dell'amministratore:

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

2 risposte

2

Nella nostra applicazione multi-tenant, abbiamo separato gli utenti dagli altri elementi basati sui titolari e invece abbiamo aggiunto una gestione delle autorizzazioni separata che è basata sui titolari. Ciò consente quindi di concedere l'accesso a più titolari a un utente (o gruppo di utenti), simile a quello in cui si dispone di un account che funziona su tutti i siti SE.

Con questa configurazione, non avresti un titolare "amministrativo" ma piuttosto un ruolo amministrativo per ogni titolare e potrai scegliere chi è membro di questo ruolo. Aggiungendo il supporto per i gruppi e un gruppo di amministratori, che è implicitamente membro del ruolo amministrativo di tutti i tuoi inquilini, puoi anche implementare amministratori globali.

L'altra cosa che abbiamo fatto in modo molto diverso è che non abbiamo scelto le chiavi composite, ma i GUID come chiavi primarie. C'è sempre un dibattito sui pro e contro, ma per noi era importante che non ci fosse collisione (si pensi alla fusione / sincronizzazione / trasferimento dei dati dei titolari) e che la chiave non diventi ingombrante. Questo, insieme alle autorizzazioni che ho menzionato, consente persino di implementare i dati che sono condivisi tra titolari e simili se si desidera (o si ha bisogno di) quello.

Per la separazione dei tenant disponiamo effettivamente di un database separato per ciascun tenant, in modo che possiamo persino eseguire il backup, il ripristino o il trasferimento di un solo tenant alla volta.

    
risposta data 17.11.2016 - 02:31
fonte
1

Supponendo di voler mantenere le chiavi composite e mantenere gli inquilini nello stesso database, ci sono altre opzioni nell'ambito che la tua domanda sembra implicare, come ...

1. Elimina il concetto di tenant di amministrazione e assegna a ogni utente amministratore una vera identità utente nella tabella degli utenti.

Ciò significa che gli utenti amministratori avranno più righe utente nella tabella Utenti. La loro identità di utenti admin sarebbe probabilmente in una tabella separata, ad esempio AdminUser, come ...

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

La tabella utenti potrebbe quindi avere una chiave esterna nullable che fa riferimento a AdminUser, se il database e i criteri lo consentono.

2. Oppure crea un'altra tabella come ...

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

Rendi le tue chiavi esterne riferite alla tabella TenantUser, dove dovrebbero essere elencati solo gli utenti validi.

Quindi, ogni utente appartiene a un titolare tramite la relazione Utenti- > Tenant. Il titolare dell'amministratore esiste ancora e possiede gli utenti amministratori.

Tuttavia, a ciascun utente possono essere concessi diritti per più titolari, tramite TenantUser.

Con entrambe le opzioni è possibile registrare l'ID utente reale per scopi di audit trail, ecc. e mantenere il codice abbastanza standardizzato senza Utenti Ghost.

Hai chiesto delle alternative, e interpreto questo come un significato di alternative che non sono radicalmente differenti. Non sto cercando di raccomandare queste alternative.

Ci sono, naturalmente, anche alcune alternative radicalmente diverse, come dare a ciascun cliente un database separato.

La risposta giusta dipende molto dalla tua attività.

    
risposta data 17.11.2016 - 03:14
fonte

Leggi altre domande sui tag