LINUX.ORG.RU

inversifyjs

 , ,


0

2

Сабж.

Насмотрелся я тут на проекты обложенные всякими декораторами @injectable, контейнерами, символами и интерфейсами (которые реализует один единственный класс) по самое не могу, аж в глазах рябит.

Кто-то это вообще использует? Чем оно лучше ванильного js/babel/ts?

@injectable()
export class My implements MyInterface {
    public constructor(
        @inject(Symbols.Logger) private readonly _log: Logger,
    ) {
    }
}

Что это такое? Почему нельзя сделать просто:

export class My implements MyInterface {
    private readonly log: Logger;

    public constructor()
    {
        this.log = new Logger();
    }
}

И не тащить за собой лишние пакеты?

Ну хочется людям юзать декораторы до того как в стандарте утвердят. В чем проблема-то? Babel гораздо больше проделывает.

https://github.com/nodeca/argparse/blob/70fc26eb5a9a829d6ddcceb6b4c87802218227fd/argparse.js#L200-L232 вот пример когда с декораторами удобнее.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Дело не в декораторах, хотя они идут в качестве бонуса к плохой читабельности (когда их становитcя много).

А вот сама библиотека - столько движений, ради сомнительной выгоды. Вот я и интересуюсь. Зачем оно надо.

neversleep ★★ ()
Последнее исправление: neversleep (всего исправлений: 1)

Как минимум, удобнее тестировать и конфигурировать приложение, поскольку легко можно инжектить альтернативные реализации интерфейсов в зависимости от настроек. В частности, можно инжектить noop-заглушки или моки, чтобы не было побочных эффектов в тестах.

static_lab ★★★★★ ()
Ответ на: комментарий от crutch_master

Я делаю var log = require(«./mylogger»); И засовываю всем.

А потом в тестах этот логгер потребует все свои настройки, начнёт писать лог-файлы, срать в stdout или стучаться во внешний сервис логирования.

static_lab ★★★★★ ()
Ответ на: комментарий от neversleep

Ну может они не осилили это через бабель привернуть. Или у тебя на бабель тоже диатез?

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

Vit ★★★★★ ()
Ответ на: комментарий от crutch_master

Так, ну-ка, и как же вы это делать будете?

src/sum.js

const {log} = require('./mylogger');

const sum = (a, b) => {
    const result = a + b;
    log(`sum: ${a} + ${b} = ${result}`);
    return result;
}

module.exports = {
    sum,
};

src/mylogger.js

const log = (str) => {
    console.log(str);
};

module.exports = {
    log,
};

Нужно протестировать функцию sum.

static_lab ★★★★★ ()
Последнее исправление: static_lab (всего исправлений: 2)
Ответ на: комментарий от anonymous

Что тебе конкретно не нравится?

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

Писать и отлаживать кастомный лоадер — отдельная задача, а в результате изобретётся всё тот же DI-контейнер.

static_lab ★★★★★ ()
Ответ на: комментарий от static_lab

Писать и отлаживать кастомный лоадер — отдельная задача Что там писать и отлаживать? Ты просто передаешь ему мапу вида {source_url: target_url} для проксирования модулей и все.

А мапу пишешь когда тебе надо, какую тебе надо, под любое окружение.

anonymous ()
Ответ на: комментарий от static_lab
const proxyquire = require('proxyquire');
const { assert } = require('chai');
const Sinon = require('sinon');

describe('sum', () => {
  it('sums two numbers', () => {
    const fakeLogger = {
      log: Sinon.stub(),
    };

    const sum = proxyquire('../src/sum', {
      './mylogger': {
        log: fakeLogger.log,
      }
    }).sum;

    assert.equal(sum(1, 2), 3);
    assert(fakeLogger.log.calledOnce);
  });
});
neversleep ★★ ()
Последнее исправление: neversleep (всего исправлений: 1)
Ответ на: комментарий от anonymous

Что там писать и отлаживать? Ты просто передаешь ему мапу вида {source_url: target_url} для проксирования модулей и все.

А мапу пишешь когда тебе надо, какую тебе надо, под любое окружение.

Демонстрируйте на примере выше.

static_lab ★★★★★ ()
Ответ на: комментарий от neversleep

А сейчас чем пользуетесь?

В последнем проекте как раз Inversify. В следующем, может быть, попробовал бы инжектор из Nest.js.

Конструктор из моего примера в первом посте можно переписать в сторону DI

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

Дело в том, что когда у вас класс A требует B и C, B требует D и E, а C требует E и F, то становится неудобно инициализировать эти объекты вручную — нужно следить за зависимостями и создавать экземпляры, начиная с листьев дерева зависимостей. Инжекторы делают эту работу за вас.

static_lab ★★★★★ ()
Ответ на: комментарий от static_lab
const map = { './mylogger.js': './mylogger.debug.js' }

global.require = Object.setPrototypeOf(
    Object.assign(
        id => require.original(id in map ? map[id] : id), 
        { original: require }
    ), require)

const { assert } = require('chai')

describe('sum', () => {
  it('sums two numbers', () => {
    const sum = require('../src/sum').sum;
    assert.equal(sum(1, 2), 3);
  });
});
anonymous ()
Ответ на: комментарий от static_lab

Дело в том, что когда у вас класс A требует B и C, B требует D и E, а C требует E и F, то становится неудобно инициализировать эти объекты вручную — нужно следить за зависимостями и создавать экземпляры, начиная с листьев дерева зависимостей. Инжекторы делают эту работу за вас.

И наконец-то вместо классов будут ломаться инжекторы :)

Vit ★★★★★ ()
Ответ на: комментарий от static_lab
patchRequire()

const { assert } = require('chai')

describe('sum', () => {
  it('sums two numbers', () => {
    require.map.set('./mylogger.js', './mylogger.debug.js')
    const sum = require('../src/sum').sum;
    assert.equal(sum(1, 2), 3);
  });
});

function patchRequire() {
    let req = require, map = new Map
    global.require = id => req(map.has(id) ? map.get(id) : id)
    Object.assign(require, { original: req, map })
    Object.setPrototypeOf(require, req)
}
anonymous ()

механично упражНЯясь

module.exports = { 
    "log" :(
        () => return ( 
            process.env.TEST && require("./test/logger")
                             || (...args) => console.log(...args)
        ))()
}


let log =process.env.TEST && require("./test/logger")
                             || (...args) => console.log(...args)
module.exports = { log }

забавно что отличии в поведении в случае облома тестера при наличии флага теста.

qulinxao3 ()
Ответ на: комментарий от static_lab

Так, ну-ка, и как же вы это делать будете?

Чем угодно.

Начиная от формирования тестовой среды в отдельном подкаталоге только с нужными модулями и необходимым минимумом мокапов. (Что имхо абсолютно правильно, так как 100% отрезает от теста всё лишнее.) И заканчивая ветвлением по переменной окружения.

А вот наворачивание специализированных абстракций порождает необходимость тестировать и поддерживать еще и их.

Учитесь обходиться минимумом имеющихся средств. А то скоро чтобы 2+2 посчитать придётся заводить фабрику фабрики фабрики.

wandrien ()
Ответ на: комментарий от static_lab

Тестируете вы примерно так? (не стал typescript тащить)

import Sinon from 'sinon';
import { Container } from 'inversify';
import { assert } from 'chai';

import Symbols from '../src/ioc/symbols';
import appContainer from '../src/ioc/container';

describe('Warrior', () => {
  describe('attack', () => {
    let container;
    let fakeSword = {
      hit: Sinon.stub(),
    };

    before(() => {
      // FIXME: Чтобы не портить основной контейнер делаем merge?
      container = Container.merge(appContainer, new Container());
      container.rebind(Symbols.Sword).toConstantValue(fakeSword);
    });

    it('does incredible damage', () => {
      const warrior = container.get(Symbols.Warrior);

      warrior.attack();

      assert(fakeSword.hit.calledOnce);
    });
  });
});
Или есть подходы получше? Тестируемый класс:
import * as inversify from "inversify";
import Symbols from './ioc/symbols';

require('reflect-metadata');

export default class Warrior {
  constructor(weapon) {
    this.weapon = weapon;
  }

  attack() {
    this.weapon.hit();
  }
}

inversify.decorate(inversify.injectable(), Warrior);
inversify.decorate(inversify.inject(Symbols.Sword), Warrior, 0);
В целом, интересная штука, но полностью пока не проникся. Надо будет попробовать на чём-то покрупнее.

neversleep ★★ ()
Последнее исправление: neversleep (всего исправлений: 1)
Ответ на: комментарий от wandrien

Учитесь обходиться минимумом имеющихся средств. А то скоро чтобы 2+2 посчитать придётся заводить фабрику фабрики фабрики.

Тоже верно.

neversleep ★★ ()
Последнее исправление: neversleep (всего исправлений: 1)
Ответ на: комментарий от static_lab

Ну так и живут :). Иньекции - не панацея, а замена одной проблемы на другую. А если поддаться на соблазн втыкать их везде где можно, в долгой перспективе станет грустно.

Vit ★★★★★ ()
Ответ на: комментарий от crutch_master

Удобно декораторы как в питоне тащить.
В питоне в flask, например, делаешь декораторы на роли юзеров и типа перед каждым куском кода делаешь так различный доступ.

Наверно, можно по-другому, но так выглядит и чисто, и понятно.

Т.е. иногда декораторы запутывают код, а иногда наоборот, делают его более простым.

Соответственно, в первом случае не нужно из использовать, во втором - нужно.

В топике очень «модное» странное использование декоратора. Но я вообще плохо понимаю использование концепций других языков в яваскрипте если один хрен в браузере это в итоге одним файлом будет и вся модульность - игра в «шоб как в джаве було», а в дереве объектов всё равно можно можно лезть руками куда угодно и в итоге всё зависит от самодисциплины программиста. Уж лучше использовать TypeScript в стиле .NET и не морочить себе и окружающим мозги.

Shadow ★★★★★ ()
Ответ на: комментарий от Vit

Ты гвоздями логгер приколотил

К чему? К env?

просто не напрямую а в соседнем файле

Ну можно в конфиг приложения его приколотить, вместо танцев с env. Просто не понятно, зачем делать в жс также как в жабке. Такие манёвры тут совершенно неуместны.

crutch_master ★★★★★ ()
Ответ на: комментарий от Shadow

делаешь декораторы на роли юзеров и типа перед каждым куском кода делаешь так различный доступ

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

crutch_master ★★★★★ ()
Ответ на: комментарий от crutch_master

Изначально был разговор, что надо уметь менять логер. Ты сказал что это не проблема, но в качестве примера привел код где все прописано жестко (ну 2 варианта вместо одного)

Грубо говоря, вместо решения проблемы ты перекинул ее в другое место.

Просто не понятно, зачем делать в жс также как в жабке. Такие манёвры тут совершенно неуместны.

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

https://github.com/nodeca/event-wire - я делал еще такой вариант, когда песочница прогоняется через произвольную пачку функций, которую можно налепить от балды. Но это не сильно устойчивая конструкция, только для чего-то мелкого.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

но в качестве примера привел код где все прописано жестко (ну 2 варианта вместо одного)

Ну, могу 3 варианта сделать. Что ты имеешь ввиду под жестко/мягко? Менять логгер как и на что? Ну типа можно в env писать путь до модуля, но я подумал, что меня сразу утопят за такое.

Чуть посложнее проект - и быстро приходим к пробрасыванию кучи опций в конструктор.

Да нахрен оно вообще нужно это оопэ с их конструкторами/аннотациями, когда всю иерархию классов можно лепить прям на ходу (и тут же проверять на вменяемость) вместе со всяким зависимостями и прочей ботвой в удобоваримом конфиге. Зачем вообще писать код и пытаться приделать к нему декларативщину, если можно сразу писать декларативщину, впихивая к ней какие-то мелкофункции? Типа это софткодинг и антипаттерн, но какая разница, если и так все этим занимаются в своих спрингах. Гулять так гулять.

crutch_master ★★★★★ ()
Последнее исправление: crutch_master (всего исправлений: 1)