У реляционных баз данных есть один небольшой недостаток - они не выводят табличные данные на экран пользователя. С одной стороны - какой же это недостаток, когда технология занимается исключительно своими базаданновыми вещами и не лезет туда, где её не надо. С другой - приходится данные пробрасывать через другой язык программирования, а то и два-три в случае веба. Ну и сам язык запросов забывать не надо. Он тоже вроде как язык.
Все существующие языки предназначены для уменьшения цифровой энтропии и оснащены инструментами для уборки цифрового навоза (кроме брейнфака, пожалуй). В том числе и инструментами для сбора в кучу разрозненных полей, да ещё и с возможностью как-то назвать эту структуру. В ООП языках это вообще часть парадигмы с далеко идущими последствиями, можно сказать, это у них в крови. А ООП языков, работающих между БД и экраном пользователя, осмелюсь предположить, большинство. Естественно, у разработчика возникает сразу желание воспользоваться структурными инструментами языка, чтобы утрамбовать поля таблицы в класс. Вот так и появляются дтошечки, энтитички, поджошечки и прочие попочки.
И вроде не велика проблема - делаем структуру в нашем любимом языке, такую же, как в БД и из наших кубиков складывается слово "счастье". Складываться-то оно складывается, но ненадолго. Очень быстро структура БД и структура в коде начинают разъезжаться. А если не начинают, то потыкайте в свой проект палочкой - скорее всего он уже умер. Как говорится "не щебечет дохлый щегол, а мёртвый проект не меняет свою структуру". Ну и далее по тексту...
Не сказать бы, что эту проблему не научились решать. Решают, и ещё как. Например, я слышал такой способ: надо просто аккуратно вносить изменения и в БД и одновременно в код, и не ошибаться. Тогда ничего разъезжаться не будет. Вы тоже поржали, да?
Прежде чем перейти к рабочим методам решения проблемы, я хочу акцентировать внимание на том, что это именно фундаментальная проблема. Она не зависит от базы, от языка и фреймворка. Вы можете быть сколько угодно продвинутым архитектором, сколь угодно помидористым синьёром, но если вы работаете с базой данных, то эта проблема у вас точно есть.
Да, я сейчас говорю про реляционные формы данных. В документных БД свои погремушки, там дрейф структуры приобретает особо извращённые очертания, когда в одной таблице объекты со временем обрастают новыми полями, правила консистентности меняются от айди к айди, а потом вообще код может обрасти костылями вроде разных десериализаторов в зависимости от диапазона айди. Потому что формат сменился. Сверху по вкусу посыпать версионными преобразованиями на лету.
В реляционных БД изменения делаются для всех объектов сразу, если вы добавляете поле, оно добавляется и к самым первым объектам, делая их совместимыми с новой структурой. Конечно, иногда приходится прогонять какой-нибудь мигратор, чтобы привести старые данные в новый формат консистентности, но это избавляет от головной боли, связанной с многоверсионностью данных. Так что я отношусь к этому как к статической типизации. Как к инструменту, который заставляет держать структуру в порядке. Так что вернёмся к реляционкам.
Наверняка вы слышали на собеседовании вопрос "чем отличается code-first от db-first, какие преимущества и недостатки". Этот вопрос очень любят на собеседованиях и я вам принёс ответ на него. Тут я хотел добавить мем с Пахомом, но постеснялся. Поэтому только таблица.
Подход |
Преимущества |
Недостатки |
|---|---|---|
Code-first |
Приводит структуру БД в соответствии с кодом |
Приводит структуру БД в соответствии с кодом |
DB-first |
Приводит структуру в коде в соответствии с БД |
Приводит структуру в коде в соответствии с БД |
Не стоит благодарить, я от чистого сердца вам помогаю.
Я себя всегда относил к лагерю "DB-first". Ну потому что это же данные. Вот чисто интуитивно - как это позволить какому-то мигратору ковырять базу? А вдруг кто-то случайно удалил или переименовал поле в коде? Тогда что, просто удалится столбец в БД?
Когда случается ошибка при генерации модели в db-first - то ничего страшного. Легко перегенерить, поправить или даже восстановить из гита. В случае обратного преобразования структуры, т.е. из кода делаем структуру БД, то затрагивается не только структура, но и сами данные. А это очень ценная штука. Из гита их не взять, в бекапе лежат вчерашние, а зеркало уже перезатёрлось.
Как-то я уже привык считать, что данные первичны. Да и когда мы делаем новый проект, обычно данные уже есть. Даже если это не готовая реляционная база, то как минимум эксельные файлы. Сначала делается структура в БД, а потом вокруг этой структуры уже пишется код. И структуре уделяется максимальное внимание, потому что "garbage in - garbage out". А править структуру БД намного сложнее, чем код. Потому что там ещё и данные лежат.
Code-first я считал неким таким баловством, которое проще изучить, когда только сталкиваешься с реалиями БД и делаешь свои первые пет-проекты. И считал, что в дальнейшем этот подход принесёт проблемы при росте данных, в которых, на минуточку, вложено много труда пользователей системы. Правда code-first хорошо лежит в гите, а значит и хорошо бранчится по фич-веткам, чтобы потом благополучно смерджиться. Это явно плюс.
В реальности оба подхода работают и имеют право на жизнь. Тогда в чём же проблема и причём тут аксиома Эскобара?
Чтобы разобраться, нам нужно понять, а что именно делают эти два подхода. Генерация кода и синхронизация с БД - это инструменты. А в самих подходах есть нечто более идеологическое, более высокоуровневое, чем выполнение скрипта при запуске вашего приложения.
Источник истины

Да, это единственная разница. Просто разные источники истины.
Тадам! Оказывается это тот же Шалтай-Болтай, только в анфас и профиль. Один подход декларирует, что истина в коде, а второй - что истина в БД. Не "правда", а именно истина. Когда данные разъезжаются, правы и код и БД. Да и вообще, как БД может быть не права? Она не умеет врать. И код не умеет врать. Это прерогатива кожанных (с недавних пор ещё и некоторых электронных болванов).
Просто мы условно считаем, что БД глаголет истину, а код изначально еретичен. Ну или наоборот. Это не значит, что код врёт. Он просто неверный, в том плане, что за ним следить надо, чтобы он следовал истинному и не оступался от пути праведного.
Вот только мой любимый db-first перестаёт работать при наличии нескольких окружений. Вот есть прод база, есть стейдж, есть дев, есть тест. Ну и локальная база под каждую фичу. И у всех истина. Ну не бывает же так, что у всех истина, правда? Да ещё и разная. Code-first имеет симметричный фатальный недостаток - он перестаёт работать при наличии более одного сервиса на БД. А если много сервисов на нескольких окружениях - то вообще хоть вешайся.
Не сказать бы, что эти проблемы не научились решать. Научились, и ещё как. Как говорится, если парадигма не работает, то вы добавили слишком мало костылей. В случае db-first это сложные синхронизаторы окружений, это история миграций и много чего ещё. Не очень-то просто.
Но меня сейчас интересует не конструкция костылей для работы подходов, а какое-то более фундаментальное решение вопроса. И оно у меня есть.
В туториалах и жарких спорах между сторонниками этих двух подходов забывают, что это классическая ложная дихотомия. Существует как минимум ещё один подход, который логичен и понятен. Давайте объясню на примере проектирования реляционных баз данных.
Ну вот смотрите: когда у нас отношение 1:М, то ссылающийся айди указывается в правой таблице. Когда М:1, то в левой. А когда М:М? Правильно, делается таблица-связка. Отдельная.
Может не очень удачное сравнение, но действительно, чтобы избежать проблем, описанных выше, нужно вынести источник истины в отдельную сущность. Это будет не код, это будет не БД. Это будет нечто другое. И только тогда это будет работать с несколькими окружениями и с несколькими сервисами без фундаментальных противоречий.
Такой источник истины принято называть моделью. А подход - model-first.
Вот шарписты помнят старый Entity Framework, который использует подход model-first. И уже наверное представляют, о чём я говорю. Только в EF model-first... плюшевый. Почему плюшевый?
Потому что .edmx файлы модели находятся внутри солюшена. Потому что только EF с ними работает. А если вам надо добавить другой проект к этой же модели? А если захотите использовать dapper или вообще самописный маппер? А если у вас там ещё подпружиненный CRM на hibernate крутится и джангисты из логистики лезут периодически в ту же базу по своим логистическим делам? А я напомню, всё не в статике, дрейф структуры никто не отменял. Пока вы пилите свою часть, часть модели уже уплыла. Можете, конечно, попросить джангистов ничего не трогать, пока вы пилите свою часть, но я догадываюсь, что вам ответят.
Нет, настоящий model-first должен быть независим от языка, фреймворка и мировоззрения создателя генератора кода. Истинно говорю вам. Только так его можно расшарить между командами и сервисами. Соответственно, технология должна быть language-agnostic. В EF модель гвоздями прибита к вижуал студии (которая протухла на маках год назад, а на линукс вообще никогда не завозилась) и к си шарпу. Я ничего не имею против шарпа - это мой любимый язык. Но я не могу поддержать лозунг "давайте всё перепишем на один язык, чтобы всем стало хорошо". Ну ладно, даже если у вас все сервисы написаны на шарпе с EF, то всё равно отсутствует "гальваническая развязка" между моделью и структурой в коде. То есть все проекты, которые пользуются этой моделью, вынуждены иметь полный набор сущностей из модели. Хотя далеко не всё им надо.
Модель должна быть единой для всех сервисов и расшарена между командами. Вам в любом случае придётся договариваться между собой, как должна выглядеть структура данных проекта. Вот модель - это как раз письменный контракт о ваших договорённостях. Там должна присутствовать та самая ИСТИНА, о которой я так долго говорю. А ещё хорошо было бы эту самую истину положить в гит, версионировать, диффануть, разделить на фич-ветки чтобы потом смержить или даже просто обмениваться по емейлу или показать в зуме, чтобы определить, чья истина истиннее.
Три года назад я искал third-party инструменты, которые без боли и страданий позволят работать с моделью.

Вот парадигма есть, а инструментов - нет. Как то самое слово, про которое говорила Фаина Раневская. Я начал понимать, почему подход не пользуется популярностью.
Максимально приближена к моим требованиям была одна софтина немецкого производства, но она больше была заточена под ER-диаграммы, чем под серьёзные проекты. Мне же хотелось большего.
Софтина, кстати, была весьма платная. Что-то около 250 долларов на разраба в год. Сейчас ещё дороже. В бесплатной версии не работает с файлами модели. Отличные условия, да? Просто входной билет в model-first - 250 долларов в год. А лучше ничего нет. Изобретайте, пожалуйста, свои велосипеды. Только чтобы там гуй был, поддержка идеологии RDBMS со всеми нюансами, продвинутая синхронизация структуры БД и кодогенерация. Даже на первый взгляд не очень просто выглядит.
Поэтому два года назад я начал писать свой database model editor. С этими самыми, да. Как я вижу, как я чувствую. Тоже с ER-диаграммами (пусть будут), но уже с упором именно на разработку и поддержку сложных проектов. Это же просто - за пару месяцев можно прототип склепать. Чуть сложнее, чем когтеточки, зато не так прибыльно.
За два года решил несметное количество неожиданных проблем. И только сейчас закончил работу над основной идеологией, когда уже можно действительно пользоваться продуктом в реальной разработке. Можно сказать MVP. Сейчас хочу набрать бесплатных тестеров для устранения багов.
Пожалуй, о возможностях продукта нужно будет сделать отдельную статью. Но вот хочется привести пример из последних изменений: я добавил фичу "Named Rows". Это когда часть данных (именно данных, а не структуры) попадает в ваш кодогенератор, позволяя к отдельным объектам из таблицы обращаться в коде по имени.
Там я решил проблему разных окружений, разных языков, разных фреймворков и даже разных подходов. Решил психологическую проблему "можно ли доверять изменение БД стороннему приложению". Я по-своему реализовал "гальваническую развязку" между моделью и её представлением в коде. Там много интересных решений. Но сегодня я хотел рассказать именно о том, почему "model-first". Поэтому на сегодня всё.
Да прибудет с вами Истина. Всем пше згыр!
Комментарии (9)

kmatveev
12.10.2025 19:11Блин, как же приятно читать текст, написанный живым человеком!
Наша команда реализовала model-first, используя protobuf-описания в качестве модели. Из них генерируются классы на C++ и Java, из них же генерируются таблицы в базе, protobuf используется как формат сериализации. Из недостатков: нет описания связей, нам пока норм, в будущем можно будет доработать плагины, чтобы внести эту функциональность.

Kerman Автор
12.10.2025 19:11Спасибо. Я для написания прошлой статьи делал систему, где всё наоборот. Была модель, а из неё делались entity классы и proto-файлы. Вот репозиторий. Туда я тоже джойнов не завёз, хотя в оригинальной системе они были.

whoisking
12.10.2025 19:11Вот парадигма есть, а инструментов - нет.
Знаю такой один, правда, завязан на постгрес
https://docs.geldata.com/reference/datamodel
(Ранее назывался edgedb)
sepulkary
А зачем зеркалить модели в базу? Чистая архитектура рекомендует писать бизнес-логику так, как удобно для бизнес-домена, а база висит где-то там, за интерфейсом.
Понятно, что это идеал, часто бизнес-логика размазана по хранимкам и триггерам, но стоит ли зеркалить...
Kerman Автор
Ну как зачем? Модель создана для того, чтобы зеркалиться в базу. Это её назначение. Если мы этого не делаем, то у нас DB-first и модель нам не нужна.
Так model-first и не вступает в противоречие с DDD. Они вообще на разных уровнях. Model-first решает проблему поддержки консистентности структуры базы и кода. В вашем случае можно было бы использовать, ну назовём это "3d model first". Когда предметная область задаёт структуру данных, то есть модель, а из модель подтягивает структуру БД до истинной и генерит код.
sepulkary
Если я правильно понимаю чистую архитектуру:
бизнес-логика - это то, чем люди будут заниматься даже при отсутствии какой-либо вычислительной техники вообще; скажем, банкиры будут считать сложный процент, а астрофизики - параметры звёзд, даже если у них останется только бумажка с ручкой; соответственно, при создании модели вообще про такие вещи, как зеркалирование, SQL, NoSQL думать нельзя, это инфицирует бизнес-логику чуждым кодом;
можно сказать, что вообще "никто не first"; Роберт Мартин особо подчёркивал (и даже гордился), что БД - это такой же легко заменяемый сегмент внешнего круга ПО, как, скажем, веб-интерфейс (с учётом реального положения дел, такое, конечно, можно встретить нечасто), и "проблемы базы шерифа не волнуют";
консистентность и правда хромает.
Но это, разумеется, только один из подходов к проектированию ПО, абсолютизировать его было бы неразумно.
whoisking
Не работает эта чистейшая архитектура на практике без "инфицирования бизнес-логики чужим кодом". Начиная с простого примера - уникальности сущностей. Создаём констрейнты и в этот момент уже смешиваем слой бд с бизнес-логикой. Как мы должны поступить в чистой архитектуре? Писать код, который будет проходить по всем записям и проверять, есть ли эта сущность уже в хранилище?
Kerman Автор
Без вычислительной техники - возможно. Без схемы данных - неа.
Бизнес-логика просто ничего не сможет сделать без модели. У всех звёзд одни и те же параметны, но разные. Процент пишется одними и теми же цифрами по одному и тому же правилу, от такой же цифры слева от процента. И получится абсолютно прямоугольная таблица, только нарисованная ручкой.
sepulkary
Без структур данных действительно мало что получится. Цитируя Торвальдса, "Плохие программисты беспокоятся о коде. Хорошие программисты беспокоятся о структурах данных и их взаимосвязях".
Но структура данных ≠ схема данных. Возможно, вам для бизнес-логики оптимально подойдёт красно-чёрное или префиксное дерево, и заранее засовывать его в прокрустово ряляционное ложе было бы, на мой взгляд, большой ошибкой.