Pull to refresh
12
0
Михаил@mkant

Ведущий программист TypeScript, Node, Vue

Send message

Автор вроде описал другое разнообразие - база одна, а платформы разные. Т.е. чтобы обеспечить "общий подход к работе" - ему и приходится отказываться от ORM, (который каждый со своим DSLем) - в пользу стандартизированного SQL который на всех платформах одинаков.

"раскладывание результата по полям объектов" - всё так и есть, кроме JavaScript/TypeScript - там драйвера сразу отдают массив нативных объектов

Это на спринге он бесплатен (можно и не отказываться, я с этим не спорил). А на ноде нет - на ней, особенно в RESTах отказываться есть смысл - всё что вы описали делается либо без DI вообще, либо точечным DI с нулевыми издержками на добавление из-за отличия архитектуры языка.

DB as API не давали админы делать (корпоративные системы). Запись/апдейты через хранимки видел, но как ни странно, на джаве да еще и с гибернейтом. То что мы делали в рамках того проекта, разбивая на микро-REST-сервисы получалось сильно проще и не требовало хранимок (т.е. получался бы просто UPDATE с pl/sql обвесом). Один раз пробовали через вью и триггеры INSTEAD OF (view из нескольких таблиц) - считай то же что хранимки. Показалось чересчур неявно т.к. отдельного DBA не было, то все быстро забывали что от куда берётся. Кажется, что тут вступает закон Конвея - "структура ПО соответствует структуре команды". Если есть отдельный базовик, то через хранимки быстрее разрабатывать. Если отдельного нет, то быстрее, когда SQL в коде. Так-то получается-то что SQL все равно писать, вопрос только кто это будет делать и где.

Спасибо за комментарий!
Да, ORM действительно удобен для быстрых прототипов и типовых CRUD-операций. Но «быстрый прототип на SQL» может быть ещё проще: SELECT * и вставка через query builder, без абстракций. А когда появляется время — DDL легко превращается в типизированные интерфейсы (в том числе с помощью ИИ), и получается почти то же, что вы показали, но без ORM-слоя.

ORM появился не в экосистеме Node.js и не под REST-архитектуру — его сильные стороны проявляются в других контекстах. В лёгких сервисах часто проще и надёжнее писать SQL напрямую.

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

Я критикую моду на DI как решение проблем, а не сам DI - он бывает уместен, но таких случаев в реальной жизни очень мало, гораздо меньше, чем распространение DI, особенно в RESTах, поэтому, я считаю, его использование надо обосновывать каждый раз - он решает очень узкий круг задач.

"...Когда у вас класс инстанцируется там где применяется - чтобы поменять его аргументы - нужно найти все вхождения..." Собственно, вот вы и обозначили сферу применения DI - если ваш класс имеет разные аргументы при инстанцировании, то, вероятно это кандидат в DI. Но большинство репозиториев, контроллеров и т.п.(в типичных RESTах) никаких аргументов не имеют. А в js/ts экспорт инстанса из файла решает проблему со стереотипными аргументами - не надо искать никакие вхождения, меняется всё в одном месте.

"синглтон - антирпетрн" - так почти все провайдеры в спринге по умолчанию синглтоны (@Component, @Service, @Repository...) Или вы имеете в виду, что их синглтонит не конструктор класса а контейнер? Так в данном случае никакой разницы нет - сам Spring использует этот термин (@Scope("singleton") ). А для js/ts это вообще не актуальный вопрос - это решается формой экспорта что по механике как раз и есть контейнерный синглтон как в спринге - в JS для этого не нужны ни отдельные контейнеры ни библиотеки - это встроено в архитектуру языка (а точнее в движок ноды).

UserStory { let dataProvider = DataProvider } - не надо ничего присваивать, надо сразу статические методы вызывать DataProvider.doSomething()

PS. Кажется, будто я оспариваю ваши утверждения - но это не так. Я полностью согласен с утверждением "если вы его делаете (адекватно) - то это хорошо, а если нет, могут быть проблемы". Я просто добавляю две ремарки - 1) адекватных случаев мало, особенно в RESTах, а в JS - микроскопически мало. 2) Бессмысленное использование DI хоть и не ломает код, но делает его неявным, что может ухудшить поддерживаемость. Ну и соглашусь с ремаркой, что в спринге это стереотипный подход

Ааа, если вы про Джаву/Котлин, ну ОК - может быть. Там весь спринг построен на инверсии управления и инъекции зависимостей и вся документация крутится вокруг этого. Поэтому, чтобы избегать DI надо применять доп.усилия (вроде не понятно зачем). Т.е. тотальное DI является таким распространённым, что его отсутствие может вызвать доп. вопросы. Код, правда, из-за этого очень неявный - не очевидно что от куда прилетает, что injectable что нет, где надо autowire где нет.
Про js/ts. Типизация в Java номинальная, т.е. требуется непременно потомок определенного класса, а в тайпскрипте как в Go она структурная - поля совпадают по типу - то и норм. Java-файл - это класс, т.е. его надо потом инстанцировать. А в js из файла можно экспортировать что угодно (в т.ч. и сразу инстанс, а не класс) и сколько угодно (инстансов и классов в перемешку), а импорты - всегда синглтоны. Это значит что если вы экспортнёте не класс, а инстанс - то вот вам автоматический синглтон. Поэтому в JS, мне кажется, DI проще делать вручную - это элементарно, соответственно библиотеки DI - не очень понятно что - типа сахара для массового внедрения.
Ну и джава это не только RESTы, в ней действительно больше применений, когда DI уместен.
Т.е. моё мнение - в Spring (Java/Kotlin) так же можно прекрасно сократить количество DI в несколько раз без потери функциональности (это не супер-сила), но из-за тотального доминирования в документации DI-подхода проще согласиться, чем сопротивляться т.к. единственный выигрыш - чуть проще и прямолинейнее код (т.е. тоже не охренеть какой профит).
В JS/TS наоборот - лучше применять только по месту там где он действительно нужен и тем более не таскать библиотеки из-за него

Видимо от оргструктуры зависит. Наша команда делала и клиента и сервер. Могли себе позволить. Сначала добавили новые ендпоинты с разделённым API, типа api/v2/... Потом клиент постепенно перешёл на новые запросы (пришлось частично переделывать логику), потом погасили старые. Потом откорректировали БД, т.к. с новым API структуру стало возможным упростить.

Наверное так, но это кажется пугающим.
Как-то пришлось портировать проект с Java + Hibernate на Node. Сначала думали просто портировать, а потом полезли всякие странности в БД - какие-то гигантские ID при небольшом количестве записей, непонятные таблицы связей, ссылки на разные, но одинаково заполненные записи, большущие VIEW для каких-то промежуточных срезов. Ну вот это оказалось последствиями таких вот ORM-вставок и попыток распутать это пост-фактум. Мы тогда разделили API - связанные записи писали отдельно. На GUI стало возможным это отображать - "записываем это...", "обновляем то...", "готово!"
Правда связей было сильно меньше, чем 20. И вышло, конечно, многословнее. Но работать стало сильно быстрее, а из-за GUI казалось, что мгновенно.
Но самое главное - гораздо легче дебажить и апгрейдить (собственно, это была основная причина портирования)
НО! Не Java и не ORM были там главной причиной тормозов - и на Гибернейте можно было сделать удовлетворительно. Ну а на Ноде вообще отпала необходимость использовать ORM.
Желаю, конечно, чтобы у вас был другой случай, и чтобы всё было уместно.

Ну вроде да - местами удобно. Но такие решения часто сфокусированы на определенном виде удобства. CRUD - удобно, миграция к новой версии - вроде удобно, откат к старой - уже не понято. Сделать отчет выкачав данные из нескольких таблиц с предварительным расчетом агрегатов - не понятно. Миграции на knex - считай, чистый SQL с явным описанием как провести откат - вроде тоже удобно и прозрачно. Знание SQL кажется куда как более универсальным навыком, чем умение писать призма-схемы

я написал отдельную статью про ORM (как обещал) - https://habr.com/ru/articles/959518/
Помня о вашем комментарии, добавил примеры вложенных объектов на чистом SQL

Да, я напарывался, когда надо было сделать запрос посложнее, Hibernate скорее тормозит чем помогает. Но в Java хоть какое-то рациональное объяснение есть. А в Node который специально проектировался для HTTP серверов это бессмысленно гораздо чаще

Да, да. И что важно, node-драйвера из-за этого работают несколько медленнее - т.е. они тратят доп. ресурсы, чтобы вернуть нативные объекты вместо итератора. И поверх этого еще одну жирную абстракцию навертеть - это какая-то безудержная расточительность

Я наверное, не до конца понял пример, возможно, не сталкивался с такими коллизиями на Node. Но я не против DI вообще, да и любой технологии - я за то, что надо чётко понимать, что она делает в вашем конкретном проекте. Вы понимаете что делаете, описываете пример - это ОК. А я сталкивался с ситуациями когда люди вообще не могли объяснить зачем они это сделали - ну просто читали где-то, что это "круто", а то что это круто не в любой ситуации, даже не пытались разбираться. Вот мне хотелось сбить это ощущение безусловной крутизны - нет, она условная и годится не везде.

Конечно, фразы типа "все мы знаем, что большинство..." - это действительно преднамеренное нарушение логики. Поэтому про часто/редко я старался сфокусировать - что это мой личный опыт написания REST серверов. Т.е. мне неуместное использование DI попадалось в чужом коде гораздо чаще, чем уместное. Использовалось оно только потому, что его предоставлял фреймворк. Более того, те кто его использовали не могли толком объяснить, что это и зачем.
В этом смысле Ваш выбор совпадает с моим - если бы я стал использовать DI то скорее всего вручную, без фремворков — это более явно и является сигналом того, что выбор сделан осознанно.

Ну вот по описанию сразу возникает подозрение, что это не просто REST. Ну так я и согласен. Там где DI уместен, нужно применять. В голых RESTах такое бывает не часто

В статье я преимущественно имею в виду задачу написания REST API серверов.
Старался писать так, чтобы было понятно, что я не против этих технологий как таковых. А против их неуместного использования, и "выбирать подходы следует именно из анализа вашей предметной области". Мне нечего возразить этому комментарию - всё так и есть.
Моя ремарка состоит в том, что не надо использовать технологию, если она не соответствует предметной области и задачам - бесполезное увеличение сложности - это не бесплатно

Я старался подать платёжные шлюзы как пример уместного DI. Но кажется, что подключение разных реализаций с одинаковым интерфейсом - это редкий случай, не думаю, что ради этого стоит тащить специальную библиотеку обслуживающую инъекции, можно сделать их вручную - код будет только понятнее.
Фреймворк действительно набор паттернов, и некоторые чувствуют себя обязанным применить их все. Я думаю, вы согласитесь, что это излишне.
"народ .. активно суёт вызовы чего угодно напрямую " - я такое видел на Java на Спринге, конечно DI там тоже был - ничто не способно удержать заядлого кулинара от приготовлении лапши.
Мне кажется, что сам DI не делает код ни понятнее, ни лучше, ни более гибким. Это можно сделать с помощью уместного применения DI, я хотел, чтобы статья была об этом.

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

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Registered
Activity

Specialization

Бэкенд разработчик, Архитектор программного обеспечения
Ведущий
TypeScript
Node.js
Управление разработкой
Проектирование архитектуры приложений
SQL
Java
Golang