Передача контекста через множество уровней абстракции приложения
От: LWhisper  
Дата: 30.05.16 10:51
Оценка: 11 (4)
Всем привет.
Пришло время собирать камни — привести архитектуру приложения в божеский вид.
Сейчас она описывается меткой фразой "некогда думать, надо копать".
Проявляется это во всём, начиная с циклических референсов в сборках, благодаря рефлекшену, и заканчивая Constructor Injection c 28 зависимостями.
Если за первое нужно просто отрывать руки, то последнее — следствие непродуманной архитектуры. Но это уже есть и возникает вопрос — как бы изловчиться и, не переписывая десятки мегабайт кода, привести существующее безобразие в более-менее адекватный вид.

В связи с чем вопрос — что делать?
Есть некий воркфлоу приложения, который, продираясь через множество уровней абстракции, уходит всё глубже в дебри internal-сборок и попутно обрастает параметрами-зависимостями, в нижней точки своего падения приходя к тем самым 28 аргументам в конструкторе очередного класса, без которых он жить не может.

Тут и Id сессии, и Id текущей задачи, и идентификатор пользователя, и логгер и ещё чёрт знает что, на лицо ужасное, стрёмное внутри.
Хочется выбросить это в какой-нибудь легко доступный (как в плане кода, так и в плане перфоманса) контекст, с которым будет легко общаться.
Для данных таковым контекстом выступает БД, благодаря тому, что она всего одна, то доступ осуществляется через синглтон (когда-нибудь это обязательно выстрелит -_-).
А вот для всего остального он отсутствует, и тянутся эти зависимости per aspera ad astra.

Возьмём, для примера, логирование.
Вот запускается приложение и моментально начинает писать лог. Имя лога спускается в аргументах командой строки.
Окей, от этого трудно отказаться (хотя и можно), но пока терпимо.
Происходит аутентификация пользователя. Теперь лог пишется уже в каталог этого горемычного юзера.
Юзер выполняет некоторую команду, лог пишется всё в ту же папку, но уже в файл с именем выполняемой команды.
Команда начинает параллелиться и работает с разными компьютерами в сети. Каждая задача начинает писать лог в файл с именем упомянутой выше команды + имя компьютера.
В процессе, мы начинаем использовать более низкоуровневые интерфейсы, которые тоже пишут лог и ни сном ни духом не знают ничего ни о юзерах, ни о командах, ни о хостах. Но при этом тоже хотят писать в лог текущее состояние и логировать ошибки, уходя на ретраи если сетка моргнула, но ещё есть шанс получить отклик от удалённой машинки. А писать то логи нужно в те же самые файлы с именем команды, хоста, в папке юзера. Появляются ThreadLocal-коллекции логеров, через которые проходят все вызовы.
Но низкоуровневые компоненты тоже не лыком шиты и помимо распаралеливания на высшем уровне, дёргают асинхронные методы на нижнем, которые и вовсе выполняются в случайном потоке из пула и в свою очередь тоже дёргают ещё более низкоуровневые компоненты, которые всё ещё хотят писать логи в те же файлы, что и далёкий сверхудёрнувший их компонент. А значит приходится тащить сквозь все слои логер и регистрировать его в статичной ThreadLocal-коллекции из предыдущей серии.
Вот примерно так и рождаются вереницы аргументов в конструкторах и методов, которых от версии к версии становятся всё больше. Иногда видишь подобное безобразие, свернёшь 10-ок аргументов в мини-контекст, поменяешь вызовы. Месяц-другой код живёт, а потом глядь — добавилось ещё два аргумента, а в твоём контексте добавились лишние свойства, которые нужны в 4 из 7 методов, но инциализируются, естественно, всегда. Кода становится ещё больше, страдает читабельность, производительность, и простые компоненты обрастают сложной логикой, нужной для инициализации одного из компонентов нижнего уровня, который просто не знает — куда ему писать лог.

Некоторое время душу терзают мысли о Dependeny Injection. Но в компании уже был неудачный опыт внедрения этой штуки (о котором(ой) я ни сном, ни духом), закончившийся выпиливанием оной с большим количеством матюгов. В общем, не прижилось, да оно и понятно — время жизни и порядок инициализации объектов были непоняты от слова совсем, и зачастую людям возвращались объекты, которые они не просили, и были зарегистрированы вообще не пойми кем, когда и с какой целью. В общем, всё плохо, и опять же из-за непродуманной архитектуры, отрисованой на коленке, чтобы хоть как-то работало в стиле write-only.

В общем, я буду благодарен любым камрадам и камрадкам, которые подскажут — как быть в подобной ситуации. Желательно, с реальными примерами, близкими к описанной ситуации с логированием. Что делать, куда смотреть, что читать, что изучать? Какие паттерны подойдут, а какие не стоит трогать ни в коем случае. Если DP, то с ссылками на книги и статьи по правильной разработке и конфигурации этого чуда в реалиях .NET.

Спасибо за внимание.
.net архитектура dependeny injection legacy code refactoring logging
Re: Передача контекста через множество уровней абстракции приложения
От: Sinix  
Дата: 30.05.16 11:46
Оценка: 16 (3) +1
Здравствуйте, LWhisper, Вы писали:

LW>Всем привет.


Для начала вот что надо сделать:

1. Выделить чисто утилитарные классы, которые сами по себе с зависимостями не работают и всё нужное явно декларируют в API, через свойства / параметры.
Этот слой вы скорее всего захотите тестировать и заморачиваться с "вспомни, какие зависимости забыл положить" вам вряд ли понравится.

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

3. Внедрить immutable класс-контекст, который будет отвечать за передачу / подмену зависимостей. Что-то очень легковесное, типа ServiceContainer.

ВАЖНО: Никогда, ни при каких обстоятельствах не повторяйте вот эту ошибку в дизайне (метод по умолчанию возвращает null, если сервис не найден). Довелось поработать на проекте с таким решением, если коротко — выстреливало постоянно.
Методы, которые в случае "нунишмогла" возвращают null, надо обзывать с префиксом Try. Иначе ловить вам null reference в самых внезапных местах до конца жизни проекта.

4. Задокументировать типовые сервисы в виде extension-методов. Т.е. большинство вызовов должны выглядеть как context.GetLogger(), а не как context.GetService<ILogger>(). Без этого различить "используем стандартное API" от "протаскиваем экзотичную фигню" невозможно и код превращается в мешанинуиз зависимостей.

5. Для свежего фреймворка (читай, для 4.6): рассмотрите возможность передавать контекст неявно, через AsyncLocal<T>. Упрощает передачу контекста до
var logger = Context.CurrentContext.Logger(); // Context - static class

а с using static — до
var logger = CurrentContext.Logger();

без необходимости протаскивать контекст через параметры.


Вот после того как всё это сделано, большинство проблем с зависимостями рассосётся и оставшееся можно будет обсуждать предметно. Конкретно:


LW>Происходит аутентификация пользователя. Теперь лог пишется уже в каталог этого горемычного юзера.

В момент аутентификации происходит подмена контекста, в новом контексте переопределяется логгер.

LW>Юзер выполняет некоторую команду, лог пишется всё в ту же папку, но уже в файл с именем выполняемой команды.

Снова подмена контекста.

Оффтоп: Вас саппорт случаем не убивает за необходимость собирать лог из десятка файлов?


LW>Команда начинает параллелиться и работает с разными компьютерами в сети. Каждая задача начинает писать лог в файл с именем упомянутой выше команды + имя компьютера.

Нувыпоняли

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

LW>Некоторое время душу терзают мысли о Dependeny Injection. Но в компании уже был неудачный опыт внедрения этой штуки (о котором(ой) я ни сном, ни духом), закончившийся выпиливанием оной с большим количеством матюгов.

DI — это уже нашлёпка поверх протаскиваемого контекста. Если с ним бардак, то DI только замаскирует проблему, а не поможет её вылечить.
Re[2]: Передача контекста через множество уровней абстракции приложения
От: LWhisper  
Дата: 30.05.16 12:12
Оценка:
S>3. Внедрить immutable класс-контекст, который будет отвечать за передачу / подмену зависимостей. Что-то очень легковесное, типа ServiceContainer.
Ага, спасибо, взгляну.

S>5. Для свежего фреймворка (читай, для 4.6): рассмотрите возможность передавать контекст неявно, через AsyncLocal<T>.

Вот тут и начинается самое интересное.
.NET 4.6 и VS2015 — это серьёзный шаг, до которого ещё пёхать и пёхать.
Проблему ThreadLocal я уже описывал. Но в связи с тем, что в .NET нет иерархии потоков, как я понимаю, AsyncLocal точно также придётся поддерживать вручную во всех типах, которые запускают потоки. Но было бы здорово почитать про это поподробнее. Цена вопроса, области применения и т.д.

S>В момент аутентификации происходит подмена контекста, в новом контексте переопределяется логгер.

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

S>Оффтоп: Вас саппорт случаем не убивает за необходимость собирать лог из десятка файлов?

Нет, он вполне себе счастлив, так как гигабайты логов из параллельных потоков в одном файле читать было бы весьма неприятно.

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

Можно подробнее?

S>DI — это уже нашлёпка поверх протаскиваемого контекста. Если с ним бардак, то DI только замаскирует проблему, а не поможет её вылечить.

Понял, спасибо.
Re[3]: Передача контекста через множество уровней абстракции приложения
От: Sinix  
Дата: 30.05.16 12:53
Оценка: 4 (1)
Здравствуйте, LWhisper, Вы писали:

LW>Вот тут и начинается самое интересное.

LW>.NET 4.6 и VS2015 — это серьёзный шаг, до которого ещё пёхать и пёхать.
Зря. Таргетинг под 4.6 можно и в 2012й врубить. На всём что древнее сидеть — редкостный мазохизм.

LW>Проблему ThreadLocal я уже описывал.

ThreadLocal вообще obsolete надо объявить. Лучшего способа отхватить граблей в сочетании с тасками я ещё не видел.

Ну ок, не нравится AsyncLocal — можно с LogicalCallContext поэксперементировать. В 4.0 он с тасками емнип не дружил, начиная с 4.5 всё ок.
Пример.

LW>Но в связи с тем, что в .NET нет иерархии потоков, как я понимаю, AsyncLocal точно также придётся поддерживать вручную во всех типах, которые запускают потоки.

Вообще-то call context с первого фреймворка был. Ссылки в предыдущем абзаце. Матчасть никто не знает, какабычна

LW>Цена вопроса, области применения и т.д.

В смысле цена вопроса? Вот этого?
        static readonly AsyncLocal<string> _current = new AsyncLocal<string>();

        static void Main(string[] args)
        {
            _current.Value = "Calling DoAsync";

            Console.WriteLine("In Main \tcurrent: " + _current.Value);
            DoAsync().Wait();
            Console.WriteLine("In Main \tcurrent: " + _current.Value);

            Console.Write("Done...");
            Console.ReadKey();
        }

        static async Task DoAsync()
        {
            await Task.Delay(500);
            Console.WriteLine("In DoAsync\tcurrent: " + _current.Value);

            _current.Value = "Calling DoAsync2";
            Console.WriteLine("In DoAsync\tcurrent: " + _current.Value);
            await DoAsync2();
            Console.WriteLine("In DoAsync\tcurrent: " + _current.Value);
        }

        static async Task DoAsync2()
        {
            await Task.Delay(500);
            Console.WriteLine("In DoAsync2\tcurrent: " + _current.Value);

            _current.Value = "Calling DoInThread";
            Console.WriteLine("In DoAsync2\tcurrent: " + _current.Value);
            var t = new Thread(DoInThread);
            t.Start();
            t.Join();
            Console.WriteLine("In DoAsync2\tcurrent: " + _current.Value);
        }

        static void DoInThread()
        {
            Thread.Sleep(500);
            Console.WriteLine("In DoInThread\tcurrent: " + _current.Value);

            _current.Value = "From DoInThread";
            Console.WriteLine("In DoInThread\tcurrent: " + _current.Value);
        }

Оно автоматом работает. Открываем msdn/гуглим примеры, пишем пруфтесты и используем с чистой совестью. Что-то поломается — узнаете по отпавшим пруфтестам.
Пруфтесты для таких вещей обязательны.


LW>При наличии неявной передачи контекста выглядит довольно просто и удобно. При явной, к сожалению, придётся тащить его в самые отдалённые места, в каждый метод, в каждый класс, который когда либо хотел, хочет или может захотеть что-нибудь залогировать.

Ну да. А какие ещё варианты?
Принцип тот же самый во всех прочих подходах будет, отличается только обёртка.

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

LW>Можно подробнее?

Что-то типа PresentationTraceSources. Т.е. отдельный логгер для каждой из категорий задач. Иначе неправильно подменённый контекст — и весь вывод радостно усвистывает не в тот файл.
Re: Передача контекста через множество уровней абстракции приложения
От: 0x7be СССР  
Дата: 30.05.16 13:52
Оценка: +1 :)
Здравствуйте, LWhisper, Вы писали:


LW>Вот примерно так и рождаются вереницы аргументов в конструкторах и методов, которых от версии к версии становятся всё больше. Иногда видишь подобное безобразие, свернёшь 10-ок аргументов в мини-контекст, поменяешь вызовы. Месяц-другой код живёт, а потом глядь — добавилось ещё два аргумента, а в твоём контексте добавились лишние свойства, которые нужны в 4 из 7 методов, но инциализируются, естественно, всегда. Кода становится ещё больше, страдает читабельность, производительность, и простые компоненты обрастают сложной логикой, нужной для инициализации одного из компонентов нижнего уровня, который просто не знает — куда ему писать лог.

Для этого идеально подходит комбинация DI + паттерна Wrapper.

LW>Некоторое время душу терзают мысли о Dependeny Injection. Но в компании уже был неудачный опыт внедрения этой штуки (о котором(ой) я ни сном, ни духом), закончившийся выпиливанием оной с большим количеством матюгов. В общем, не прижилось, да оно и понятно — время жизни и порядок инициализации объектов были непоняты от слова совсем, и зачастую людям возвращались объекты, которые они не просили, и были зарегистрированы вообще не пойми кем, когда и с какой целью. В общем, всё плохо, и опять же из-за непродуманной архитектуры, отрисованой на коленке, чтобы хоть как-то работало в стиле write-only.

Речь идет о конкретных DI-контейнерах?
Re[2]: Передача контекста через множество уровней абстракции приложения
От: Sinix  
Дата: 30.05.16 14:09
Оценка:
Здравствуйте, 0x7be, Вы писали:

0>Для этого идеально подходит комбинация DI + паттерна Wrapper.

DI сам по себе не решает проблему передачи зависимостей по цепочке вызовов. Нужно протаскивать контейнер, что превращает DI в извращённую разновидность service locator.
Wrapper тоже не поможет, т.к. в подобных задачах список зависимостей заранее неизвестен.
Re[3]: Передача контекста через множество уровней абстракции приложения
От: 0x7be СССР  
Дата: 30.05.16 14:45
Оценка: 43 (2) +1
Здравствуйте, Sinix, Вы писали:

S>DI сам по себе не решает проблему передачи зависимостей по цепочке вызовов. Нужно протаскивать контейнер, что превращает DI в извращённую разновидность service locator.

S>Wrapper тоже не поможет, т.к. в подобных задачах список зависимостей заранее неизвестен.
Зачем контейнер???

Объявляем интерфейс:
interface ILogger
{
    void log(string message);
}


Инжектируем его в компонент-пользователя:

class MyComponent
{
    public MyComponent(..., Ilogger logger, ...)
    {
        ...
    }
}


Имеем "листовую" реализацию ILogger, которая осуществляет физическую запись куда надо.
По необходимости добавляем обёртки, которые добавляют в записываемое сообщение нужные атрибуты: имя компонента, идентификатор потока, дату/время и т.п.
В итоге компонент, в который инжектируется логгер понятия не имеет обо всех этих тонкостях, ему дали трубу — он туда дует, а куда она дует — его не волнует.
Наличие DI-контейнера для этой схемы строго опционально. В конце концов можно сконструировать нужную структуру объектов "руками".
Re[4]: Передача контекста через множество уровней абстракции приложения
От: Sinix  
Дата: 30.05.16 14:49
Оценка:
Здравствуйте, 0x7be, Вы писали:

0>Имеем "листовую" реализацию ILogger, которая осуществляет физическую запись куда надо.

А, в этом смысле?
Тогда да, получается примерно то же самое, что написал выше, только вместо контекста выступает di-контейнер.

У топикстартера как раз одна из проблем — как этот контейнер по всей цепочке вызовов протащить. Есть какие-нибудь идеи?
Re: Передача контекста через множество уровней абстракции приложения
От: Нахлобуч Великобритания https://hglabhq.com
Дата: 30.05.16 15:40
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Вот запускается приложение и моментально начинает писать лог. Имя лога спускается в аргументах командой строки.


А что если (отстаньте, мыши, я -- стратег):

interface ILogger
{
    IDisposable DecoratedWith(LoggerDecorator decorator);
    void Write(string message);
}


void Main(string[] args)
{
    var logger = new Logger();
    logger.DecoratedWith(new FileNameDecorator(args[0]));
}

// ...

void PostAuthenticateUser()
{
    var logger = Context.GetLogger();
    logger.DecoratedWith(new UserNameDecorator(CurrentUser.UserName))
}

// ...

void ExecuteCommand(string name)
{
    var logger = Context.GetLogger();
    using(logger.DecoratedWith(new CommandNameDecorator(name)))
    {
        ThreadPool.QueueUserWorkItem(...);
    }
}



И потом всякий желающий через Context.GetLogger() достает ILogger, уже нафаршированный, гм, "декораторами" и, стало быть, пишущий туда, куда надо.
HgLab: Mercurial Server and Repository Management for Windows
Re[4]: Передача контекста через множество уровней абстракции приложения
От: LWhisper  
Дата: 30.05.16 16:23
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Зря. Таргетинг под 4.6 можно и в 2012й врубить. На всём что древнее сидеть — редкостный мазохизм.

Пасиб, нашёл.

S>ThreadLocal вообще obsolete надо объявить. Лучшего способа отхватить граблей в сочетании с тасками я ещё не видел.

Ага.

S>Ну ок, не нравится AsyncLocal — можно с LogicalCallContext поэксперементировать. В 4.0 он с тасками емнип не дружил, начиная с 4.5 всё ок.

S>Пример.
S>Вообще-то call context с первого фреймворка был. Ссылки в предыдущем абзаце. Матчасть никто не знает, какабычна
Спасибо, ознакомлюсь. :D))

S>Что-то типа PresentationTraceSources. Т.е. отдельный логгер для каждой из категорий задач. Иначе неправильно подменённый контекст — и весь вывод радостно усвистывает не в тот файл.

И ещё раз спасибо!
Re[2]: Передача контекста через множество уровней абстракции приложения
От: LWhisper  
Дата: 30.05.16 16:25
Оценка:
Здравствуйте, 0x7be, Вы писали:

0>Речь идет о конкретных DI-контейнерах?

Использовалось расширение Mocknity для MS Unity.
Re[2]: Передача контекста через множество уровней абстракции приложения
От: LWhisper  
Дата: 30.05.16 16:30
Оценка:
Здравствуйте, Нахлобуч, Вы писали:

Н>А что если (отстаньте, мыши, я -- стратег):

Н>И потом всякий желающий через Context.GetLogger() достает ILogger, уже нафаршированный, гм, "декораторами" и, стало быть, пишущий туда, куда надо.
Вот там, выше, Sinix предложил варианты с AsyncLocal и LogicalCallContext. Если удастся при помощи одного из них прозрачно протащить контекст до любого компонента, то после этого подойдёт любой из вариантов.
Re[5]: Передача контекста через множество уровней абстракции приложения
От: 0x7be СССР  
Дата: 30.05.16 16:33
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Тогда да, получается примерно то же самое, что написал выше, только вместо контекста выступает di-контейнер.

А сам компонент о контейнере вообще ничего знать не должен. Ему нужны сервисы — ему их дают через параметры конструктора. Как — его не волнует.

S>У топикстартера как раз одна из проблем — как этот контейнер по всей цепочке вызовов протащить. Есть какие-нибудь идеи?

У топикстартера проблема в том, чтобы протащить все те параметры, которые я предлагаю упаковать в разные врапперы.
Re[3]: Передача контекста через множество уровней абстракции приложения
От: 0x7be СССР  
Дата: 30.05.16 16:35
Оценка:
Здравствуйте, LWhisper, Вы писали:

0>>Речь идет о конкретных DI-контейнерах?

LW>Использовалось расширение Mocknity для MS Unity.
А зачем? Что мешает руками создавать и инжектировать нужные объекты друг в друга без особой контейнерной магии с autowiring`ом и гаданиями о том, почему контейнер выдал не тот экземпляр?
Re[6]: Передача контекста через множество уровней абстракции приложения
От: Sinix  
Дата: 30.05.16 16:47
Оценка:
Здравствуйте, 0x7be, Вы писали:

S>>Тогда да, получается примерно то же самое, что написал выше, только вместо контекста выступает di-контейнер.

0>А сам компонент о контейнере вообще ничего знать не должен. Ему нужны сервисы — ему их дают через параметры конструктора. Как — его не волнует.

Ну так в том-то и проблема, что "не знать про контейнер" не получается, у топикстартера это только самый нижний из слоёв. А выше — компонент вызывает другие компоненты, в которые надо тоже протащить зависимости. И далее по цепочке. Причём зависимости надо разруливать динамически, заранее они неизвестны, т.к. реализация обычно за простенький интерфейс прячется.


S>>У топикстартера как раз одна из проблем — как этот контейнер по всей цепочке вызовов протащить. Есть какие-нибудь идеи?

0>У топикстартера проблема в том, чтобы протащить все те параметры, которые я предлагаю упаковать в разные врапперы.
Нееет. Ты похоже просто с такими системами не сталкивался. В них типовая проблема — цепочка вызовов в 5-7 слоёв, в каждом из которых могут потребоваться свои зависимости, о которых вызывающий код ничего знать не должен. И общее число зависимостей — не единицы, а ближе к паре десятков. Ибо они добавляются/убираются буквально на ходу, под текущий набор требований.

Что-то типа сильно нелинейного пайплайна получается.
Re[7]: Передача контекста через множество уровней абстракции приложения
От: 0x7be СССР  
Дата: 30.05.16 16:57
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Ну так в том-то и проблема, что "не знать про контейнер" не получается, у топикстартера это только самый нижний из слоёв. А выше — компонент вызывает другие компоненты, в которые надо тоже протащить зависимости. И далее по цепочке. Причём зависимости надо разруливать динамически, заранее они неизвестны, т.к. реализация обычно за простенький интерфейс прячется.

А почему в эти другие компоненты зависимости должен протаскивать вызывающий код, а не тот, который его конструирует?


S>Нееет. Ты похоже просто с такими системами не сталкивался. В них типовая проблема — цепочка вызовов в 5-7 слоёв, в каждом из которых могут потребоваться свои зависимости, о которых вызывающий код ничего знать не должен. И общее число зависимостей — не единицы, а ближе к паре десятков. Ибо они добавляются/убираются буквально на ходу, под текущий набор требований.

Вызывающий код и не должен знать. Должен знать код конструирующий.
В любом случае, это пока теория, я не знаю, что там точно у автора.
Re[8]: Передача контекста через множество уровней абстракции приложения
От: Sinix  
Дата: 30.05.16 17:31
Оценка:
Здравствуйте, 0x7be, Вы писали:


0>А почему в эти другие компоненты зависимости должен протаскивать вызывающий код, а не тот, который его конструирует?

Потому что тогда требование "протаскиваем зависимости" заменяется на "протаскиваем код, который конструирует объекты", т.е. по сути ничего не меняется. Вот типичная схема:
static SomeResult DumpA(..., Context context)
{
   var data = context.Get<IAReportService>().PrepareData(...);

   var dumper = context.Get<IDumpService>();

   return dumper.Dump(data);
}

и

внутри IAReportService нам нужен, допустим, логгинг, доступ к субд, к интернету (подтягиваем обновления данных, если ещё не) и к сервису с API от интегрированной внешней системы.

внутри IDumpService() — IO (используется временный файл), логгинг и сервис-хелпер для редиректа потока данных клиенту даже после завершения метода — чтоб не держать лишние ресурсы и не вызывать метод заново при обрыве связи.

Нюансы в следующем:
1. Зависимости заранее неизвестны. В крайнем случае бывает вот так: завтра клиент попросит — выкинем часть расчётов в пул задач, интернет поменяем на чтение переданного файла, лог будем показывать бегущей строкой на клиенте а IDumper вообще заменим на реализацию клиента. При необходимости конкретная реализация будет подсунута на лету, без пересборки всего хозяйства. При этом клиенты из другой организации на том же сервисе получат контекст с старыми зависимостями.
2. Оба сервиса могут вызывать другие бизнес-сервисы и так пока не надоест.

Оба требования в итоге не оставляют выбора — в каждый из уровней явно/неявно должен протаскиваться текущий контекст. Будет это делаться явно, передачей через параметры, или контейнер будет запихивать самого себя в создаваемый объект в момент вызова Get<>() — это уже нюансы реализации, чисто принципиально подход от этого не поменяется.
Re[9]: Передача контекста через множество уровней абстракции приложения
От: 0x7be СССР  
Дата: 31.05.16 10:24
Оценка:
Здравствуйте, Sinix, Вы писали:


S>Потому что тогда требование "протаскиваем зависимости" заменяется на "протаскиваем код, который конструирует объекты", т.е. по сути ничего не меняется. Вот типичная схема:

S>
S>static SomeResult DumpA(..., Context context)
S>{
S>   var data = context.Get<IAReportService>().PrepareData(...);

S>   var dumper = context.Get<IDumpService>();

S>   return dumper.Dump(data);
S>}
S>

А точно надо протаскивать эти зависимости через цепочку вызовов (т.е. в параметрах методов)?
Я как-то до этого обходится инжектированием зависимостей через конструктор.


S>Нюансы в следующем:

S>1. Зависимости заранее неизвестны. В крайнем случае бывает вот так: завтра клиент попросит — выкинем часть расчётов в пул задач, интернет поменяем на чтение переданного файла, лог будем показывать бегущей строкой на клиенте а IDumper вообще заменим на реализацию клиента. При необходимости конкретная реализация будет подсунута на лету, без пересборки всего хозяйства. При этом клиенты из другой организации на том же сервисе получат контекст с старыми зависимостями.
Тут тонкий нюанс. Я в целом против такого подхода, когда в теле метода происходит извлечение ссылок из некоторого Service Provider`а. Такой подход делает внешние зависимости класса "затерянными" в процедурном коде и практически исключает compile-time контроль того, что граф объектов собран как надо.
Re[5]: Передача контекста через множество уровней абстракции приложения
От: LWhisper  
Дата: 31.05.16 10:28
Оценка: 4 (1)
S>>Ну ок, не нравится AsyncLocal — можно с LogicalCallContext поэксперементировать. В 4.0 он с тасками емнип не дружил, начиная с 4.5 всё ок.
S>>Пример.
S>>Вообще-то call context с первого фреймворка был. Ссылки в предыдущем абзаце. Матчасть никто не знает, какабычна
LW>Спасибо, ознакомлюсь. :D))
CallContext — шикарная штука! Работает! Огромное спасибо!
А есть какие-нибудь best practices по разработке бизнес-логики с использованием контекстов? Как донести до читателей-писателей кода, что вот эта штука зависит от того же ILogger, который необходимо проинициализировать, а если этого не сделать, то рухнешь на ровном месте с NotInitializedException? А может и не рухнешь, а рухнет у кастомера, который пошёл по самому редкому сценарию, не покрытому тесами и избежавшего пристального взора QA?
Re[5]: Передача контекста через множество уровней абстракции приложения
От: Sharov Россия  
Дата: 31.05.16 10:57
Оценка:
Здравствуйте, Sinix, Вы писали:

S>У топикстартера как раз одна из проблем — как этот контейнер по всей цепочке вызовов протащить. Есть какие-нибудь идеи?


ExecutionContext Class? Ну об похожем говорилось выше, да (LogicalCallContext).
Кодом людям нужно помогать!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.