Обновить

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

Как вы относитесь к переходу от магии декораторов к строгой типизации?

Положительно отношусь. Даже запилил свой DI-контейнер под чистый JS - https://github.com/teqfw/di

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

На этом фоне у традиционных DI-решений есть три пути, и все три — тупиковые

Есть ещё масса нетрадиционных тупиковых путей. Надо только покопать.

Я, кстати, у себя вообще отказался от container.registerXXX , оставил только для тестового режима. Всё остальное "на магии", как вы говорите - через настройку маппинга пространства имён на пути в файловой системе (калька с Java с их classpath и PHP с PSR-4).

Зато такой подход даёт возможность вообще отказаться от статических импортов (они есть только в Composition Root) и добавить в приложения такие экзотические для JavaScript вещи, как interface.

То, что это, кроме меня, нафиг никому не надо в JS - это отдельный вопрос. Но у меня есть :)

Посмотрел teqfw/di - спасибо интересный подход.

Согласен с вами в одном важном моменте: в runtime TypeScript типы действительно так же отсутствуют, как и JSDoc, но для InferDI ключевая ценность не в runtime, а в том, что TypeScript успевает проверить граф до запуска приложения и запретить некорректный граф на этапе компиляции: неправильный порядок зависимостей, отсутствующий ключ, несовпадение constructor signature, singleton → scoped/transient и т.д.

Ваш подход, насколько я понял, делает ставку на runtime-конвенции и модульный resolver. Это сильная идея для pure JS. InferDI же сознательно идёт в другую сторону, только явный Composition Root, потому что именно он становится типом графа.

Есть интересный пример реализации DI системы - @tinkoff/dippy. Слышали о таком?

Нет, раньше не встречал @tinkoff/dippy, спасибо за пример. Посмотрел — интересная реализация.

Но как раз в рамках статьи я говорю о другом ожидании от DI в 2026 году. TypeScript сегодня может намного больше, чем просто типизировать отдельный token или provider. Поэтому современный DI-контейнер, на мой взгляд, должен использовать всю мощь TypeScript, чтобы давать максимум статических гарантий ещё до запуска приложения.

DI не должен добавлять новые источники ошибок: незарегистрированные зависимости, ошибки в списке constructor-зависимостей, случайные singletonscoped/request зависимости. Напротив, он должен помогать сводить такие проблемы к минимуму на этапе компиляции.

В этом смысле Dippy не позволяет компилятору проверить корректность всего DI-графа до запуска приложения.

Все игнорируют слона в комнате

Вы решаете проблему доп проверками, валидациями

В $mol объект существует ровно пока от него кто-то зависит

Т.е. проблема 3 у вас невыразима в компиляции, а у $mol невыразима в принципе. Нельзя написать такой ошибочный код

namespace $ {
  // singleton: на корневом $, один на процесс
  export class $my_metrics extends $mol_object2 {
    @ $mol_mem static total() { return 0 }
  }

  function handle( req: Request ) {
    const $$ = Object.create( $ ) as typeof $
    $$.$my_request_user = () => req.user   // "scoped": живёт только в этом $$

    // ...вся работа по запросу идёт через $$...

    $.$my_metrics.total()                  // singleton читает из КОРНЯ $
    // req-данных на корневом $ нет → протащить их в singleton нечем
  }
}

данные текут только в одну сторону, от долгоживущего корня к короткоживущему наследнику, но не обратно.

Да, в $mol действительно сильный архитектурный приём: проблема решается не дополнительными проверками DI-графа, а самой моделью контекста.

Но у этого подхода есть цена: нужно принять целую парадигму $mol: ambient context, реактивные мемоизированные свойства и жизненный цикл объектов через дерево зависимостей. То есть приложение должно быть написано в этой модели.

Поэтому я бы разделил эти подходы так: $mol делает такую ошибку невыразимой через архитектуру всего приложения, а InferDI через тип DI-графа внутри обычной TypeScript-архитектуры. Это разные уровни решения одной и той же проблемы.

А я старался над комментарием...

А вы видели как di реализован в последнем angular? service = inject(UserService) и это красиво. А вот ваши контейннеры выглядят неочень. К тому же не увидел в InferDi с инжектом интерфейсов?

Да, service = inject(UserService) выглядит лаконично. Однако это именно тот компромисс, о котором я писал в разделе «Магия автосвязывания vs Строгость компилятора». Angular делает разрешение зависимостей неявным и переносит часть рисков в runtime. Если провайдер не зарегистрирован, токен указан неверно или inject() вызван вне контекста инъекции — вы узнаете об этом только в runtime.

В InferDI фокус принципиально другой: приоритетом является не внешняя простота, а проверяемость DI-графа на этапе компиляции TypeScript. Да, зависимости нужно явно описывать в Composition Root и визуально это выглядит менее эффектно, но зато компилятор строго валидирует ключи, типы, порядок аргументов конструктора и lifetime-связи. Это осознанный инженерный выбор в пользу надежности.

API Angular действительно изящнее в месте использования. Но InferDI предлагает другую эстетику — архитектурную надежность и статические гарантий до запуска приложения.

По интерфейсам — в Angular тоже их нельзя использовать как DI token, потому что они исчезают после компиляции. Angular решает это через InjectionToken<T>, а InferDI через аналогичную регистрацию по Symbol('token').

Будущее это Ai разработка. Если спросить у разных Ai построить архитектуру Ai first приложения и какие антипаттерны лучше не использовать то все ai вам скажут что di это зло. До свидания.

Аргумент «все ai вам скажут что di это зло» не является техническим. Если безоговорочно принимать ответы LLM за архитектурные решения, это превращает работу с AI скорее в «религию», чем в инженерный анализ.

На мой взгляд, AI полезен не для перекладывания ответственности за результат, а для поиска вариантов, сравнения компромиссов и проверки решений. Финальное решение всё равно должно опираться на понимание предметной области, требований и рисков.

Тем более в AI-first приложениях зависимости никуда не исчезают. Отказ от DI просто переносит их в глобальные imports, скрытые singleton’ы, service locator, неявное состояние и runtime инициализацию. Архитектура от этого не становится чище.

Поэтому «di это зло» слишком общее утверждение. Плохой DI — зло. Собственно, значительная часть статьи как раз об этом.

Отказ от di в пользу явных импортов. Явное и контракты предпочитает неявному. Куски кода одной логики лежащие рядом лучше чем размазанное по проекту. Я сейчас заканчиваю тестовую оснастку которая будет чекать его утверждения но уже сейчас перепроверка утверждений критиками в нескольких нейронках показывает что многие вещи верны. Да и по производительности кодогенерации видно.

Интересная позиция. Покажите, пожалуйста, пример кода: как в вашей парадигме один класс использует другой без применения DI и сервис-локатора, сохраняя при этом слабую связанность (loose coupling)?

Возможно ли протестировать ваши классы изолированно?

давайте вернемся к этому вопросу чуть попозже. я сейчас на пол пути, и этот путь очень зыбкий и мне постоянно надо корректировать чтото в своем подходе. поэтому я сейчас одно говорю а потом на кодовой базе в 100к увижу что агенты деградировать начали и переобуваться придется. я собственно поэтому оснастку пилю потому что все приходится ставить под сомнение и перепроверять (это чтото типа тестов только тесты архитектурных подходов). По итогу скорее всего на хабре чтото выложу интересное, попиннаете)

"Talk is cheap. Show me the code." (c) Linus Torvalds

Я вам свой код показал. Вот ещё ИИ-код с DI. Покажите свой продукт.

Да и по производительности кодогенерации видно.

Важна не производительность, а управляемость. А то, как говорила одна секретарша из анекдота: "Я могу печатать со скоростью 1000 знаков в минуту. Но такая ерунда получается".

я здесь не для того чтобы кому то чтото доказывать. вы можете не слушать меня и сами сделать замеры, это не сложно. я просто погрузился в это и выводы вот такие. после первых опытов понял что под сомнение надо ставить почти все мои убеждения в правилах разработки которые накопились за 20 лет. Просто берете утверждение нейронки по архитектруре, выставляете требования для сверхсложной и системы (на них он более жесткую архитектуру предлагает) и начинаете гонять по каждому аспекту. Обязательно в связке с еще одной нейронкой критиком. Я например гоняю gpt + deepseek и они там активно друкг друга поправляют. сейчас я свой crm движок переписываю под ai first архитектуру и уже вижу результаты. в старой архитектуре, которая претендует на чистую архитектуру со всеми слоями, правильными паттернами и md кастылями для нейронки где я терпеливо разжевывал ка все работает, ваншотных промтов на фичу было около 70% (причем для новой фичи я отдельно писывал бек и отдельно фронт), сейчас на новой архитектуре, бек+фронт за один промт почти всегда правильно понимает что надо делать. и md файлов там 4 шутки. Кодовая база нового проекта растет, приходится тюнить чтото конечо, но я вижу что стратегически это правильный подход.

Talk is cheap. Show me the code.

похоже, кода мы так и не дождемся)

Будущее это Ai разработка.

Тут согласен.

Если спросить у разных Ai построить архитектуру Ai first приложения и какие антипаттерны лучше не использовать то все ai вам скажут что di это зло.

Это я не проверял, но уверенно предположу, что это ложь. DI десятилетиями (!!) использовалась "кожанными" в разных ЯП, особенно в "кровавом энтерпрайзе". Это проверенный временем и практикой архитектурный шаблон, на котором, кстати, модели сами учились программировать.

Да, и вообще, ваша LLM ответит вам то, что вы хотите услышать. Они те ещё манипуляторы! Мой GPT-чат от DI в JS в восторге. Вот пример кода, полностью написанного ИИ (Codex-агент), и там DI ¯\_(ツ)_/¯

Ужесточите условие что это erp система на 10млн строк и что надо экономить токены и время и увидите совсем другую картину 

Выглядит так что речь идет о вайбкодинге, где архитектура проекта превращается в black box. В этом случаи без четкого разделения на слои, явного распределения ответственности и DI, вы очень быстро получите сверхсвязанный, тяжелый спагетти-монолит.

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

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

DI очень популярен в таких языках, как Java и C#, на которых строят совсем не персональные веб-странички. DI используется в Magento обеих версий. Во второй версии как раз и есть несколько миллионов строк на PHP и это только в ядре, без учёта сторонних расширений.

Если вы считаете, что статическое связывание JS-кода на этапе компиляции через импорты - хорошее решение, то ознакомьтесь с причинами появления архитектурного шаблона Inversion of Control. А если вы считаете, что true IoC - это Ambient Context, а DI - лажа, то вы явный сектант. Авторы книги "Dependency Injection" Steven van Deursen и Mark Seemann прямо называют Ambient Context антипаттерном в IoC и формулируют причины.

Чтобы экономились токены как раз и нужна декомпозиция и позднее связывание. В npm-реестре свыше 4 млн. пакетов, а строк кода - сотни миллиардов. Тем не менее, и человек, и ИИ-агент спокойно могут выдернуть любой пакет из этой кодовой базы и использовать в своём приложении.

"Кожаные" уже всё придумали, "силиконовым" осталось только использовать.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации