Комментарии 10
Статья хороша, почерпнул много доселе мне неизвестного.
На языке крутится вопрос: а есть ли у нас в стране хоть один ВУЗ, в котором обучают архитектурному проектированию ПО? В котором расскажут про подобные инструменты и потребуют в сдать в их формате лабораторные работы и написать с их помощью какой-нибудь курсовой проект? Или у нас учат только языкам программирования, а подобные знания черпаются уже потом из книг, спецкурсов и собственного опыта?
Спасибо! Я думаю проектированию ПО много где обучают, но в основном там используются готовые языки, методики, инструменты моделирования. Не слышал о специальностях, где рассказывают про метамодели или разработку своих DSL, хотя может они и существуют. В целом это достаточно специфическая область, не так много компаний, которые занимаются разработкой своих языков моделирования или инструментов моделирования. Например, мы занимаемся и у нас есть внутренний курс для сотрудников и заказчиков с лабораторными работами и т.п.
Лично мне просто всегда было интересно моделирование и повезло с проектами. Но вообще я уверен, что эта область знаний могла бы быть полезной во многих проектах и компаниях, а не только каких-то специфических. Возможно статья получилась не очень удачная, попробую это учесть :)
Первая проблема при создании такого рода инструмента - это поддержка актуальности. Поэтому я рассматривал подход model-first, когда модель данных создаётся и редактируется в отдельном приложении, и затем уже синхронизируется с БД и кодом.
Собственно, я такое приложение и разрабатываю. Это что-то вроде dbSchema, но есть возможность передавать всю структуру данных в python-скрипт, который уже в нужном языке сгенерирует нужный код. У каждого свои потребности, поэтому я решил именно так сделать.
Да, у нас тоже model‑first, в этом и смысл модельно‑ориентированного подхода, что модель — это основной артефакт, а всё остальное — производное от модели. Но при этом у модели могут быть разные формы представления. Например, у нас можно открыть модель одновременно в текстовом и диаграммном редакторе, вносить изменения в любом из них, при этом будет обновляться второе представление:

Наличие текстового редактора для моделей не делает наш подход code‑first.
Насчет поддержки актуальности, да, проще всего вносить изменения только в модель, остальное перегенерировать. Это позволяет в том числе версионировать модели, оценивать обратную совместимость изменений в каждой версии, генерить SQL‑скрипты, которые вносят изменения в базу данных, а не пересоздают её.
Обратная задача (reverse engineering — создание или обновление модели по коду) может быть уже немного сложнее, хотя в принципе тоже имеет право на существование. Особенно если есть legacy‑проект, где просто база данных, код и нет никаких моделей
Насчет Python. Вообще, несмотря на то, что в этой области есть много разных DSL для преобразования моделей, генерации кода и т. д. Python тоже достаточно популярный, например, есть Python‑плагин для Capella, ещё есть такая библиотечка для работы с MOF‑моделями в Python. Мы к своему инструменту моделирования тоже планируем прикрутить Python, чтобы не пугать людей QVTo, Acceleo и т. д.
Насчет поддержки актуальности, да, проще всего вносить изменения только в модель, остальное перегенерировать
А по-другому никак... Потому что всегда нужно хранить дополнительные параметры модели, которых нет ни в БД, ни в коде. Притом разрабы работают в разных окружениях (тест, дев, прод) и могут менять модель под свою задачу, а потом это всё надо мержить вместе с кодом, чтобы и тот и тот PR заработал в другом окружении.
И да, всё правильно, нужно делать не пересоздание структуры БД, а изменение. У меня этот механизм называется DDL diff. Состояние БД сравнивается с моделью и генерируется DDL, который приводит БД к состоянию модели. Не напрямую, конечно, с выбором и вычиткой DDL. Это всё довольно сложные механизмы, требующие знаний в механике баз данных.
Тут ещё есть один краеугольный камень - удобство. Это уже больше из области психологии, но всё же. Если разрабам неудобно напрямую в таком редакторе менять структуру - хоть что делай, они будут работать в другом инструменте, и иногда синхронизировать модель. Наверное. Но это не точно.
Обратная задача (reverse engineering — создание или обновление модели по коду) может быть уже немного сложнее, хотя в принципе тоже имеет право на существование. Особенно если есть legacy‑проект, где просто база данных, код и нет никаких моделей
Как раз такой проблемы нет. В случае, если приложение уже умеет генерировать DDL diff. Если есть база данных, то можно прочитать её структуру и создать модель. Механизм всё тот же самый. Конечно, с кодом нужно будет повозиться, особенно если запросы раскиданы по всей кодобазе и особенно, если не использовался ORM. Но это в любом случае надо делать, чтобы понимать, какие части модели действительно используются в коде.
Потому что всегда нужно хранить дополнительные параметры модели, которых нет ни в БД, ни в коде
В принципе и в коде (например, в аннотациях) можно хранить дополнительные параметры или в базе данных в комментариях. Но потом захочется хранить в аннотациях названия названия сущностей и атрибутов на разных языках. Потом захочется хранить в аннотациях только ключи, а сами локализованные строки в properties‑файлах. Потом захочется запилить DSL для описания модели данных. Потом захочется описывать ещё какие‑нибудь модели (машины состояний, процессы). Проще уж сразу для всего этого использовать отдельный инструмент моделирования, где всё хранится в простом и удобном виде.
Ещё модели можно хранить рядом с кодом, не обязательно отдельно
Состояние БД сравнивается с моделью и генерируется DDL, который приводит БД к состоянию модели
Можно так или можно в инструменте моделирования хранить версии моделей и сравнивать их между собой
Если разрабам неудобно напрямую в таком редакторе менять структуру - хоть что делай, они будут работать в другом инструменте, и иногда синхронизировать модель
Здесь вопрос кто отвечает за модель данных. Если аналитики и архитекторы, то им может быть удобнее работать в инструменте моделирования, где у них остальные модели.
Например, на наших проектах было неудобно редактировать SQL‑скрипты и Java‑код руками, потому что модель данных достаточно большая, всё это правилось разными разработчиками. Во-первых это просто муторно править этот boilerplate код, причём в нескольких местах и ещё документацию обновлять. Во-вторых несмотря на обилие правил линтеров, архитектурных тестов на ArchUnit и т.д. всё равно этот код оскорблял мои чувства перфекциониста. Проще поправить в одном месте, перегенерить всё и получить идеальный код, соответствующий всем соглашениям.
На мой взгляд процесс разработки должен быть выстроен так что правится только модель данных, остальное генерируется и не возникает проблемы, что что-то хочется поправить в коде минуя модель. Плюс, согласен, и инструмент моделирования должен быть удобным, например, поправил модель в той же IDE, в которой работаешь с кодом, перегенерил код и всё
Так а как синхронизировать "сторонний" код, который не хранится в модели, с новым состоянием системы после изменения модели и перегенерации всех артефактов?
Это делается ручками на основе понимания, что было изменено в модели?
Идеальный вариант — это разделить сгенеренный и написанный вручную код. Есть разные способы это сделать:
Например, генерим Java‑классы для ORM или ещё какие‑нибудь и всё, руками в них ничего не правим, только ссылаемся на них. При перегенерации эти файлы просто полностью заменяют предыдущие версии
Если всё‑таки нужно что‑то править, то типовой подход — унаследовать от сгенеренных классов свои и реализовать в них какие‑нибудь методы, которые не могут быть сгенерены. В этом случае мы тоже можем полностью заменять перегенеренные файлы и нет сложностей с синхронизацией изменений
Ещё хорошая идея хранить сгенеренный код отдельно в папке src‑gen или generated. Или в папке target, если он генерится при сборке
На мой взгляд, для большинства случаев этого достаточно
Более сложный вариант — это отметить в сгенеренном коде защищенные области исправленные вручную. И кодогенератор не будет их переписывать. Например, Acceleo это поддерживает:
Incremental Generation
Whether you are considering it or not, you will one day have to modify manually your generated code and you want to keep your modification even if you are regenerating your code. Acceleo lets you define protected areas in which you can safely modify the generated.
Ещё более замороченный вариант — это вообще не генерить код, а интерпретировать модели в runtime. Но это гораздо сложнее с точки зрения отладки, сгенеренный код хотя бы посмотреть можно, а что там в runtime происходит не узнаешь пока не запустишь приложение. Плюс сгенеренный код обычно гораздо проще, чем код такого интерпретатора. Ещё в этом варианте дополнительные накладные расходы на интерпретацию модели

Получается, у локализации могут быть свои локализации (и так далее, до самого дна)? Если нет, то почему композиция связывает её с NamedElement, а не с DataModel?
Да, жесть, хз как я упустил этот момент :) В принципе это легко решается добавлением дополнительного класса LocalizedElement, который наследуется от NamedElement и в который переносится localizations или иначе. Но в итоге мы отказались от этой схемы и храним переводы в мапах «локаль — перевод»:

У нас уже в настоящем инструменте моделирования, про который я расскажу в следующей статье, есть упрощенный редактор метамоделей и в нём хотелось простой способ отмечать, что такой-то атрибут является локализуемым. Чтобы людям не приходилось добавлять эти классы Localization в каждую метамодель.
Пример решения одной задачи с помощью модельно-ориентированного подхода