Как можем да преведем бизнес изискванията в конкретни структури от данни на примера на проектирането на база за месинджър "от нулата".
- Част 1: проектираме каркаса на базата

Нашата база няма да е толкова мащабна и разпределена, или , а "за да има", но да е добре — функционално, бързо и да се побира на един сървър PostgreSQL — за да можем да разположим отделен екземпляр на услугата някъде настрани, например.
Затова няма да разглеждаме въпросите за шардинг, репликация и геораспределени системи, а ще се фокусираме върху схемни решения вътре в БД.
Стъпка 1: Малко бизнес-специфика
Нашето съобщение ще проектираме не абстрактно, а ще го вградим в контекста на . Тоест, хората не "просто си пишат", а общуват помежду си в контекста на решаването на определени бизнес-задачи.
А какви могат да са бизнес задачите?.. Нека погледнем примера на Василий — ръководител на отдела за разработки.
- "Николай, за тази задача патчът е нужен днес!"
Значи, кореспонденцията може да се води в контекста на някакво документ. - "Коля, идвай вечерта на дота?"
Тоест, дори при една двойка събеседници, комуникацията може да се води по различни теми. - "Петър, Николай, вижте прикачения файл с цените на новия сървър."
Така, едно съобщение може да има няколко адресата. При това съобщението може да съдържа прикачени файлове. - "Семен, и ти погледни."
И трябва да има възможност в вече съществуваща кореспонденция да се покани нов участник.
Засега ще спрем на този списък с "очевидни" нужди.
Без да разбираме приложната специфика на задачата и зададените от нея ограничения, е практически невъзможно да проектираме ефективна схема на БД за нейното решение.
Стъпка 2: Минимална логическа схема
Схемата засега изглежда много подобна на имейл-кореспонденцията — традиционен инструмент за водене на бизнес. Действително, "алгоритмично" много бизнес задачи си приличат, затова и инструментите за тяхното решение ще са структурно сходни.
Нека фиксираме вече получената логическа схема на отношенията между съществата. За удобство на разбирането на нашата модел ще използваме най-примитивния вариант на без усложнения от UML или IDEF нотации:

В наш пример персона, документ и бинарно«тело» файла — это «външни» сущности, които самиостоятелно съществуват и без нашия сервиз. Затова просто ще ги възприемем занапред като някои връзки «някъде» по UUID.
Рисувайте схеми колкото се може по-просто — повечето от тези, които ще им показвате, не са експерти в четенето на UML/IDEF. Но — рисувайте непременно.
Стъпка 3: Набросвайте структурата на таблиците
За имената на таблиците и полетатаКъм «руските» наименования на полетата и таблиците може да се отнасяме по-различно, но това е въпрос на вкус. Тъй като нямаме чуждестранни разработчици, а PostgreSQL ни позволява да даваме имена дори и на йероглифите, ако те са заключени в кавички, предпочитаме да именуваме обектите ясно и разбираемо, за да не възникват недоразумения.
Тъй като съобщенията се пишат от много хора едновременно, част от тях могат да го правят в офлайн режим, най-простият вариант е да използваме UUID като идентификатори не само за външни същности, но и за всички обекти вътре в нашия сервиз. И генерирането им може да се извършва дори от клиентската страна — това ще ни помогне да поддържаме изпращането на съобщения при кратковременна недостъпност на БД, а вероятността за колизия е изключително ниска.
Черновата структура на таблиците в нашата база ще изглежда така:
Таблици : RU
CREATE TABLE "Тема"(
"Тема"
uuid
PRIMARY KEY
, "Документ"
uuid
, "Название"
text
);
CREATE TABLE "Сообщение"(
"Сообщение"
uuid
PRIMARY KEY
, "Тема"
uuid
, "Автор"
uuid
, "ДатаВремя"
timestamp
, "Текст"
text
);
CREATE TABLE "Адресат"(
"Сообщение"
uuid
, "Персона"
uuid
, PRIMARY KEY("Сообщение", "Персона")
);
CREATE TABLE "Файл"(
"Файл"
uuid
PRIMARY KEY
, "Сообщение"
uuid
, "BLOB"
uuid
, "Имя"
text
);Таблици : 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
);Най-простото при описването на формата е — да започнете «развиването» на графа връзки от таблиците, които не се отнасят са сами за себе си.
Стъпка 4: Изясняваме неочевидните нужди
Всичко, проектирахме база, в която може отлично да се пише и по някакъв начин да се чете.
Нека да се поставим на мястото на потребителя на нашия сервис — какво бихме искали да правим с него?
- Последни съобщения
Това подредени хронологично по различни критерии. Къде съм един от адресатите, къде съм авторът, къде ми е било написано, а не съм отговорил, къде не са ми отговорили, … - Участници в разговорите
Кой всъщност участва в този дълъг разговор?
Нашата структура позволява да решим и двете задачи „по принцип“, но бързо — не. Проблемът е, че за подреждане в рамките на първата задача не може да се създаде индекс, подходящ за всеки от участниците (и ще се наложи да извлечем всички записи), а за решаването на втората е необходимо да извлечем всички съобщения по темата.
Непредвидени потребителски задачи могат да поставят сериозен кръст на производителността.
Стъпка 5: Разумна денормализация
И двете наши проблеми могат да бъдат решени с помощта на допълнителни таблици, в които ще дублираме част от данните, необходими за формирането на подходящи индекси за нашите задачи.

Таблици : RU
CREATE TABLE "СписъкСъобщения"(
"Собственик"
uuid
, "ТипСписък"
smallint
, "ДатаЧас"
timestamp
, "Съобщение"
uuid
, PRIMARY KEY("Собственик", "ТипСписък", "Съобщение")
);
CREATE INDEX ON "СписъкСъобщения"("Собственик", "ТипСписък", "ДатаЧас" DESC);
CREATE TABLE "УчастникТема"(
"Тема"
uuid
, "Лице"
uuid
, PRIMARY KEY("Тема", "Лице")
);Таблици : 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)
);Тук приложихме два типични подхода, които се използват при създаване на помощни таблици:
- Умножаване на записите
Създаваме по една основна вхoдяща запис на съобщение с веднага няколко произтичащи записа в различни видове списъци за различни собственици — както за подателя, така и за получателя. Но всеки от списъците вече попада в индекса — тъй като в типичния случай ще искаме да виждаме само първата страница. - Уникализация на записите
При всяко изпращане на съобщение в конкретна тема е достатъчно да проверим дали такава запис вече съществува. Ако не — добавяме го в нашия „речник“.
В следващата част на статията ще говорим за в структурата на нашата база.
Източник: habr.com
