Передача контекста через множество уровней абстракции приложения
От: 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
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.