«Database as Сode» Experience


    SQL, что может быть проще? Каждый из нас может написать простенький запрос — набираем select, перечисляем необходимые колонки, затем from, имя таблицы, немного условий в where и все — полезные данные у нас в кармане, причем (почти) независимо от того какая СУБД в это время находится под капотом (а может и не СУБД вовсе). В результате работу практически с любым источником данных (реляционным и не очень) можно рассматривать с точки зрения обычного кода (со всеми вытекающими — version control, code review, статический анализ, автотесты и вот это все). И это касается не только самих данных, схем и миграций, а вообще всей жизнедеятельности хранилища. В этой статье поговорим о повседневных задачах и проблемах работы с различными БД под прицелом "database as code".


    И начнем прямо с ORM. Первые батлы вида "SQL vs ORM" были замечены еще в допетровской Руси.


    Объектно-реляционный мапинг

    Сторонники ORM традиционно ценят скорость и простоту разработки, независимость от СУБД и чистоту кода. Для многих из нас код работы с БД (а зачастую и сама БД)


    обычно выглядит примерно так...
    @Entity
    @Table(name = "stock", catalog = "maindb", uniqueConstraints = {
            @UniqueConstraint(columnNames = "STOCK_NAME"),
            @UniqueConstraint(columnNames = "STOCK_CODE") })
    public class Stock implements java.io.Serializable {
    
        @Id
        @GeneratedValue(strategy = IDENTITY)
        @Column(name = "STOCK_ID", unique = true, nullable = false)
        public Integer getStockId() {
            return this.stockId;
        }
      ...

    Модель обвешена умными аннотациями, а где-то за кулисами доблестный ORM генерирует и выполняет тонны какого-то SQL-кода. К слову сказать, разработчики всеми силами пытаются отгородиться от своей БД километрами абстракций, что говорит о некоторой "SQL ненависти".


    По другую сторону баррикад приверженцы чистого "handmade"-SQL отмечают возможность выжимать все соки из своей СУБД без дополнительных прослоек и абстракций. В результате чего появляются "data-centric" проекты, где базой занимаются специально обученные люди (они же "базисты", они же "базовики", они же "базденьщики" и т.д.), а разработчикам только остается "дергать" готовые вьюхи и хранимые процедурки, не вдаваясь в подробности.


    А что если взять лучшее из двух миров? Как это сделано в замечательном инструменте с жизнеутверждающим названием Yesql. Приведу пару строк из общей концепции в моем вольном переводе, а более подробно с ней можно познакомиться здесь.


    Clojure это крутой язык для создания DSL'ей, но SQL уже сам по себе является крутым DSL, и нам не нужен еще один. S-выражения прекрасны, но здесь они не добавляют ничего нового. В итоге получаем скобки ради скобок. Не согласны? Тогда дождитесь того момента когда абстракция над БД даст течь, и вы начнете борьбу с функцией (raw-sql)

    И что делать? Давайте оставим SQL обычным SQL'ем — один файл на один запрос:

    -- name: users-by-country
    select *
      from users
     where country_code = :country_code

    … а затем прочитайте этот файл, превратив его в обычную Clojure функцию:

    (defqueries "some/where/users_by_country.sql"
       {:connection db-spec})
    
    ;;; A function with the name `users-by-country` has been created.
    ;;; Let's use it:
    (users-by-country {:country_code "GB"})
    ;=> ({:name "Kris" :country_code "GB" ...} ...)

    Придерживаясь принципа "SQL отдельно, Clojure отдельно", вы получаете:
    • Никаких синтаксических сюрпризов. Ваша база данных (как и любая другая) не соответствует SQL стандарту на 100% — но для Yesql это не важно. Вы никогда не будете тратить время на охоту за функциями с синтаксисом эквивалентным SQL. Вам никогда не придется возвращаться к функции (raw-sql "some ('funky' :: SYNTAX)")).
    • Лучшая поддержка редактора. Ваш редактор уже имеет отличную поддержку SQL. Сохраняя SQL как SQL, вы можете просто использовать его.
    • Командная совместимость. Ваши DBA могут читать и писать SQL, который вы используете в своем Clojure проекте.
    • Более простая настройка производительности. Нужно построить план для проблемного запроса? Это не проблема, когда ваш запрос является обычным SQL.
    • Повторное использование запросов. Перетащите эти же SQL-файлы в другой проекты, потому что это просто старый добрый SQL — просто поделитесь им.

    По моему идея очень крутая и при этом очень простая, благодаря чему проект обрел множество последователей на самых разных языках. А мы далее попробуем применить похожую философию отделения SQL-кода от всего остального далеко за пределами ORM.


    IDE & DB-менеджеры

    Начнем с простой повседневной задачи. Часто нам приходится искать какие-либо объекты в БД, например, найти таблицу в схеме и изучить ее структуру (какие используются колонки, ключи, индексы, констрейнты и прочее). И от любой графической IDE или маломальского DB-manager'а, в первую очередь, мы ждем именно этих способностей. Чтобы было быстро и не пришлось ждать по полчаса, пока нарисуется окошко с нужной информацией (особенно при медленном соединении с удаленной БД), и при этом чтобы полученная информация была свежей и актуальной, а не закешированное старье. Причем чем сложнее и крупнее БД и больше их количество, тем сложнее это сделать.


    Но обычно я забрасываю мышь куда подальше и просто пишу код. Допустим, необходимо узнать, какие таблицы (и с какими свойствами) содержаться в схеме "HR". В большинстве СУБД нужного результата можно добиться вот таким нехитрым запросом из information_schema:


    select table_name
         , ...
      from information_schema.tables
     where schema = 'HR'

    От базы к базе содержимое таких таблиц-справочников варьируется в зависимости от способностей каждой СУБД. И, например, для MySQL из этого же справочника можно получить специфичные для этой СУБД параметры таблицы:


    select table_name
         , storage_engine -- Используемый "движок" ("MyISAM", "InnoDB" etc)
         , row_format     -- Формат строки ("Fixed", "Dynamic" etc)
         , ...
      from information_schema.tables
     where schema = 'HR'

    Oracle не умеет information_schema, зато у него есть Oracle metadata, и больших проблем не возникает:


    select table_name
         , pct_free       -- Минимум свободного места в блоке данных (%)
         , pct_used       -- Минимум используемого места в блоке данных (%)
         , last_analyzed  -- Дата последнего сбора статистики
         , ...
      from all_tables
     where owner = 'HR'

    Не исключение и ClickHouse:


    select name
         , engine -- Используемый "движок" ("MergeTree", "Dictionary" etc)
         , ...
      from system.tables
     where database = 'HR'

    Что-то похожее можно сделать и в Cassandra (где есть columnfamilies вместо tables и keyspace'ы вместо схем):


    select columnfamily_name
         , compaction_strategy_class  -- Стратегия сборки мусора
         , gc_grace_seconds           -- Время жизни мусора
         , ...
      from system.schema_columnfamilies
     where keyspace_name = 'HR'

    Для большинства остальных БД также можно придумать похожие запросы (даже в Mongo есть специальная системная коллекция, которая содержит в себе информацию о всех коллекциях в системе).


    Само собой, таким способом можно получить информацию не только о таблицах, а вообще о любом объекте. Периодически добрые люди делятся таким кодом для разных БД, как, например, в серии хабра-статей "Функции для документирования баз данных PostgreSQL" (айб, бен, гим). Само собой, держать всю эту гору запросов в голове и постоянно набирать их — это "такое себе" удовольствие, поэтому в любимой IDE/редакторе у меня есть заранее заготовленный набор сниппетов для часто используемых запросов, и остается только впечатать имена объектов в шаблон.


    В итоге такой способ навигации и поиска объектов гораздо более гибок, экономит много времени, позволяет получить именно ту информацию и в том виде, в котором сейчас необходимо (как, например, описано в посте "Экспорт данных из БД в любом формате: что умеют IDE на платформе IntelliJ").


    Операции с объектами

    После того как мы нашли и изучили нужные объекты, самое время с ними сделать что-нибудь полезное. Естественно, также не отрывая пальцев от клавиатуры.


    Не секрет, что простое удаление таблицы будет выглядеть одинаково почти во всех БД:


    drop table hr.persons

    А вот с созданием таблицы уже поинтереснее. Практически любая СУБД (в том числе и многие NoSQL) в том или ином виде умеет "create table", и основная его часть даже мало будет отличаться (имя, список колонок, типы данных), но остальные детали могут разительно отличаться и зависят от внутреннего устройства и возможностей конкретной СУБД. Мой любимый пример — в документации Oracle только одни "голые" БНФ'ы для синтаксиса "create table" занимают 31 страницу. Другие СУБД обладают более скромными возможностями, но каждая из них также обладает множеством интересных и уникальных фич по созданию таблиц (postgres, mysql, cockroach, cassandra). Вряд ли какой-нибудь графический "wizard" из очередной IDE (особенно универсальной) сможет полностью покрыть все эти способности, а если и сможет, то это будет зрелище не для слабонервных. В то же время правильно и вовремя написанный оператор create table позволит без труда воспользоваться всеми из них, сделать хранение и доступ к вашим данным надежным, оптимальным и максимально комфортным.


    Также во многих СУБД есть свои специфичные типы объектов, которые отсутствуют в других СУБД. Причем мы можем выполнять операции не только над объектами БД, но и над самой СУБД, например "убить" процесс, освободить какую-либо область памяти, включить трассировку, перейти в режим "read only" и многое другое.


    А теперь немного порисуем

    Одна из самых распространенных задач — построить диаграмму с объектами БД, на красивой картинке увидеть объекты и связи между ними. Это умеет практически любая графическая IDE, отдельные «command line»-утилиты, специализированные графические тулы и моделлеры. Которые вам что-то нарисуют "как умеют", а немного повлиять на этот процесс можно только с помощью нескольких параметров в конфигурационном файле или галочек в интерфейсе.


    Но эту проблему можно решить гораздо проще, гибче и элегантнее, и конечно же с помощью кода. Для построения диаграмм любой сложности у нас есть сразу несколько специализированных языков разметки (DOT, GraphML etc), а к ним — целая россыпь приложений (GraphViz, PlantUML, Mermaid), которые умеют читать такие инструкции и визуализировать в самых разных форматах. Ну а информацию об объектах и связях между ними мы уже знаем как получить.


    Приведем небольшой пример того, как это могло бы выглядеть, с использованием PlantUML и демонстрационной база данных для PostgreSQL (слева SQL-запрос, который сгенерирует нужную инструкцию для PlantUML, а справа результат):



    select '@startuml'||chr(10)||'hide methods'||chr(10)||'hide stereotypes' union all
    select distinct ccu.table_name || ' --|> ' ||
           tc.table_name as val
      from table_constraints as tc
      join key_column_usage as kcu
        on tc.constraint_name = kcu.constraint_name
      join constraint_column_usage as ccu
        on ccu.constraint_name = tc.constraint_name
     where tc.constraint_type = 'FOREIGN KEY'
       and tc.table_name ~ '.*' union all
    select '@enduml'

    А если немного постараться, то на основе ER-шаблона для PlantUML можно получить что-то сильно похожее на настоящую ER-диаграмму:


    SQL-запрос чуууть посложнее
    -- Шапка
    select '@startuml
            !define Table(name,desc) class name as "desc" << (T,#FFAAAA) >>
            !define primary_key(x) <b>x</b>
            !define unique(x) <color:green>x</color>
            !define not_null(x) <u>x</u>
            hide methods
            hide stereotypes'
     union all
    -- Таблицы
    select format('Table(%s, "%s \n information about %s") {'||chr(10), table_name, table_name, table_name) ||
           (select string_agg(column_name || ' ' || upper(udt_name), chr(10))
              from information_schema.columns
             where table_schema = 'public'
               and table_name = t.table_name) || chr(10) || '}'
      from information_schema.tables t
     where table_schema = 'public'
     union all
    -- Связи между таблицами
    select distinct ccu.table_name || ' "1" --> "0..N" ' || tc.table_name || format(' : "A %s may have\n many %s"', ccu.table_name, tc.table_name)
      from information_schema.table_constraints as tc
      join information_schema.key_column_usage as kcu on tc.constraint_name = kcu.constraint_name
      join information_schema.constraint_column_usage as ccu on ccu.constraint_name = tc.constraint_name
     where tc.constraint_type = 'FOREIGN KEY'
       and ccu.constraint_schema = 'public'
       and tc.table_name ~ '.*'
     union all
    -- Подвал
    select '@enduml'


    Если внимательно присмотреться, то под капотом многие инструменты-визуализаторы точно также используют похожие запросы. Правда, запросы эти обычно глубоко "зашиты" в код самого приложения и сложны для понимания, не говоря уже о какой-либо их модификации.


    Метрики и мониторинг

    Перейдем к традиционно сложной теме — мониторинг производительности БД. Вспомню небольшую true story, рассказанную мне "одним моим другом". На очередном проекте жил-был некий могущественный DBA, и мало кто из разработчиков был с ним знаком лично, да и вообще видел когда-нибудь его в глаза (несмотря на то, что трудился он, по слухам, где-то в соседнем корпусе). В час "X", когда poduction-система крупного ретейлера начинала в очередной раз "плохо себя чувствовать", он молча присылал скриншоты графиков из ораклового Enterprise Manager, на которых бережно выделял критичные места красным маркером для "понятности" (это, мягко говоря, мало помогало). И вот по этой "фотокарточке" приходилось лечить. При этом доступа к драгоценному (в обоих смыслах этого слова) Enterprise Manager ни у кого не было, т.к. система сложная и дорогая, вдруг "разрабы чего-нить натыкают и все поломают". Поэтому разработчики "эмпирическим" путем находили место и причину тормозов и выпускали патч. Если грозное письмо от DBA не приходило повторно в ближайшее время, то все с облегчением выдыхали и возвращались к своим текущим задачам (до нового Письма).


    Но процесс мониторинга может выглядеть более весело и дружелюбно, а самое главное — доступно и прозрачно для всех. Хотя бы базовая его часть, как дополнение к основным системам мониторинга (которые безусловно полезны и во многих случаях незаменимы). Любая СУБД свободно и абсолютно безвозмездно готова поделиться информацией о своем текущем состоянии и производительности. В той же самой "кровавой" Oracle DB практически любую информацию о производительности можно получить из системных представлений, начиная от процессов и сессий и заканчивая состоянием буферного кеша (например, DBA Scripts, раздел "Monitoring"). В Postgresql также есть целая россыпь системных представлений для мониторинга работы БД, в частности такие незаменимые в повседневной жизни любого DBA, как pg_stat_activity, pg_stat_database, pg_stat_bgwriter. В MySQL для этого предназначена даже отдельная схема performance_schema. А В Mongo встроенный профайлер агрегирует данные о производительности в системную коллекцию system.profile.


    Т.о., вооружившись каким-либо сборщиком метрик (Telegraf, Metricbeat, Collectd), который умеет выполнять кастомные sql-запросы, хранилищем этих метрик (InfluxDB, Elasticsearch, Timescaledb) и визуализатором (Grafana, Kibana), можно получить достаточно легкую и гибкую систему мониторинга, которая будет тесно интегрирована с другими общесистемными метриками (получаемыми, например, от сервера приложений, от ОС и пр.). Как, например, это сделано в pgwatch2, где используется связка InfluxDB + Grafana и набор запросов к системным представлениям, к которым также можно добавить кастомные запросы.


    Итого


    И это только приблизительный перечень того, что можно сделать с нашей БД посредством обычного SQL-кода. Уверен, можно найти еще множество применений, пишите в комментариях. А о том, как (и самое главное зачем) это все заавтоматизировать и включить в свой CI/CD pipeline мы поговорим в следующий раз.

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 15

      –8
      в допетровской Руси.

      18 дек 11, 21:43

      А вы хотя бы ПТУ закончили?
        +6
        9 лет для ИТ отрасли этот как другая эпоха. В литературе этот прием называется гиперболой.
        С какой целью интересуетесь моим образованием?
          –3

          Ну я вот тоже обратил на это внимание. 11-й год как-то не вяжется с таким названием, это же меньше 10 лет назад. Да и ORM сильно раньше появились. Ладно бы еще начало 2000-х. И вообще, я в 10-м универ закончил, а тут оказывается 11-й это "допетровская Русь")

        +1
        под прицелом «database as code»

        Переведите, пожалуйста, что понимаете под «database as code» — из статьи это не получается определить.
        А от этого и весь смысл статьи как-то утекает.
        Вы хотели показать как можно работать с различными СУБД при помощи запросов SQL?
        При этом упомянули ORM, но главную проблему ORM (попытка объединить два мира: множеств и скаляров), которую по-человечески не решили и вряд ли решат когда-нибудь, не затронули.
        Непонятно что хотели сказать.
          +1
          Ок, ответа наверно не будет.
          Я тогда просто ссылку положу здесь.
          Читали такое?
          Вьетнам компьютерной науки
          Если нет, то очень рекомендую.
            0
            Спасибо, почитаю. У меня еще в wait-листе «Дефрагментация мозга», там вроде тоже что-то похожее.
            На самом деле статья (и сама идея) не про маппинг, а про отношение, отношение к SQL-коду как к Коду.
            0
            Переведите, пожалуйста, что понимаете под «database as code» — из статьи это не получается определить.

            Это мое небольшое хобби. Идея простая — если есть «infrastructure as code», «configuration as code» и пр., то где «database as code»? Причем в случае с БД даже не надо выдумывать никаких yml-ов, ведь уже есть настоящий код — это SQL.
            Если интересно, то у меня есть еще одна статья на Хабре на эту тему, и небольшой доклад.
            Готов ответить на любые вопросы (если они будут).
              0

              Так вы на вопрос не ответили. Что вы хотели сказать-то? Ну вот допустим программист или сисадмин проникся концепцией "database as code", что конкретно должно измениться в его работе, что он должен начать делать по-другому?

                0

                Раз автор не отвечает, прокомментирую тезисы из статьи.


                Модель обвешена умными аннотациями, а где-то за кулисами доблестный ORM генерирует и выполняет тонны какого-то SQL-кода.

                А что вы предлагаете, писать SQL вручную? Нет, спасибо, это сложно в поддержке.


                А что если взять лучшее из двух миров? Как это сделано в замечательном инструменте с жизнеутверждающим названием Yesql.
                а затем прочитайте этот файл, превратив его в обычную Clojure функцию

                В PHP можно строки с SQL задавать, без всяких файлов. Это пишет каждый начинающий PHP-шник, который не умеет пользоваться фреймворками с ORM. Или опытный, который точно знает, что делает.


                Часто нам приходится искать какие-либо объекты в БД, например, найти таблицу в схеме и изучить ее структуру (какие используются колонки, ключи, индексы, констрейнты и прочее). И от любой графической IDE или маломальского DB-manager'а, в первую очередь, мы ждем именно этих способностей.
                В большинстве СУБД нужного результата можно добиться вот таким нехитрым запросом из information_schema

                Так IDE именно такой код и выполняют. И ORM тоже.
                От IDE и DB-менеджеров мы ждем наглядного представления и удобного редактирования, а не просто текстовые названия чего-то. И да, их используют чтобы такой запрос не писать самому.
                Про information_schema знают практически все, в том числе и начинающие, потому что она как раз выводится в любом DB-менеджере.


                В итоге такой способ навигации и поиска объектов гораздо более гибок, экономит много времени, позволяет получить именно ту информацию и в том виде, в котором сейчас необходимо

                Такой способ занимает много времени, потому что это надо руками писать, даже со сниппетами. Ну и как бы нет, не в том виде, в котором необходимо. Структура БД обычно нужна в наглядном графическом виде, со связями и прочим, с выпадающими списками для редактирования, а не в текстовом результате запроса.
                Ну и да, то, что к базе можно сделать запрос, знают практически все.


                В то же время правильно и вовремя написанный оператор create table позволит без труда воспользоваться всеми из них

                Ввод вручную в правильном синтаксисе это тоже труд. Именно его и избегают использованием графических клиентов. Все знают, что в базе есть CREATE TABLE, просто не все хотят писать его вручную.


                Причем мы можем выполнять операции не только над объектами БД, но и над самой СУБД, например "убить" процесс, освободить какую-либо область памяти, включить трассировку

                Я что-то сомневаюсь, что SHOW PROCESSLIST или аналог это команда, неизвестная для большинства разработчиков. А из знания о ней можно предположить и существование других функций.


                Ну а информацию об объектах и связях между ними мы уже знаем как получить.
                Приведем небольшой пример того, как это могло бы выглядеть, с использованием PlantUML и демонстрационной база данных для PostgreSQL (слева SQL-запрос, который сгенерирует нужную инструкцию для PlantUML, а справа результат)

                Да, только это дольше.
                Вместо того, чтобы воспользоваться IDE, надо изучить синтаксис PlantUML, составить запрос, и воспользоваться PlantUML. Какая в этом цель?


                А если немного постараться, то на основе ER-шаблона для PlantUML можно получить что-то сильно похожее на настоящую ER-диаграмму

                А если использовать DB-manager, то даже и стараться не надо. MySQL Workbench это точно умеет, MS SQL тоже, для остальных СУБД наверно тоже кто-то аналогичные инструменты сделал.


                Т.о., вооружившись каким-либо сборщиком метрик (Telegraf, Metricbeat, Collectd), который умеет выполнять кастомные sql-запросы, хранилищем этих метрик (InfluxDB, Elasticsearch, Timescaledb) и визуализатором (Grafana, Kibana), можно получить достаточно легкую и гибкую систему мониторинга. Как, например, это сделано в pgwatch2

                Что все и делают. Либо сами, либо используют инструменты типа pgwatch2 или Enterprise Manager. А если сами не делают, значит не понимают, что эта информация означает, и визуализатор это не исправит.


                Итого, все что описано в статье, давно всем известно и везде применяется.

                  +1
                  Итого, все что описано в статье, давно всем известно

                  Данная статья задумывалась как обновленная и более развернутая расшифровка одной из частей моего доклада с одноименным названием. Если здесь все понятно, то это хорошо.

                  и везде применяется

                  Идея не только в том, что везде используется SQL-код, а что относиться к нему стоит как к настоящему коду.
                  А что мы делаем с кодом — храним его в git'е, настраиваем всякие пайплайны чтобы им управлять, делаем ревью и вот это все. При чем это относится и к коду приложений и инфраструктурному коду.
                  В database as code тоже идея состоит в том, что весь SQL-код проходит через репозиторий (не только миграции). Написали запрос, закомитили, сделали merge request и он пошел по пайплайну.
                  А именно:
                  — запустились какие-то линтеры
                  — сделали ревью
                  — автотесты (почему бы и нет — github.com/dimitri/regresql)
                  — обновились доки и диаграммы в них
                  — в IDE/Manager добавилась новая закладка, где смотреть его результаты в более удобном виде, делать выгрузки всякие
                  — при необходимости начали собираться метрики на базе результатов запроса
                  — появился новый дашборд в Графане/Кибане на основании этих метрик
                  — и пр.
                    0
                    А что мы делаем с кодом — храним его в git'е, настраиваем всякие пайплайны чтобы им управлять, делаем ревью
                    В database as code тоже идея состоит в том, что весь SQL-код проходит через репозиторий

                    Так собственно об этом и был изначальный вопрос — какой конкретно код вы предлагаете программисту хранить в git-е? Тот, который ORM генерирует? Это никому не нужно. Его именно потому и сделали генерируемым, а не захардкоженным, потому что это удобно.
                    Это не говоря уже про динамический SQL, где число джойнов и условий в WHERE зависит от фильтров, заданных в интерфейсе. Предлагаете в репозитории хранить все возможные варианты?


                    запустились какие-то линтеры

                    Линтеры нужны, чтобы найти ошибку до выполнения кода, причем в основном ошибку в типах. В SQL довольно сложно сделать ошибку в типах, а уж коммитить SQL, который ни разу не запускался, это какая-то крайне исключительная ситуация.


                    сделали ревью

                    Ревью не надо делать, если генератор запросов проверен и протестирован. Ревью логики построения запроса лучше проверять в виде кода на языке программирования. Кстати про ревью, сырой SQL сложен в поддержке, вы почему-то это упорно игнорируете.


                    автотесты

                    Автотесты прекрасно работают с кодом, который этот SQL генерирует, для этого не надо хранить его в репозитории.


                    при необходимости начали собираться метрики на базе результатов запроса

                    Метрики нужны во время выполнения запроса, а не во время появления его в репозитории. И обычно нужны не все подряд, а вполне определенные, что задается кодом.


                    в IDE/Manager добавилась новая закладка, где смотреть его результаты в более удобном виде, делать выгрузки всякие

                    Да, получится несколько сотен закладок, и быстрее руками скопировать в редактор, чем искать среди них. Тем более что 99% запросов используют параметры — конкретные id или строки, и все равно без редактирования его не запустить.


                    обновились доки и диаграммы в них

                    Я не очень понимаю, что вы имеете в виду, и почему нельзя сделать то же самое для любого другого кода.

            –2
            9 лет для IT срок конечно не малый, но, в обсуждаемой области ORM каких то прорывных тенденций я не вижу.
            В литературе этот прием называется гиперболой.
            Гипербола это преувеличение, а не использование несоответствующих фактов. У допетровской эпохи есть конкретные даты, и именно поэтому у Вас поинтересовались достаточностью образования.
            С литературной же точки зрения можно было использовать формулировки «предыдущей эпохи», «доГейсовой эпохи» (или иное значимое в обсуждаемой категории имя).
            После фразы «допетровской» по ссылке ожидался некий исторический каламбур восходящий к 17 веку. Но увы.
              +1
              9 лет для IT срок конечно не малый, но, в обсуждаемой области ORM каких то прорывных тенденций я не вижу.

              Да, и это одна из главных идей поста (по крайней мере я так планировал).

              Гипербола это преувеличение, а не использование несоответствующих фактов.

              Т.е. фразу «мы не виделись сто лет» могут употреблять только долгожители? Я уже молчу про «со скоростью пули», «задушить в объятьях» и пр.

              После фразы «допетровской» по ссылке ожидался некий исторический каламбур восходящий к 17 веку. Но увы.

              Приношу «тысячу извинений», что не оправдал Ваших надежд.

              Вообще не думал, что такой безобидный речевой оборот вызовет столько полемики.
                –3
                Вообще не думал, что такой безобидный речевой оборот вызовет столько полемики.

                Ну возможно статья такая, что больше нечего обсуждать)

              0

              Вообще хорошо что появляются такие статьи. Через год об авторах уже никто не вспомнит, а зерно сомнения уже посажено и ростки здравого смысла уже всходят.
              Ой, опять литературный прием, да еще и заезженный…

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое