Да, уважаемый коллега, я хочу связать свой класс с экосистемой Spring по максимуму, чтобы использовать все возможности этой экосистемы на полную катушку. И на самом деле код класса будет различаться для разных фреймворков. Коней на переправе не меняют, не так ли? Вы можете представить себе смену фреймворка для живого проекта? Это означает фактически переписать всё с нуля, если это не простейший микросервис. А если вы имеете в виду просто выдрать класс из проекта и использовать его в vanilla Java проекте — ну так добавить конструктор впоследствии ничего не стоит, IDE вам сгенерит его автоматически.
Да, новые члены команды (новички в Spring) в любом случае будут ошеломлены и вынуждены будут очень много учиться, прежде чем поймут как работает магия заклинаний-аннотаций и что там находится под капотом. DI это самое простое что есть в Spring, с этим как раз всё будет просто.
С юнит тестами вообще нет проблем. Используйте аннотации Mockito, которые выполняют внедрение зависмостей точно так же как это делает спринговый контекст. Без всяких усилий с вашей стороны. Декларативный стиль, вообще без кода.
Вы спрашиваете: "Что, если потребуется инициализация через BeanFactory? Как передавать зависимости?"
Очевидно, фабрика для синглтонов не требуется, или же этот одноразовый код будет размещен в @Configuration. Речь идёт о прототипах, которые выполняют какую-то короткоживущую бизнес-логику, скорее всего однопоточную. И им, как правило, для выполнения этой работы не требуется много зависимостей или сложная настройка. Прототипы просты. Поэтому мне всегда хватало стандартной саринговой фабрики (ObjectFactory, Provider, Supplier и т.п.). Например так:
В случае абстрактной фабрики опять же мы имеем несколько автоматически сгенерированных конкретных фабрик плюс минимум кода донастройки, если он вообще нужен.
А вот скажите лучше, как вы собираетесь с помощью конструктора передавать в синглтон @PersistenceContext? Это required dependency, но объект будет каждый раз новый для нового потока, запускающего метод, т.к. он представляет собой обёртку вокруг коннекта к БД. Его нельзя передать извне, только создавать внутри ручками через фабрику. Мне больше нравится декларативный стиль. Аннотации вполне достаточно.
Или вот ещё пример. Очень часто нужно вызывать транзакционные методы из других методов того же класса. Без self-injection тут не обойтись. Это тоже required dependency, то есть по логике нужно её передавать в конструкторе. Но на этапе конструктора у вас ещё не создан proxy с логикой обработки транзакций.
В свое время я посмотрел много выступлений дяди Боба, раз уж вы его вспомнили. И первый из его принципов всегда был такой: "Smaller code is much easier to understand". Он рекомендует следующее:
Long methods mix high-level abstractions with unimportant details. They overwhelm you.
How do you fix it? The first rule: functions should be small.
How small should they be? 1? 5? 10 lines? Fit into the screen? There is a better rule: a function should do one thing.
What is “one thing”? A function does one thing if you cannot meaningfully extract another function from it.
You should extract methods until you can’t extract anymore.
Кроме того, есть принцип KISS. Keep it simple.
Насчёт того, что код увеличивается при добавлении слоев абстракции, я совершенно не согласен. Да, он немного увеличивается и даже усложняется на старте проекта, когда классов ещё не так много, но в перспективе это приводит к снижению общего количества кода и связанности между компонентами, и как результат к чистоте дизайна и кода. Если система живая и ей активно пользуются, то команда разработчиков постоянно получает новые требования. Если не использовать все эти, сложные на первый взгляд, абстракции и паттерны дизайна, то код неимоверно распухает и стоимость сопровождения проекта растёт по экспоненте.
Нет, не превращается. Вы ведь используете Mockito чтоб обеспечить изоляцию тестов? Вам в любом случае нужно как-то создать все эти mock-объекты и внедрить в ваш класс. И Mockito вам в этом помогает. Он делает DI точно так же как и Spring при поднятии контекста: по типам (интерфейсам) проаннотированных полей, а не по именам.
Первое правило чистого кода — чем его меньше, тем лучше. Я не хочу городить конструктор и вызывать его вручную в тестах. Я предпочитаю стройный ряд объявлений зависимостей в самом начале моего класса. Это очень наглядно, потому что все такие поля имеют одинаковую аннотацию. Их не спутать с обычными полями.
Кстати, к вашему сведению, Spring использует reflection для всех трёх типов DI (конструктор, сеттер, поле). Можете убедиться сами в режиме отладки, если не верите :) Или просто найдите BeanPostProcessor ответственный за обработку аннотации Autowired и почитайте код. Других способов узнать что именно вы хотели просто не существует. Так что это скорее дело вкуса и style guide принятого на проекте или в организации. И многие известные тренеры и спикеры, типа того же Жени Борисова, разделяют эту точку зрения. Часть Spring Boot, отвечающая за поддержку тестирования, также полностью исключает любые проблемы типа необходимости внедрять моки вручную, по стрковым именам полей.
Спасибо вам за совет, но мне на самом деле не пришлось предолевать никакие проблемы. Я просто привёл здесь сильно упрощенный пример взаимной зависимости компонентов, чтобы показать, что в жизни такое случатся сплошь и рядом, и это совершенно не означает, что дизайн приложения дерьмовый. Это просто требования бизнеса. Функциональную зависимость компонентов совсем не обязательно решать при помощи композиции.
Раз уж вы дали совет, опираясь на домыслы, то я раскрою немного деталей реализации. RBAC Service и MQRPC Service (назовём их так) не связаны напрямую. Они вообще друг о друге ничего не знают. Все ядровые сервисы вроде них имплементированы в shared library и внедряются во все микрсервисы и настраиваются при помощи «магии» автоконфигураций Spring Boot. Проверка ролей выполняется при помощи Java аннотаций, в том числе и в MQRPC Svc, потому что та же библиотека имплементирует кастомный SecurityExpressionRoot и т.д. А упомянутое мной broadcas оповещение отсылается как реакция на внутреннее событие, тоже annotation-driven (EventListener + ApplicationEventPublisher), а не путём прямого вызова метода. Как видите, никаких зависимостей в коде нет вообще. За это я и люблю Spring Boot. Я делаю изменение в общей библиотеке, привношу что-то совершенно новое или меняю существующее, мерджу коммит, и CI pipeline мне пересобирает докер образы и деплоит все сервисы с уже новой функциональностью, хотя я их даже не касался.
Странный пример у вас. Проблема с циклической зависимостью "решилась" убиранием поля Parent из Child. При этом ничего больше не поменялось в коде. Получается, это поле там было и не нужно изначально?
В жизни всё намного сложнее, к сожалению. Пример: сервису обработки RPC через MQ, обеспечивающий вызов удаленных процедур между микросервисами, требуется зависимость — сервис проверки ролей (RBAC) чтобы убедиться что пользователь, от имени которого прилетела команда RPC, имеет право запускать эту логику. При этом сервис RBAC также зависит от сервиса MQ, поэтому что если роли пользователя изменились, то нужно отправить broadcast сообщение всем репликам чтобы они сбросили локальные кеши ролей для этого юзера. Конечно, зависимость не означает что оба класса имеют поля-ссылки друг на друга, но зависимость есть и эта проблема должна как-то решаться. И решения есть.
И да, я использую Autowired через приватные поля для всех проектов, в том числе новых на спринг буте 2.3, и считаю это самым удобным и элегантным способом, который обеспечивает чистоту кода. Для тестов с поднятием контекста и простых Mockito используются @MockBean, либо для юнит Setter с пакетным уровнем доступа, а в редких экзотических случаях стандартный спринговый ReflectionTestUtils, чтобы не городить огород как сделано в вашем примере.
Спасибо за пост, Олег, это чрезвычайно интересная тема! А нельзя ли послушать запись интервью в оригинале? Было бы круто, чтоб не тратить драгоценное время на чтение. Текст довольно большой. Ну если нет, тогда придётся всё-таки читать.
В Java точно так же. Строки неизменяемые, и литералы хранятся в специальной области хипа (heap), называемой String Pool.
В момент присвоения строкового литерала компилятор ищет такую же строку в String Pool и возвращает ссылку на существующий объект либо создаёт новый. Это называется "интернирование" (interning).
Следует, однако, различать литералы и строки, созданные с использованием оператора new. Во втором случае всегда создаётся новый объект в куче (не в String Pool). К тому же у строк есть метод intern(), но детали здесь не буду приводить.
This is awesome, thank you so much!
Кажется, я уже видел эти примеры в презентации Олега на HL, и ещё тогда подумал что было бы круто поделиться с коллегами на работе, не говорящими по русски. Теперь можно будет просто давать ссылку.
Cheers!
TL;DR
«Я нажал какую-то кнопку на клавиатуре, но правда точно не помню какую, а потом я подождал 2 недели, но почему-то ничего не заработало. А кто-то знает что такое закрытый ключ? Может, стоит попробовать почитать что-то по теме? Через годик попробую, если так и не заработает».
В компании, на которую я работаю, десятки тысяч сотрудников по всему миру обмениваются зашифрованными имейлами. Не прикладывая ни малейших усилий! Потому что в корпоративном Outlook (Office365) всё работает из коробки. Надо лишь импортировать ключи. Но это делается полу-автоматически при первичной настройке ОС. И диски зашифрованы тоже. И телефон. Воруйте на здоровье, всё равно придётся форматировать.
На Linux немногим чуть сложнее с настройками почтовых клиентов, но и у меня и у всех моих коллег работает без проблем и Thunderbird и Evolution. Можно просто подписать ЭЦП, а можно и шифровать и читать такие письма.
IDEA является моим основным рабочим инструментом уже больше 15 лет. У меня постоянно открыто несколько проектов. И если мне нужен временный файл, то для этого предусмотрен специальный хоткей Ctrl+Shift+Alt+Insert (New scratch file). Там мне будет доступна вся мощь, за которую я и плачу компании Jet Brains. Это единственное платное приложение на моём лэптопе, всё остальное только open source.
Здесь же речь идёт об облегчённом режиме запуска, в дополнение к полноценной IDE. В этом случае мне не нужен интеллект, поддержка фреймворков и инспекции. Достаточно просто редактора с подсветкой синтаксиса и всякими удобствами типа выделения вертикальных блоков, записи макросов и т.п. А уж в режиме diff и того меньше.
Так вот вопрос — зачем IDEA в LightEdit режиме подгружает плагины и оставляет кучу debug логов в терминале? Я хочу чтоб терминал оставался чистым, мне просто неприятно видеть этот мусор, это тоже мой основной рабочий инструмент. Мне нужно быстро сравнить 2 файла, но различия настолько значительны, что мне уже мало стандартного консольного diff и требуется GUI. Какой смысл ждать 10 сек, если тот же meld справляется за 1 сек и результат ничуть не хуже?
Может под Win это будет актуально? Не знаю как сейчас, но раньше там было всё очень плохо в плане маленьких бесплатных инструментов для разработчиков на каждый день. Но LightEdit там был, кстати, назывался Notepad++ :)
LightEdit режим не такой уж и Light. Запускается нисколечки не быстрее чем вся IDE, и при этом попутно гадит в терминал, оставляя после себя кучу логов. Видно что грузит все возможные плагины в процессе старта. Универсальный лёгкий редактор должен стартовать за секунду. Попробовал разные режимы, например diff. То же самое. Если я пишу в консоли "meld file1 file2", то вижу результат мгновенно. А с idea можно сходить заварить чайку пока дождешься ответа.
Heap тоже не будет из-за внутристраничной очистки. Очередной update, обнаружив нехватку места в странице, выкинет старые версии и пометит указатели как dead.
Сравнение некорректное. Все перечисленные вами продукты отнюдь не являются велосипедами. Они были тщательнейшим образом спроектированы, и в их разработку были вложены сотни миллионов долларов. И теперь они задают отраслевые стандарты.
Да, я считаю что нужно использовать по возможности только "правильные" решения, являющиеся продуктом коллективного разума лучших инженеров планеты, признанные корп. стандартом, имеющие поддержку сообщества и интеграцию с другими "правильными" решениями из коробки.
И я предложил не одно такое решение а три, на вскидку.
Потому что вы свой колхоз опубликовали на Хабре, а кто-то его заплюсовал. Вы надеетесь этим привлечь клиентов? Скорее вы добьётесь обратного эффекта. Пожалуйста, добавьте вступление в начале поста:
Мы в компании "Тензор" любим изобретать велосипеды, но иногда они выходят с квадратными колёсами. Вместо того чтобы использовать решения промышленного уровня, признанные во всём мире, мы лепим колхоз из палок и синей изоленты. В данной задаче нам требовалось хранить логи и выполнять поиск по ним. Советуем вам для начала обратить внимание на Elastic Stack (бывший ELK), который развертывается в считанные минуты и агрегирует логи с разных частей приложения. Также недавно дозрел до продакшна Grafana Loki. Есть и более экзотические решения, хорошо зарекомендовавшие себя на рынке. Решать вам, но помните, что скупой платит дважды!
Да, уважаемый коллега, я хочу связать свой класс с экосистемой Spring по максимуму, чтобы использовать все возможности этой экосистемы на полную катушку. И на самом деле код класса будет различаться для разных фреймворков. Коней на переправе не меняют, не так ли? Вы можете представить себе смену фреймворка для живого проекта? Это означает фактически переписать всё с нуля, если это не простейший микросервис. А если вы имеете в виду просто выдрать класс из проекта и использовать его в vanilla Java проекте — ну так добавить конструктор впоследствии ничего не стоит, IDE вам сгенерит его автоматически.
Да, новые члены команды (новички в Spring) в любом случае будут ошеломлены и вынуждены будут очень много учиться, прежде чем поймут как работает магия заклинаний-аннотаций и что там находится под капотом. DI это самое простое что есть в Spring, с этим как раз всё будет просто.
С юнит тестами вообще нет проблем. Используйте аннотации Mockito, которые выполняют внедрение зависмостей точно так же как это делает спринговый контекст. Без всяких усилий с вашей стороны. Декларативный стиль, вообще без кода.
Вы спрашиваете: "Что, если потребуется инициализация через BeanFactory? Как передавать зависимости?"
Очевидно, фабрика для синглтонов не требуется, или же этот одноразовый код будет размещен в @Configuration. Речь идёт о прототипах, которые выполняют какую-то короткоживущую бизнес-логику, скорее всего однопоточную. И им, как правило, для выполнения этой работы не требуется много зависимостей или сложная настройка. Прототипы просты. Поэтому мне всегда хватало стандартной саринговой фабрики (ObjectFactory, Provider, Supplier и т.п.). Например так:
В случае абстрактной фабрики опять же мы имеем несколько автоматически сгенерированных конкретных фабрик плюс минимум кода донастройки, если он вообще нужен.
А вот скажите лучше, как вы собираетесь с помощью конструктора передавать в синглтон @PersistenceContext? Это required dependency, но объект будет каждый раз новый для нового потока, запускающего метод, т.к. он представляет собой обёртку вокруг коннекта к БД. Его нельзя передать извне, только создавать внутри ручками через фабрику. Мне больше нравится декларативный стиль. Аннотации вполне достаточно.
Или вот ещё пример. Очень часто нужно вызывать транзакционные методы из других методов того же класса. Без self-injection тут не обойтись. Это тоже required dependency, то есть по логике нужно её передавать в конструкторе. Но на этапе конструктора у вас ещё не создан proxy с логикой обработки транзакций.
В свое время я посмотрел много выступлений дяди Боба, раз уж вы его вспомнили. И первый из его принципов всегда был такой: "Smaller code is much easier to understand". Он рекомендует следующее:
Long methods mix high-level abstractions with unimportant details. They overwhelm you.
How do you fix it? The first rule: functions should be small.
How small should they be? 1? 5? 10 lines? Fit into the screen? There is a better rule: a function should do one thing.
What is “one thing”? A function does one thing if you cannot meaningfully extract another function from it.
You should extract methods until you can’t extract anymore.
Кроме того, есть принцип KISS. Keep it simple.
Насчёт того, что код увеличивается при добавлении слоев абстракции, я совершенно не согласен. Да, он немного увеличивается и даже усложняется на старте проекта, когда классов ещё не так много, но в перспективе это приводит к снижению общего количества кода и связанности между компонентами, и как результат к чистоте дизайна и кода. Если система живая и ей активно пользуются, то команда разработчиков постоянно получает новые требования. Если не использовать все эти, сложные на первый взгляд, абстракции и паттерны дизайна, то код неимоверно распухает и стоимость сопровождения проекта растёт по экспоненте.
Нет, не превращается. Вы ведь используете Mockito чтоб обеспечить изоляцию тестов? Вам в любом случае нужно как-то создать все эти mock-объекты и внедрить в ваш класс. И Mockito вам в этом помогает. Он делает DI точно так же как и Spring при поднятии контекста: по типам (интерфейсам) проаннотированных полей, а не по именам.
Первое правило чистого кода — чем его меньше, тем лучше. Я не хочу городить конструктор и вызывать его вручную в тестах. Я предпочитаю стройный ряд объявлений зависимостей в самом начале моего класса. Это очень наглядно, потому что все такие поля имеют одинаковую аннотацию. Их не спутать с обычными полями.
Кстати, к вашему сведению, Spring использует reflection для всех трёх типов DI (конструктор, сеттер, поле). Можете убедиться сами в режиме отладки, если не верите :) Или просто найдите BeanPostProcessor ответственный за обработку аннотации Autowired и почитайте код. Других способов узнать что именно вы хотели просто не существует. Так что это скорее дело вкуса и style guide принятого на проекте или в организации. И многие известные тренеры и спикеры, типа того же Жени Борисова, разделяют эту точку зрения. Часть Spring Boot, отвечающая за поддержку тестирования, также полностью исключает любые проблемы типа необходимости внедрять моки вручную, по стрковым именам полей.
Раз уж вы дали совет, опираясь на домыслы, то я раскрою немного деталей реализации. RBAC Service и MQRPC Service (назовём их так) не связаны напрямую. Они вообще друг о друге ничего не знают. Все ядровые сервисы вроде них имплементированы в shared library и внедряются во все микрсервисы и настраиваются при помощи «магии» автоконфигураций Spring Boot. Проверка ролей выполняется при помощи Java аннотаций, в том числе и в MQRPC Svc, потому что та же библиотека имплементирует кастомный SecurityExpressionRoot и т.д. А упомянутое мной broadcas оповещение отсылается как реакция на внутреннее событие, тоже annotation-driven (EventListener + ApplicationEventPublisher), а не путём прямого вызова метода. Как видите, никаких зависимостей в коде нет вообще. За это я и люблю Spring Boot. Я делаю изменение в общей библиотеке, привношу что-то совершенно новое или меняю существующее, мерджу коммит, и CI pipeline мне пересобирает докер образы и деплоит все сервисы с уже новой функциональностью, хотя я их даже не касался.
Странный пример у вас. Проблема с циклической зависимостью "решилась" убиранием поля Parent из Child. При этом ничего больше не поменялось в коде. Получается, это поле там было и не нужно изначально?
В жизни всё намного сложнее, к сожалению. Пример: сервису обработки RPC через MQ, обеспечивающий вызов удаленных процедур между микросервисами, требуется зависимость — сервис проверки ролей (RBAC) чтобы убедиться что пользователь, от имени которого прилетела команда RPC, имеет право запускать эту логику. При этом сервис RBAC также зависит от сервиса MQ, поэтому что если роли пользователя изменились, то нужно отправить broadcast сообщение всем репликам чтобы они сбросили локальные кеши ролей для этого юзера. Конечно, зависимость не означает что оба класса имеют поля-ссылки друг на друга, но зависимость есть и эта проблема должна как-то решаться. И решения есть.
И да, я использую Autowired через приватные поля для всех проектов, в том числе новых на спринг буте 2.3, и считаю это самым удобным и элегантным способом, который обеспечивает чистоту кода. Для тестов с поднятием контекста и простых Mockito используются @MockBean, либо для юнит Setter с пакетным уровнем доступа, а в редких экзотических случаях стандартный спринговый ReflectionTestUtils, чтобы не городить огород как сделано в вашем примере.
А вы не хотите выложить проект в open source? Мы поможем перевести. Это ведь инструмент для PostgreSQL, ему положено быть открытым :)
В Java точно так же. Строки неизменяемые, и литералы хранятся в специальной области хипа (heap), называемой String Pool.
В момент присвоения строкового литерала компилятор ищет такую же строку в String Pool и возвращает ссылку на существующий объект либо создаёт новый. Это называется "интернирование" (interning).
Следует, однако, различать литералы и строки, созданные с использованием оператора new. Во втором случае всегда создаётся новый объект в куче (не в String Pool). К тому же у строк есть метод intern(), но детали здесь не буду приводить.
Отлично, большое спасибо!
Женя Борисов выдающийся спикер и шоумен. Такое событие нельзя пропустить :)
Круто! А запись потом выложите, для тех кто не сможет принять участие онлайн?
This is awesome, thank you so much!
Кажется, я уже видел эти примеры в презентации Олега на HL, и ещё тогда подумал что было бы круто поделиться с коллегами на работе, не говорящими по русски. Теперь можно будет просто давать ссылку.
Cheers!
«Я нажал какую-то кнопку на клавиатуре, но правда точно не помню какую, а потом я подождал 2 недели, но почему-то ничего не заработало. А кто-то знает что такое закрытый ключ? Может, стоит попробовать почитать что-то по теме? Через годик попробую, если так и не заработает».
В компании, на которую я работаю, десятки тысяч сотрудников по всему миру обмениваются зашифрованными имейлами. Не прикладывая ни малейших усилий! Потому что в корпоративном Outlook (Office365) всё работает из коробки. Надо лишь импортировать ключи. Но это делается полу-автоматически при первичной настройке ОС. И диски зашифрованы тоже. И телефон. Воруйте на здоровье, всё равно придётся форматировать.
На Linux немногим чуть сложнее с настройками почтовых клиентов, но и у меня и у всех моих коллег работает без проблем и Thunderbird и Evolution. Можно просто подписать ЭЦП, а можно и шифровать и читать такие письма.
IDEA является моим основным рабочим инструментом уже больше 15 лет. У меня постоянно открыто несколько проектов. И если мне нужен временный файл, то для этого предусмотрен специальный хоткей Ctrl+Shift+Alt+Insert (New scratch file). Там мне будет доступна вся мощь, за которую я и плачу компании Jet Brains. Это единственное платное приложение на моём лэптопе, всё остальное только open source.
Здесь же речь идёт об облегчённом режиме запуска, в дополнение к полноценной IDE. В этом случае мне не нужен интеллект, поддержка фреймворков и инспекции. Достаточно просто редактора с подсветкой синтаксиса и всякими удобствами типа выделения вертикальных блоков, записи макросов и т.п. А уж в режиме diff и того меньше.
Так вот вопрос — зачем IDEA в LightEdit режиме подгружает плагины и оставляет кучу debug логов в терминале? Я хочу чтоб терминал оставался чистым, мне просто неприятно видеть этот мусор, это тоже мой основной рабочий инструмент. Мне нужно быстро сравнить 2 файла, но различия настолько значительны, что мне уже мало стандартного консольного diff и требуется GUI. Какой смысл ждать 10 сек, если тот же meld справляется за 1 сек и результат ничуть не хуже?
Может под Win это будет актуально? Не знаю как сейчас, но раньше там было всё очень плохо в плане маленьких бесплатных инструментов для разработчиков на каждый день. Но LightEdit там был, кстати, назывался Notepad++ :)
LightEdit режим не такой уж и Light. Запускается нисколечки не быстрее чем вся IDE, и при этом попутно гадит в терминал, оставляя после себя кучу логов. Видно что грузит все возможные плагины в процессе старта. Универсальный лёгкий редактор должен стартовать за секунду. Попробовал разные режимы, например diff. То же самое. Если я пишу в консоли "meld file1 file2", то вижу результат мгновенно. А с idea можно сходить заварить чайку пока дождешься ответа.
А ещё можно изменять умолчания для конкретных больших таблиц:
ALTER TABLE t SET (autovacuum_vacuum_scale_factor = 0.01)
Heap тоже не будет из-за внутристраничной очистки. Очередной update, обнаружив нехватку места в странице, выкинет старые версии и пометит указатели как dead.
Статья ни о чём. "Обновляйте по 3 колонки сразу, а не 3 раза по одной" © Капитан Очевидность.
А как насчёт HOT-updates и внутристраничной очистки? В этом случае ничего не распухает, сколько не обновляй.
Тема пессимистических блокировок также не раскрыта, даже не упомянули рабочую лошадку SELECT FOR UPDATE SKIP LOCKED/NOWAIT.
Сравнение некорректное. Все перечисленные вами продукты отнюдь не являются велосипедами. Они были тщательнейшим образом спроектированы, и в их разработку были вложены сотни миллионов долларов. И теперь они задают отраслевые стандарты.
Да, я считаю что нужно использовать по возможности только "правильные" решения, являющиеся продуктом коллективного разума лучших инженеров планеты, признанные корп. стандартом, имеющие поддержку сообщества и интеграцию с другими "правильными" решениями из коробки.
И я предложил не одно такое решение а три, на вскидку.
Потому что вы свой колхоз опубликовали на Хабре, а кто-то его заплюсовал. Вы надеетесь этим привлечь клиентов? Скорее вы добьётесь обратного эффекта. Пожалуйста, добавьте вступление в начале поста:
Мы в компании "Тензор" любим изобретать велосипеды, но иногда они выходят с квадратными колёсами. Вместо того чтобы использовать решения промышленного уровня, признанные во всём мире, мы лепим колхоз из палок и синей изоленты. В данной задаче нам требовалось хранить логи и выполнять поиск по ним. Советуем вам для начала обратить внимание на Elastic Stack (бывший ELK), который развертывается в считанные минуты и агрегирует логи с разных частей приложения. Также недавно дозрел до продакшна Grafana Loki. Есть и более экзотические решения, хорошо зарекомендовавшие себя на рынке. Решать вам, но помните, что скупой платит дважды!