Come tradurre i requisiti aziendali in strutture dati specifiche utilizzando come esempio la progettazione di un database per un messenger "da zero".
- Parte 1: progettiamo la struttura del database

Il nostro database non sarà così grande e distribuito, o , ma "per averlo", deve essere fatto bene: funzionale, veloce e stare su un unico server. PostgreSQL — in modo da poter disporre di un'istanza separata del servizio da qualche parte, per esempio.
Pertanto, non toccheremo le questioni di sharding, replica e sistemi geo-distribuiti, ma ci concentreremo sulle soluzioni schemiche all'interno del database.
Passo 1: Un po' di specificità aziendale.
La nostra messaggistica sarà progettata non in modo astratto, ma integrata nell'ambiente Cioè, le persone non "si scrivono solo", ma comunicano tra di loro nel contesto della risoluzione di specifici compiti aziendali.
E quali compiti ha un'azienda?.. Vediamo l'esempio di Vasiliy, il capo del reparto sviluppo.
- "Nikolai, per questo compito è necessaria una patch già oggi!"
Quindi, la corrispondenza può avvenire nel contesto di qualche documento. - "Kolia, andiamo stasera a giocare a Dota?"
Cioè, anche tra una coppia di interlocutori, la comunicazione può avvenire simultaneamente su diversi temi.. - "Petr, Nikolai, guarda in allegato il prezzo per il nuovo server."
Quindi, un messaggio può avere diversi destinatari.Inoltre, un messaggio può contenere file allegati.. - "Semyon, dai un'occhiata anche tu."
E ci deve essere la possibilità di invitare un nuovo partecipante in una corrispondenza già esistente..
Fermiamoci per ora su questo elenco di esigenze "ovvie".
Senza comprendere la specificità applicativa del compito e le limitazioni imposte, progettare uno schema di database efficace per la sua soluzione è praticamente impossibile.
Passo 2: Schema logico minimo.
Finora lo schema appare molto simile a una corrispondenza email: uno strumento tradizionale per il business. Infatti, "algoritmicamente" molte esigenze aziendali si somigliano, quindi anche gli strumenti per la loro soluzione saranno strutturalmente simili.
Fissiamo ora lo schema logico delle relazioni tra le entità già ottenuto. Per facilità di comprensione del nostro modello, utilizzeremo la variante più primitiva di rappresentazione senza complicazioni di notazioni UML o IDEF:

Nel nostro esempio, la persona, il documento e il «corpo» binario del file sono entità «esterne» che esistono autonomamente anche senza il nostro servizio. Quindi, considereremo in seguito queste entità come dei riferimenti «da qualche parte» tramite UUID.
Disegnate schemi il più semplici possibile — la maggior parte di quelli a cui li mostrerete non sono esperti nella lettura di UML/IDEF. Ma - disegnate comunque.
Passo 3: Bozza della struttura delle tabelle
Sugli nomi delle tabelle e dei campiI nomi "russi" dei campi e delle tabelle possono essere considerati in vari modi, ma è una questione di gusto. Dato che e PostgreSQL ci consente di dare nomi anche con i geroglifici, se sono racchiusi tra virgolette, preferiamo nominare gli oggetti in modo chiaro e ovvio, per evitare ambiguità.
Poiché ci sono molte persone che scrivono messaggi, alcuni di loro potrebbero farlo offline, la soluzione più semplice è utilizzare UUID come identificatori non solo per entità esterne, ma anche per tutti gli oggetti all'interno del nostro servizio. Inoltre, possono essere generati anche dal lato client - questo ci aiuterà a supportare l'invio dei messaggi in caso di temporanea indisponibilità del DB, e la probabilità di collisione è estremamente bassa.
La struttura di base delle tabelle nel nostro database avrà questo aspetto:
Tabelle : RU
CREATE TABLE "Tema"(
"Tema"
uuid
PRIMARY KEY
, "Documento"
uuid
, "Titolo"
text
);
CREATE TABLE "Messaggio"(
"Messaggio"
uuid
PRIMARY KEY
, "Tema"
uuid
, "Autore"
uuid
, "DataOra"
timestamp
, "Testo"
text
);
CREATE TABLE "Destinatario"(
"Messaggio"
uuid
, "Persona"
uuid
, PRIMARY KEY("Messaggio", "Persona")
);
CREATE TABLE "File"(
"File"
uuid
PRIMARY KEY
, "Messaggio"
uuid
, "BLOB"
uuid
, "Nome"
text
);Tabelle : EN
CREATE TABLE theme(
theme
uuid
PRIMARY KEY
, document
uuid
, title
text
);
CREATE TABLE message(
message
uuid
PRIMARY KEY
, theme
uuid
, author
uuid
, dt
timestamp
, body
text
);
CREATE TABLE message_addressee(
message
uuid
, person
uuid
, PRIMARY KEY(message, person)
);
CREATE TABLE message_file(
file
uuid
PRIMARY KEY
, message
uuid
, content
uuid
, filename
text
);La cosa più semplice quando si descrive il formato è iniziare a «sviluppare» il grafo di relazioni dalle tabelle che non si riferiscono a nessuna.
Passo 4: Scoprire le esigenze non ovvie
Bene, abbiamo progettato un database in cui possiamo ottimamente scrivere e in qualche modo leggere.
Mettiamoci nei panni dell'utente del nostro servizio: cosa vorremmo fare con il suo aiuto?
- Ultimi messaggi
Questo ordinati cronologicamente un registro «dei miei» messaggi per vari criteri. Dove io sono uno dei destinatari, dove sono l'autore, dove mi hanno scritto e non ho risposto, dove non mi hanno risposto, … - Partecipanti alla conversazione
Chi partecipa a questa lunga conversazione?
La nostra struttura consente di risolvere entrambi questi problemi «in generale», ma velocemente — no. Il problema è che per ordinare nell'ambito del primo problema è impossibile creare un indice, adatto per ciascun partecipante (e sarà necessario estrarre tutte le registrazioni), e per risolvere il secondo è necessario estrarre tutti i messaggi relative all'argomento.
Compiti utente non previsti possono compromettere seriamente le prestazioni.
Passo 5: Denormalizzazione sensata
Entrambi i nostri problemi possono essere risolti da tabelle aggiuntive, in cui possiamo duplicare parte dei dati, necessari per formare indici adeguati ai nostri compiti.

Tabelle : RU
CREATE TABLE "RegistroMessaggi"(
"Proprietario"
uuid
, "TipoRegistro"
smallint
, "DataOra"
timestamp
, "Messaggio"
uuid
, PRIMARY KEY("Proprietario", "TipoRegistro", "Messaggio")
);
CREATE INDEX ON "RegistroMessaggi"("Proprietario", "TipoRegistro", "DataOra" DESC);
CREATE TABLE "PartecipanteTema"(
"Tema"
uuid
, "Persona"
uuid
, PRIMARY KEY("Tema", "Persona")
);Tabelle : EN
CREATE TABLE message_registry(
owner
uuid
, registry
smallint
, dt
timestamp
, message
uuid
, PRIMARY KEY(owner, registry, message)
);
CREATE INDEX ON message_registry(owner, registry, dt DESC);
CREATE TABLE theme_participant(
theme
uuid
, person
uuid
, PRIMARY KEY(theme, person)
);Qui abbiamo applicato due approcci tipici utilizzati per la creazione di tabelle ausiliarie:
- Moltiplicazione dei record
Creiamo da una singola registrazione originale del messaggio diverse registrazioni correlate in diversi tipi di registro per diversi proprietari — sia per il mittente che per il destinatario. Ora ogni registro può essere posizionato su un indice — infatti, nel caso tipico vorremmo vedere solo la prima pagina. - Unicizzazione dei record
Ad ogni invio di messaggio all'interno di un tema specifico è sufficiente controllare se esiste già una registrazione di quel tipo. Se no — la aggiungiamo al nostro «dizionario».
Nella prossima parte dell'articolo parleremo di nella struttura del nostro database.
Fonte: habr.com
