Pull to refresh

Comments 34

Вообще di очень удобно делать через декораторы, они конечно пока stage1 и stage0, но скорей всего появятся рано или поздно в стандарте.
Насколько я понял из исходного кода, вы реализовали Service Locator, который инстанцирует сервисы-одиночки на основе захардкоженной в виде строк конфигурации.
Аналогичного результат можно было бы достигнуть в объекте конфига сразу произведя инстанцирование.
Имхо декораторы выглядят перспективнее для решения задачи DI. А в чём проблема компилировать свой node.js код в ES5?
Я согласен, что декораторы выглядят перспективнее, но мне не очень нравится идея компилировать серверный код ради пары фич.

А еще с декораторами нельзя будет сделать несколько инстансов одного класса (разные коннекты к дб, например)
Через декораторы несколько разных инстансов сделать не получится, да.
Но в контексте DI, если вам нужны разные инстансы коннекторов — они описываются в конфиге, а в сервисы вы уже подтягиваете нужный инстанс:
function Inject(field, instanceName) {
  return function(target) {
    target[field] = CONFIG[instanceName];
  }
}

@Inject("pgConnection", "pgConnector")
@Inject("mongoConnection", "mongoConnector")
class MyService {
  var pgConnection;
  var mongoConnection;
}
Идея интересная, но содержит неприятную проблему: в одном файле может быть несколько классов и ни один из них не будет называться по имени файла (файл — это модуль, внутри множество классов).

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

Пока в JS | ES не будет полноценной поддержки метаданных (декораторов, аннотаций), реализовать полноценный DI будет практически невозможно. А при отсутствии типов поддерживать и отлаживать такой DI контейнер будет сложно.
Вы как-то сами себе противоречите:

Почему не di.js?
  • он написан с использованием ES6, т.е. нуждается в предварительной компиляции в ES5



И следом:
Для нашей цели можно воспользоваться spread operator


Spread operator — это ES6 стандарт, как и Классы и let, которые идут в примерах.
И Spread operator и классы и let поддерживаются последней нодой, их не нужно компилировать.
UFO just landed and posted this here
А зачем там работа с асинхронным кодом? Контейнер ничего, кроме вызова конструкторов, делать не должен. Если конструктор возвращает promise — это какой-то очень странный дизайн :)
UFO just landed and posted this here
В своем примере вы ставите обязательным условием при старте иметь «гарантированно «живой» коннекшин к базе данных», но не учитываете, что соединение с базой может быть разорвано по множеству причин. А если все-таки учитываете, но не описали, то знаете как такую ситуацию обработать. Но если знаете как обработать, то отсутствие соединения при старте — всего лишь частный случай для такой обработки, т.е. проблемы на самом деле и нет. Либо я чего-то не понял в вашей схеме.
UFO just landed and posted this here
Понятно. Я не считаю, что это ответственность DI, тут нужен еще один слой абстракции.
Ну и с вечной асинхронкой мне кажется более надежным CQRS/EventSourcing-подход (модные флюксы-рефлюксы это, кстати, оно и есть в упрощенной форме).
Что-то мне неясно, чего такое получилось в итоге. Зачем нужен JSON на 20 строчек, если можно всё это напрямую в JS писать?
UFO just landed and posted this here
… из них самый интересный — di.js от Angular, но он мне не подошел и я решил написать свой.
=)

Если по теме – когда-то пилил тоже "свой", с асинхронной загрузкой, ленивым инстанцированием и прочими прелестями:
https://github.com/gobwas/dm.js

Не по теме DI —
использовать apply, но это не сработает, так как он взаимодействует с методами, а не конструкторами
Вообщем-то apply можно, вот так:
class Foo {
constructor(a, b) {
this.c = a + b;
}
}

const args = [2, 3]
const foo = new (Function.prototype.bind.apply(Foo, [null, ...args]));
console.log(foo.c) //5
Ну вот, тэги вырезались, разметка побилась.
А вообще забавно, посмотрел, во что транспайлится spread в конструкторе — как раз вот в это.
DI для классов практически не нужен. В ноде есть DL для модулей, это require, а вот простая реализация DI для модулей: https://github.com/HowProgrammingWorks/InversionOfControl/blob/master/sandboxedModule/ru/framework.js
А тут пример с декларативным описанием зависимостей модулей: https://github.com/HowProgrammingWorks/InversionOfControl/tree/master/dependencyInjection/ru
Это все только примеры использования, вся реализация уже встроена в ноду, только мало кто знает.
Это не DI, это сервис-локатор.
Сервис-локатор это когда программный компонент запрашивает у локатора ссылку на другой программного компонент (модуль, класс, интерфейс). А тут все иначе, служебный код (управляющий код, среда запуска или фреймворк) незаметно для управляемого кода (библиотеки, прикладной программы) создает контекст и внедряет в него нужные зависимости. Из контекста теперь зависимости не нужно брать через require (DL).
Не суть. DI — это когда конкретная реализация:
1) ничего не знает об «особом» способе получения зависимостей (и вообще о каком-то способе это делать), просто получает все, что надо, в конструктор;
2) не может сама «взять» то, чего ей не давали, не меняя конструктор.

Сервис-локатор же, напротив, это когда
1) реализация знает, что зависимости надо получать особым образом;
2) может взять из контейнера все, что там есть, в любой момент.

А уж что там будет написано, serviceLocator.get('logger') или context.logger — это не имеет значения.

При этом любой DI неизбежно содержит SL внутри. Чтобы это стало DI, надо добавить инстанциацию с constructor injection.
DI можно применять не только к ООП, а к классическим модулям. Вот в ноде модули это не классы, а контексты. В контексте зависимость используется не как
context.logger
а как
logger.write('string');


serviceLocator.get('logger')
и
logger.write('string');
отличаются существенно, первый получает ссылку на зависимость, а второй использует зависимость.
А, понял вас. И посмотрел внимательнее код, извините, что сразу не сделал этого.

Тут уже начинается вкусовщина, пожалуй: мне хотелось бы управлять зависимостями на уровне конструкторов классов и интерфейсов, а не на уровне модулей.

И, кстати, разве vm.createContext — не тяжелая операция?
Почти ни кто на node.js не используют ООП, а управление зависимостями происходит на уровне модулей. vm.createScript тяжелая, а vm.createContext не очень тяжелая, а в этом случае скорость исполнения вообще не важна, потому, что связывание зависимостей происходит при старте приложения.
Ну, не знаю насчет «почти никто». Я использую (хотя я на ноде мало пишу), мои коллеги используют, в том числе TypeScript.
В npm почти нет пакетов с использованием ООП и на TypeScript, а среди распространенных библиотек их вообще нет. Есть так же мнение, что ООП провалилось, http://blogerator.ru/page/oop_why-objects-have-failed Я же думаю, что его просто не умеют правильно использовать. ООП прекрасно подходит для GUI и для обертки объектов операционной системы, как то сокеты, файлы и т.д. Но вот для моделирования предметной области ООП нужно по возможности избегать и использовать структуры данных и функции, к сожалению так нельзя сделать везде, вот в Java придется все писать на ООП, а в ноде лучше писать на мультипарадигменном подходе, применять функционально и реактивное программирование, прототипное наследование, data-driven программирование, событийное и асинхронное программирование, а не лепить объекты куда угодно к месту и не к месту.
> для моделирования предметной области ООП нужно по возможности избегать и использовать структуры данных и функции

Это очень спорное мнение. Eric Evans с вами не согласен.

Вообще, про «провалилось» — это какой-то холивар, и обычно с основной мотивацией «не осилили». Оба подхода имеют право на жизнь. То, что перечисляют в статье — это неправильное использование ООП.
Согласен, в статье некомпетентно нагоняют на ООП, у меня к ООП совсем другие претензии, в основном связанные с тем, что модель предметной области в информационных системах не может иметь методов, потому, что моделируются не объекты реального мира, а лишь их информационная составляющая, например, vehicle.driveTo(address) имеет смысл только если у нас система, приводящая в действие автомобиль, но если мы работаем только с данными, и не занимаемся автоматизацией железа, то нам нужно job = { vehicle, address }. Тут vehicle и address это только только данные, практически стракты, а вот у logisticsService могут быть методы, в которые передается job, например: logisticsService.execute(job). Грубо говоря, за неимением страктов, в некоторых языках vehicle, address нужно делать классами и объектами. Для этого даже есть название «анемичные классы» и «анемичные объекты», которые не имеют поведения, только данные.
Что ж это за предметная область, где нет бизнес-логики, а только данные? Коли так, в программе нет никакого смысла и достаточно использовать СУБД.

Анемичная модель — это вообще не ООП, это эмуляция процедурного программирования средствами объектного языка. К ООП отношения по сути своей не имеет (от ключевого слова class программа не становится объектной). Anemic models и бездумное вынесение бизнес-логики в сервисы — это как раз антипаттерн в DDD. Очень, правда, популярный с распространением Active Record (там так проще).

Может быть, вы путаете anemic models и value objects? Value objects — это нормально, там максимум инварианты. А логика будет как минимум в aggregate root. Если последние два слова непонятны… Эванса читать советовать не буду (там много букв), лучше посоветую Domain Driven Design Quickly, она короткая, и pdf-ку раздают бесплатно.
Я имел в виду именно анемичные модели. Что же делать, если язык только ООП поддерживает, но программисту в задаче ООП не нужен. А бизнес-логика предметной области может находиться не в классах, если использовать не ООП. Это же не серебрянная пуля. Вот недавно с медиума статья по ООП:

https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53#.1ox7s5qd2

Автор резок, а мое мнение, что самое здоровое — это мультипарадигменное программирование с элементами процедурного, функционального, реактивного, асинхронного, событийного, декларативного и управляемого данными, объектного, структурного, обобщенного и метапрограммирования.
Статью надо назвать «goodbye inheritance». Наследование — зло, я предпочитаю делегирование и композицию, а когда (иногда) наследование уместно, придерживаюсь принципа abstract or final. ООП вообще не про наследование, и Страуструповская мантра — это про С++, а не про ООП. Алан Кей вообще говорил, что когда говорил об ООП, вот то, что в С++, никак не имел ввиду :)

Бизнес-логика может находиться где угодно, но это «где угодно» должно находиться в отдельном слое domain model — в том или ином смысле. :) Иначе она будет размазана по всему коду, и при внесении изменений в бизнес-логику будет очень «весело».

Противопоставлять причин не вижу, все перечисленные вами парадигмы прекрасно уживаются вместе.
Для себя использовал bottlejs. Зависимости описываются как в ангуляре, через .service, .factory, .constant и т.д. (что мне не сильно нравится). Пользовался в основном объявлением сервиса, куда передаешь конструктор класса и имена модулей, которые надо заинжектить. Мне понравилось – можно было использовать несколько инстансов одного класса как разные сервисы.
Sign up to leave a comment.

Articles