Come tradurre le esigenze aziendali in strutture dati concrete, ad esempio progettando da zero il database per il messaggero.
- Parte 1: progettazione della struttura del database

Il nostro database non sarà così grande e distribuito, o , ma 'solo per avere', ma deve essere buono — funzionale, veloce e che possa stare su un solo server PostgreSQL — per poter distribuire un'istanza separata del servizio da qualche parte, ad esempio.
Pertanto, non tratteremo le questioni di sharding, replicazione e sistemi geograficamente distribuiti, ma ci concentreremo sulle soluzioni schemaliche all'interno del database.
Passo 1: Un po’ di specifiche aziendali
Progetteremo il nostro scambio di messaggi non in modo astratto, ma inserendolo nell'ambiente Cioè, le persone non 'si scambiano solo messaggi', ma comunicano tra loro nel contesto della soluzione di specifici problemi aziendali.
E quali sono le problematiche aziendali? ... Vediamo attraverso l'esempio di Vasiliy — responsabile del reparto sviluppo.
- 'Nikolai, per questo compito è necessaria la patch già oggi!'
Significa che la corrispondenza può avvenire nel contesto di qualcosa del documento. - «Ciao, andiamo a giocare a Dota stasera?»
Cioè, anche in una coppia di interlocutori, la conversazione può avvenire simultaneamente su temi diversi. - «Pietro, Nicola, date un'occhiata all'allegato con il prezzo del nuovo server.»
Quindi, un messaggio può avere diversi destinatari. Inoltre, il messaggio può contenere file allegati. - «Semion, anche tu dai un'occhiata.»
E dovrebbe esserci la possibilità di invitare un nuovo partecipante in una conversazione già esistente..
Fermiamoci per ora a questo elenco di esigenze "ovvie".
Senza comprendere la specificità applicativa del compito e i vincoli richiesti, progettare una scheda DB efficace per risolverla è praticamente impossibile.
Passo 2: Schema logico minimo
Finora, tutto sembra molto simile a una corrispondenza via email — uno strumento tradizionale per fare affari. Infatti, «algoritmicamente», molte sfide aziendali sono simili tra loro, quindi anche gli strumenti per risolverle saranno strutturalmente simili.
Fissiamo già lo schema logico delle relazioni tra le entità che abbiamo ottenuto. Per facilitare la comprensione del nostro modello, utilizzeremo la variante più semplice di visualizzazione senza complicazioni di UML o notazioni 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. Pertanto, d'ora in poi le considereremo semplicemente come dei riferimenti "da qualche parte" tramite UUID.
Disegna schemi nel modo più semplice possibile — la maggior parte delle persone a cui li mostrerai non è esperta nella lettura di UML/IDEF. Ma — disegna comunque.
Passo 3: Schizziamo la struttura delle tabelle
Riguardo ai nomi di tabelle e campiAi nomi "russi" dei campi e delle tabelle si può avere un'opinione diversa, ma è una questione di gusto. Poiché non ci sono sviluppatori stranieri, e PostgreSQL ci consente di dare nomi anche in geroglifici, se sono racchiusi tra virgolette, preferiamo nominare gli oggetti in modo chiaro e comprensibile, per evitare ambiguità.
Poiché i messaggi vengono scritti simultaneamente da molte persone, alcune di esse possono farlo in modalità offline, l'opzione più semplice è utilizzare UUID come identificatori. non solo per entità esterne, ma anche per tutti gli oggetti all'interno del nostro servizio. Inoltre, è possibile generarle anche dal lato client — questo ci aiuterà a mantenere l'invio di messaggi durante la breve indisponibilità del database, mentre 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 delle relazioni da tabelle che non fanno riferimento a nessuno.
Passo 4: Identificare esigenze non evidenti
Bene, abbiamo progettato una base in cui si può scrivere e in qualche modo leggere.
Mettiamoci nei panni dell'utente del nostro servizio: cosa vorremmo fare con il suo aiuto?
- Ultimi messaggi
Questo registro 'dei miei' messaggi ordinato cronologicamente per vari criteri. Dove 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 entrambe queste questioni 'in generale', ma rapidamente — no. Il problema è che per ordinare nell'ambito del primo compito non è possibile creare un indice, adatto per ognuno dei partecipanti (e sarà necessario estrarre tutte le registrazioni), e per risolvere il secondo è necessario estrarre tutti i messaggi sull'argomento.
Compiti utente non previsti possono compromettere pesantemente le performance..
Passo 5: Denormalizzazione ragionevole
Entrambe le nostre problematiche possono essere affrontate con ulteriori tabelle, in cui duplicheremo parte dei dati , necessari per formare indici adatti alle nostre esigenze., necessari per la creazione di indici adatti 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 nella creazione di tabelle ausiliarie:
- Moltiplicazione di record
Creiamo più record conseguenti da un singolo record originario di messaggio su diversi tipi di registro per diversi proprietari — sia per il mittente che per il destinatario. In questo modo, ciascun registro ora si basa su un indice — in quanto avremmo tipicamente il desiderio di vedere solo la prima pagina. - Unificazione dei record
Ogni volta che un messaggio viene inviato all'interno di un tema specifico, è sufficiente controllare se esiste già un tale record. Se non esiste — lo aggiungiamo al nostro 'glossario'.
Nella prossima parte dell'articolo si parlerà di nella struttura del nostro database.
Fonte: habr.com
