Вроде бы все логично.
Программный код разбил на модули в соответствии со статьей.
Объекты в каком-либо слое содержат ссылки на соответствующие интерфейсы из других слоев.
Осталось только все нужные объекты создать при запуске и инициализировать.
При запуске нужно подключиться к БД, установить какие-то начальные значения, прочитать и установить настройки для работы каждого слоя, и т.д.
Например, при запуске нужно создать подключение к конкретной БД, и если подключение не установлено, то и нету смысла вызывать какую-то бизнес-операцию.
Где (в каком месте, в каком слое) надо выполнять все эти операции?
На вышеприведенной картинке не видно подходящего места.
Например, запускается web-клиент. Ясное дело, что клиент ничего не знает про подключение к БД и про настройки БД.
Слой БД тоже не знает про настройки БД. Он просто выполняет свою работу с любыми произвольными настройками, которые ему надо указать.
А кто знает? Какой слой знает про особенности и настройки конкретной БД, чтобы ее проинициализировать?
А ведь кроме БД нужно прочитать\установить настройки и других инфраструктурных микро-сервисов.
Получается, в начальной точке приложения должен быть какой-то супер-класс, который знает про особенности инициализации всех инфраструктурных слоев приложения.
Верно?
Но тогда получается, что это "плохой" God-объект.
Ведь везде пишут что такие "всезнающие" объекты — это плохо.
Как же быть?
Как делать инициализацию? В какой точке? И в каком месте указанной картинки эта точка должна располагаться?
Re: Точка создания и инициализации конкретики приложения
Здравствуйте, zelenprog, Вы писали:
Z>Добрый день!
Z>Стараюсь сделать программу по архитектуре, которая описана в этой статье: Z>https://habr.com/ru/companies/rosbank/articles/559116/
Z>Вот картинка из нее: Z>Image: Чистая архитектура _ с использованием микросервисов.png
Z>Вроде бы все логично. Z>Программный код разбил на модули в соответствии со статьей. Z>Объекты в каком-либо слое содержат ссылки на соответствующие интерфейсы из других слоев.
Z>Осталось только все нужные объекты создать при запуске и инициализировать. Z>При запуске нужно подключиться к БД, установить какие-то начальные значения, прочитать и установить настройки для работы каждого слоя, и т.д. Z>Например, при запуске нужно создать подключение к конкретной БД, и если подключение не установлено, то и нету смысла вызывать какую-то бизнес-операцию.
Z>Где (в каком месте, в каком слое) надо выполнять все эти операции? Z>На вышеприведенной картинке не видно подходящего места.
Z>Например, запускается web-клиент. Ясное дело, что клиент ничего не знает про подключение к БД и про настройки БД. Z>Слой БД тоже не знает про настройки БД. Он просто выполняет свою работу с любыми произвольными настройками, которые ему надо указать. Z>А кто знает? Какой слой знает про особенности и настройки конкретной БД, чтобы ее проинициализировать? Z>А ведь кроме БД нужно прочитать\установить настройки и других инфраструктурных микро-сервисов.
Слой БД про настройки бд знать таки должен, он скорее всего будет специфичен для соотв. бд. А вот Data Abstraction Layer,
т.е. пользователи ORM всяких должно быть пофигу.
Z>Получается, в начальной точке приложения должен быть какой-то супер-класс, который знает про особенности инициализации всех инфраструктурных слоев приложения. Z>Верно?
Не обязательно. У каждого модуля должно быть что-то типа ping -- установить простенькое соединение с бд, чтобы проверить,
что сеть в порядке, пингануть необходимые удаленные сервисы и т.п. Все это можно делать на старте. И если уже тут проблемы,
записать проблему в лог и завершиться.
Z>Но тогда получается, что это "плохой" God-объект. Z>Ведь везде пишут что такие "всезнающие" объекты — это плохо.
Ну какой-то модуль, который знает про все необходимые внешние зависимости может таки понадобиться.
Это если проверять все на старте.
Z>Как же быть? Z>Как делать инициализацию? В какой точке? И в каком месте указанной картинки эта точка должна располагаться?
Можно самодиагностику перенести в сами сервисы, чтобы проверка была по мере необходимости работы с этими сервисами.
Тут о проблемах узнаем, если вообще узнаем, по мере работы приложения. Может какой-то сервис недоступен, а он и никем
не использовался какой-то промежуток времени.
Кодом людям нужно помогать!
Re[2]: Точка создания и инициализации конкретики приложения
S>Слой БД про настройки бд знать таки должен, он скорее всего будет специфичен для соотв. бд. А вот Data Abstraction Layer, S>т.е. пользователи ORM всяких должно быть пофигу.
Я имел ввиду другие настройки: не "внутренние" настройки, а "внешние".
Например, мы хотим запустить приложение для работы с другим sql-сервером.
То есть нужно просто название sql-сервера и название базы из какого-нибудь ini-файла передать в слой DataAccess.
Какой модуль\слой должен это выполнить?
Здравствуйте, zelenprog, Вы писали:
S>>Слой БД про настройки бд знать таки должен, он скорее всего будет специфичен для соотв. бд. А вот Data Abstraction Layer, S>>т.е. пользователи ORM всяких должно быть пофигу. Z>Я имел ввиду другие настройки: не "внутренние" настройки, а "внешние". Z>Например, мы хотим запустить приложение для работы с другим sql-сервером. Z>То есть нужно просто название sql-сервера и название базы из какого-нибудь ini-файла передать в слой DataAccess. Z>Какой модуль\слой должен это выполнить?
Исходя из картинки -- репозиторий. Вообще должен быть некий слой, скрывающий хранилище. Обычно это некий ORM,
типа EF. Он предоставляет высокоуровневые методы для работы с данными, точнее скрывает детали бд от потребителя.
Вот ему и надо эти данные передавать.
Кодом людям нужно помогать!
Re: Точка создания и инициализации конкретики приложения
On May 8, 2024, 12:04 PM, zelenprog <140063@users.rsdn.org> wrote:
Z>Где (в каком месте, в каком слое) надо выполнять все эти операции?
Какие операции? Вы имеете ввиду Active Health Check, который обычно делает k8s (для того, чтобы понять сервис готов к работе или нет)? В вашем вопросе присутствует неоднозначность — у вас же микро-сервисная архитектура. И в этом случае почти каждый сервис будет устанавливать соединение с базой, большинство будут соединятся с другими сервисами. Выполнение конкретного кейса в этом случае (платежное поручение) это выполнение прохода по всему сервисному каскаду. В таких архитектурах каждый из сервисов имеет.
Если имеется ввиду конкретно где в коде, — то хорошей практикой будет завести какой-то стартовый компонент микро-сервиса, задача которого провести тест на целостность.
Z>Слой БД тоже не знает про настройки БД. Он просто выполняет свою работу с любыми произвольными настройками, которые ему надо указать. Z>А кто знает?
Коротко: "devOps должен знать". DevOps пишет сценарий запуска аркестратору, руководствуясь картой запуска и списком переменный окружения для поставляемого продукта от команды разработки. В свою очередь, карта запуска сервисов (как и переменные окружения) обязана быть представлена(ы) в качестве сопроводительного документа поставки. В дальнейшем являются составной частью различных DRP-планов, аудитов целостности и прочих перезапусков.
Z>А ведь кроме БД нужно прочитать\установить настройки и других инфраструктурных микро-сервисов.
См. выше. через переменные окружения (должен соблюдаться принцип "четырех глаз")
Z>Получается, в начальной точке приложения должен быть какой-то супер-класс, который знает про особенности инициализации всех инфраструктурных слоев приложения. Z>Верно?
Зачем? В ж делаете Loosely Coupled Architecture? Делаете тест целостности, я не знаю какое у вас практическое применение, но говоря языком Java — делайте бином. Соответственно если бин не инициализировался — продукт не стартанул. Обычно при правильно архитектуре, если вы еще добавили Active Health Check — то будут знать об этом все от дашбордов/девопсов до всех сервисов в каскаде. Ибо будет отдаваться 404.
⸻ ❧ “Be sure you put your feet in the right place, then stand firm.” ― Abraham Lincoln
Re[4]: Точка создания и инициализации конкретики приложения
Z>>Например, мы хотим запустить приложение для работы с другим sql-сервером. Z>>То есть нужно просто название sql-сервера и название базы из какого-нибудь ini-файла передать в слой DataAccess. Z>>Какой модуль\слой должен это выполнить?
S>Исходя из картинки -- репозиторий. Вообще должен быть некий слой, скрывающий хранилище. Обычно это некий ORM, S>типа EF. Он предоставляет высокоуровневые методы для работы с данными, точнее скрывает детали бд от потребителя. S>Вот ему и надо эти данные передавать.
В Репозиторий мы будем передавать строки типа "SQLServerName = testsqlserver", "DBName = testdb". Читать настройки из ini-файла — это не работа Репозитория.
А какой класс\модуль\объект\слой будет читать эти настройки из ini-файла?
И какой класс\модуль\объект\слой будет координировать работу с ini-файлом и с Репозиторием? Это и есть "центральная" точка инициализации.
Где на схеме она находится?
В функции "main()"?
Z>>Где (в каком месте, в каком слое) надо выполнять все эти операции?
R>В вашем вопросе присутствует неоднозначность — у вас же микро-сервисная архитектура.
Это не принципиально. В приложении может быть много не микро-сервисов, а любых других инфраструктурных сервисов.
R>Какие операции? Вы имеете ввиду Active Health Check, который обычно делает k8s (для того, чтобы понять сервис готов к работе или нет)?
Нет, я имею ввиду не только проверку.
Я имею ввиду любую инициализацию всех объектов\сервисов всех слоев.
Эта инициализация должна быть выполнена при запуске приложения, объекты различных слоев должна быть созданы и связаны друг с другом, должна быть заданы их начальные значения, которые хранятся в настройках приложения, и т.д.
R>Выполнение конкретного кейса в этом случае (платежное поручение) это выполнение прохода по всему сервисному каскаду.
Перед выполнением этого кейса, все сервисы должны быть подготовлены и приведены в "рабочее" состояние.
Где в коде (в коде какого слоя\модуля) должны вызываться методы создания\подготовки\инициализации сервисов?
R>Если имеется ввиду конкретно где в коде, — то хорошей практикой будет завести какой-то стартовый компонент микро-сервиса, задача которого провести тест на целостность.
В этом месте будет код, который тестирует самого себя.
А какой код (из какого модуля) будет вызывать эту проверку?
Z>>Слой БД тоже не знает про настройки БД. Он просто выполняет свою работу с любыми произвольными настройками, которые ему надо указать. Z>>А кто знает?
R>Коротко: "devOps должен знать".
DevOps — это более высокоуровневая организация работы. К программному коду не имеет отношения.
Меня интересует чисто разбиение программного кода на слои, и в каком месте этого программного кода располагается "центральная" точка инициализации приложения.
R> ... через переменные окружения ... R> ... Делаете тест целостности ...
Получается, этот код будет делать:
— читать переменные окружения,
— вызывать инициализацию сервисов со значениями этих переменных
— запускать тест целостности
В каком модуле\слое должен располагаться этот код?
Re[3]: Точка создания и инициализации конкретики приложения
On May 8, 2024, 2:03 PM, zelenprog <140063@users.rsdn.org> wrote:
Z>Где в коде (в коде какого слоя\модуля) должны вызываться методы создания\подготовки\инициализации сервисов?
А вы где обычно делаете? Там и делайте.
Z>В каком модуле\слое должен располагаться этот код?
Вы задаете слишком объемные вопросы, ответ на которые, займут, увы, много времени из-за того что мне придется расписывать какие-то базовые понятия в этой области, возможно быстрее вам будет найти ответы, прочитав книги по корпоративным паттернам проектирования. В особенности те части, относящимися к: Health Endpoint Monitoring pattern Retry pattern External Configuration Store pattern Sharding pattern Anti-corruption Layer pattern PS. Ссылки на Microsoft, но они плюс-минус передают суть. Особенно в части Use Case.
⸻ ❧ “Doubt is a killer. You just have to know who you are and what you stand for.” — Jennifer Lopez
Re[4]: Точка создания и инициализации конкретики приложения
Z>>Где в коде (в коде какого слоя\модуля) должны вызываться методы создания\подготовки\инициализации сервисов?
R>А вы где обычно делаете? Там и делайте.
Я делаю как придется — это неправильно.
Мне хочется разобраться как делать правильно.
Z>>В каком модуле\слое должен располагаться этот код?
R>... возможно быстрее вам будет найти ответы, прочитав книги по корпоративным паттернам проектирования. В особенности те части, относящимися к: R> R> Health Endpoint Monitoring pattern R> Retry pattern R> External Configuration Store pattern R> Sharding pattern R> Anti-corruption Layer pattern R>
Из этого списка более-менее по моему вопросу подходит статья "External Configuration Store pattern".
Все остальные статьи — далеки от сути моего вопроса.
R>Вы задаете слишком объемные вопросы...
Мне кажется, вы просто не понимаете мой вопрос.
Я просто интересуюсь: в каком логическом слое программы (если говорить о разделении программного кода на слои в соответствии с принципами "Чистой Архитектуры") должен располагаться код создания объектов, их инициализации, установки связей между ними.
Ответ на этот вопрос в принципе должен быть простым, например "Такой код располагается в слое "Контроллеры"". Ну и можно добавить краткое обоснование.
Вот например в приведенной вами статье есть кусок кода:
static void Main(string[] args)
{
// Start monitoring configuration changes.
ExternalConfiguration.Instance.StartMonitor();
// Get a setting.var setting = ExternalConfiguration.Instance.GetAppSetting("someSettingKey");
…
}
Но это только начало — прочитать настройки.
Дальше нужно создать конкретный контроллер, конкретный источник данных. Этот конкретный источник данных должен быть проинициализирован в соответствии с настройками. Затем должен быть создан конкретный репозиторий, этот репозиторий в конструкторе должен получить проинициализированный источник данных и т.д. То есть должно быть сделано много предварительных действий.
И только затем в Контроллере должна быть вызвана какая-то бизнес-функция.
Так вот в каком месте\модуле\слое программы выполняются все эти предварительные настроечные\конфигурационные действия?
Re[5]: Точка создания и инициализации конкретики приложения
Z>Получается, в начальной точке приложения должен быть какой-то супер-класс, который знает про особенности инициализации всех инфраструктурных слоев приложения. Z>Верно?
Да. Это место так и называется "точка сборки", "корень композиции", composition root и т.п.
Z>Но тогда получается, что это "плохой" God-объект.
It depends.
Z>Как же быть? Z>Как делать инициализацию? В какой точке? И в каком месте указанной картинки эта точка должна располагаться?
Инициализация делается в той точке где приложение стартует. Что, если подумать, логично, где еще ее делать-то?
А вот как именно делать — тут возможны варианты.
Далее я даю примеры на .NET/C#, потому что работаю в основном с этим окружением, но, думаю, в Java принципиальных отличий не будет.
1. Прямолинейный — собираем граф зависимоcтей руками (aka pure man DI)
void Main() {
IConfig config = ReadConfigFromSomewhere();
IDbConnectionManager dbConManager = new dbConManager(config.DbConnectionString);
IRepository1 repository1 = new MyRepository1(dbConManager);
IRepository2 repository2 = new MyRepository2(dbConManager);
IApplicationService1 appService1 = new ApplicationUseCaseService1(repository1, repository2);
IApplicationService2 appService2 = new ApplicationUseCaseService2(repository2);
// И т.д. и т.п. собираем весь граф зависимостей руками
// Затем запускаем приложение.
// В данном случае некий сервис, которые принимает запросы по сети.using var server = new ApiServer(appService1, appService2);
server.Start();
// При этом не обязательно создавать абсолютно все объекты сразу.
// Можно добавить фабрику, которая будет делать это по требованию, e.g.var appServiceFactory = new ApplicationServiceFactory(config);
using var server = new ApiServer(appServiceFactory);
// Внутри реализации ApiServerprivate void OnRequest(string uri, object payload) {
return uri switch {
"useCase1" => _appServiceFactory.CreateAppService1().RunUseCase1(payload.ParseAs<UseCase1RequestData>());
...
}
}
}
void Main() {
IConfig config = ReadConfigFromSomewhere();
using var di = new Container();
di.AddSingleton(config);
// RepositoriesModule - эта штука, которая лежит в сборке с имплементациями и регистрирует конкретные типы репозиториев под обобщенными интерфейсами
// e.g.
// ioc.AddSingleton<IDbConnectionManager>(new MySqlDbConnectionManager(config.DbConnectionString));
// ioc.AddTransient<IRepo1, Repo1Impl>();
di.Add<RepositoriesModule>(config); // Здесь можно как передать config в регистрацию и достать из него конкретные значения для передачи в конструкторы классов
di.Add<ApplicationServiceModule>(); // Так и принимать в конструкторе класса весь IConfig и самостоятельно доставать оттуда необходимое
di.AddSingleton<ApiServer>();
di.Resolve<ApiServer>().Start();
}
Здравствуйте, zelenprog, Вы писали:
Z>Получается, в начальной точке приложения должен быть какой-то супер-класс, который знает про особенности инициализации всех инфраструктурных слоев приложения. Z>Верно? Z>Но тогда получается, что это "плохой" God-объект. Z>Ведь везде пишут что такие "всезнающие" объекты — это плохо.
Да этот объект получается знает про все остальные объекты, но он очень прост и это не приводит к проблемам.
Z>Как же быть? Z>Как делать инициализацию? В какой точке? И в каком месте указанной картинки эта точка должна располагаться?
В методе Main например вот так:
public class Factory : Ioc
{
private static Factory factory;
public static Factory CreateFactory()
{
if (factory == null)
{
factory = new Factory();
}
return factory;
}
private Factory()
// Новые объекты добавляем здесь
{
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
RegisterSingleton<IniFile>(x => new IniFile(Constants.IniFileName, Constants.IniFileEncoding,x.Resolve<Сryptography>()));
RegisterSingleton<Сryptography>(x => new Сryptography());
Register<string>("server", Resolve<IniFile>().GetValue("sql", "server"));
Register<string>("database", Resolve<IniFile>().GetValue("sql", "database"));
Register<string>("login", Resolve<IniFile>().GetValue("sql", "login"));
}
.............................
/// <summary>
/// Главная точка входа для приложения.
/// </summary>static void Main()
{
try
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
Factory.CreateFactory().Resolve<Service>()
};
ServiceBase.Run(ServicesToRun);
}
catch (Exception e)
{
Factory.CreateFactory().Resolve<FileLog>().Write(e);
}
}
Программа – это мысли спрессованные в код
Re: Точка создания и инициализации конкретики приложения
Здравствуйте, zelenprog, Вы писали:
Z>Как же быть? Z>Как делать инициализацию? В какой точке? И в каком месте указанной картинки эта точка должна располагаться?
А на каком языке вы пишите?
Если на c#, то там есть options pattern. https://learn.microsoft.com/en-us/dotnet/core/extensions/options
При запуске сервиса происходит чтение настроек из конфигурационного файла, окружения и т.д. Создаются соответствующие опции, которые потом через DI доставляются сервисам, которые в них нуждаются. Например в вашем случае адрес, логин и пароль DB попадут в классы репозитория.
Инициализация/обновление структуры БД, при необходимости так же производится на этапе запуска приложения.
В принципе там же можно добавить логику, которая сделает Ping к сторонним сервисам и завершит процесс, если надо, но я бы так не делал, так как в нормальном окружении процесс будет перезапущен, и так в цикле, а это может быть достаточно дорогой операцией.
Как вариант, вы можете добавить некоторый сервис, который будет делать Ping для зависимостей по таймеру.
Z>>Получается, в начальной точке приложения должен быть какой-то супер-класс, который знает про особенности инициализации всех инфраструктурных слоев приложения. Z>>Верно? RD>Да. Это место так и называется "точка сборки", "корень композиции", composition root и т.п.
Ага. Почитал про "точку сборки", "корень композиции", composition root.
Вроде это то, про что я спрашивал.
А к какому слою относится этот кусок кода?
Судя по тому, что он знает про "тонкости" всех других слоев, этот код надо считать еще одним отдельным слоем, который "висит" над другими слоями.
Почему тогда этот кусок кода на схемах архитектуры нигде не выделяют?
Например, как должна выглядеть схема слоев, которую я привел в первом сообщении топика, если на ней показать точку сборки?
Z>>Но тогда получается, что это "плохой" God-объект. RD>It depends.
От каких факторов это зависит? Будет ли точка сборки плохим объектом или хорошим?
RD>Инициализация делается в той точке где приложение стартует. Что, если подумать, логично, где еще ее делать-то? RD>А вот как именно делать — тут возможны варианты. RD>Далее я даю примеры на .NET/C#, потому что работаю в основном с этим окружением, но, думаю, в Java принципиальных отличий не будет.
Примеры на .NET/C# — очень хорошие, понятные. Я понимаю этот язык.
Спасибо.
RD>1. Прямолинейный — собираем граф зависимоcтей руками (aka pure man DI)
Суть понятна.
Но как быть, если целевой объект нужен где-то глубоко в каком-нибудь слое инфраструктуры?
Допустим, ситуация следующая:
— где-то в глубине какого-нибудь инфраструктурного слоя есть объект (назовем его объект-клиент, клиентский объект, AnyServiceClassClient), который будет создавать и использовать другой объект (назовем его целевой объект, AnyServiceClassTarget)
— причем этот целевой объект может понадобиться клиентскому объекту только при определенных условиях
— этот целевой объект реализован разными под-классами, и конкретный под-класс определяется текущими условиями
— допустим цепочка "вложенности" классов этого инфраструктурного слоя следующая "AnyService — AnyServiceClass1 — AnyServiceClass2 — AnyServiceClassClient — AnyServiceClassTarget":
-- класс "AnyService" — это класс самого сервиса, он создается в точке сборки
-- класс "AnyService" в ходе своей работы создает класс "AnyServiceClass1", класс "AnyServiceClass1" в ходе своей работы создает класс "AnyServiceClass2", класс "AnyServiceClass2" — "AnyServiceClassClient", класс "AnyServiceClassClient" — "AnyServiceClassTarget"
— так как класс AnyServiceClassTarget в точке сборки создавать не нужно, то для него надо сделать фабрику.
Но как ее передать в "глубь" слоя? Ведь клиентский объект AnyServiceClassClient, который будет использовать эту фабрику расположен "глубоко" внутри слоя.
Получается, в конструкторы всех классов инфраструктурного слоя надо добавить параметр — эту фабрику, и передавать ее по всей цепочке вызовов?
Как-то это тоже нехорошо получается.
RD>2. Используем контейнер внедрения зависимостей (aka DI-контейнер, IOC-контейнер)
Почитал про контейнеры.
Мнения разделяются, некоторые пишут что контейнеры — это зло.
Re[2]: Точка создания и инициализации конкретики приложения
Z>>Получается, в начальной точке приложения должен быть какой-то супер-класс, который знает про особенности инициализации всех инфраструктурных слоев приложения. Z>>Верно? Z>>Но тогда получается, что это "плохой" God-объект. Z>>Ведь везде пишут что такие "всезнающие" объекты — это плохо.
Q>Да этот объект получается знает про все остальные объекты, но он очень прост и это не приводит к проблемам.
Z>>Как же быть? Z>>Как делать инициализацию? В какой точке? И в каком месте указанной картинки эта точка должна располагаться?
Q>В методе Main например вот так:
Суть понял, спасибо.
Это похоже на реализацию DI-контейнера "вручную". Верно?
Я почитал про контейнеры...
Некоторые пишут, что это зло, и что лучше все-таки использовать "статические" зависимости. То есть создавать зависимости явным созданием объектов, когда они нужны с помощью new, чтобы была доступна проверка типов компилятором.
Но насколько я понимаю, полностью заменить DI-контейнер явным созданием объектов — это очень неудобно.
Так как придется писать длинные цепочки передачи фабрик через параметры конструкторов в глубину слоя.
В предыдущем своем сообщении я пример этой проблемы описал: https://rsdn.org/forum/design/8744930.1
Z>>>Получается, в начальной точке приложения должен быть какой-то супер-класс, который знает про особенности инициализации всех инфраструктурных слоев приложения. Z>>>Верно? Z>>>Но тогда получается, что это "плохой" God-объект. Z>>>Ведь везде пишут что такие "всезнающие" объекты — это плохо.
Q>>Да этот объект получается знает про все остальные объекты, но он очень прост и это не приводит к проблемам.
Z>>>Как же быть? Z>>>Как делать инициализацию? В какой точке? И в каком месте указанной картинки эта точка должна располагаться?
Q>>В методе Main например вот так:
Z>Суть понял, спасибо.
Z>Это похоже на реализацию DI-контейнера "вручную". Верно?
Да Z>Я почитал про контейнеры... Z>Некоторые пишут, что это зло, и что лучше все-таки использовать "статические" зависимости. То есть создавать зависимости явным созданием объектов, когда они нужны с помощью new, чтобы была доступна проверка типов компилятором.
Не очень понятно о чем это. Здесь все проверяется компилятором.
Z>Но насколько я понимаю, полностью заменить DI-контейнер явным созданием объектов — это очень неудобно. Z>Так как придется писать длинные цепочки передачи фабрик через параметры конструкторов в глубину слоя.
Одно из преимуществ контейнеров это то что они освобождают от раздумий о том как сделать не принципиальные вещи типа передачи зависимостей, к их использованию быстро привыкаешь.
Z>В предыдущем своем сообщении я пример этой проблемы описал: Z>https://rsdn.org/forum/design/8744930.1
Регистрируем в контейнере другой контейнер, который уже заряжен для создания нужных нам объектов и передаем его AnyServiceClassClient, а тот уже будет использовать контейнер для создания нужных ему объектов в процессе работы программы. Это если я правильно понял суть проблемы.
Программа – это мысли спрессованные в код
Re: Точка создания и инициализации конкретики приложения
Z>>Некоторые пишут, что это зло, и что лучше все-таки использовать "статические" зависимости. То есть создавать зависимости явным созданием объектов, когда они нужны с помощью new, чтобы была доступна проверка типов компилятором. Q>Не очень понятно о чем это. Здесь все проверяется компилятором.
Ну вот например: https://www.yegor256.com/2014/10/03/di-containers-are-evil.html
Q>Регистрируем в контейнере другой контейнер, который уже заряжен для создания нужных нам объектов и передаем его AnyServiceClassClient, а тот уже будет использовать контейнер для создания нужных ему объектов в процессе работы программы. Это если я правильно понял суть проблемы.
Можете пример кода показать?
Re[3]: Точка создания и инициализации конкретики приложения
Z>А к какому слою относится этот кусок кода? Z>Судя по тому, что он знает про "тонкости" всех других слоев, этот код надо считать еще одним отдельным слоем, который "висит" над другими слоями. Z>Почему тогда этот кусок кода на схемах архитектуры нигде не выделяют? Z>Например, как должна выглядеть схема слоев, которую я привел в первом сообщении топика, если на ней показать точку сборки?
Хз. Наверное, потому что архитектура "портов и адаптеров" (aka гексогональная архитектура) — она все же про логическую организацию взаимосвязей компонентов программы.
А не про то, как правильно сделать эту связь в конкретном ЯП/фреймворке/платформе.
Z>От каких факторов это зависит? Будет ли точка сборки плохим объектом или хорошим?
Смотри на это так, что задача Main — настроить и связать между собой все остальные компоненты приложения.
Отсюда логически вытекает, что для выполнения этой задачи Main должен знать обо всех компонентах, которые он связывает.
При этом он не содержит никакой другой логики (и тем более бизнес-логики), кроме относящейся к настройке и запуску приложения.
Z>Но как быть, если целевой объект нужен где-то глубоко в каком-нибудь слое инфраструктуры? Z>Допустим, ситуация следующая: Z>... Z>Получается, в конструкторы всех классов инфраструктурного слоя надо добавить параметр — эту фабрику, и передавать ее по всей цепочке вызовов? Z>Как-то это тоже нехорошо получается.
Ты немного путаешься.
Если AnyService — AnyServiceClass1 — AnyServiceClass2 — AnyServiceClassClient — AnyServiceClassTarget — это классы одного логического уровня, то физически они будут лежать в одной сборке (в одном java-пакете), а публичным при этом является только AnyService, который реализует условный интерфейс IAnyService и который ты и создаешь в Main:
IAnyService srv = new AnyService(...);
// А как этот AnyService будет делать свою работу и какие классы при этом будет создавать - нам здесь не важно.
// Главное, что он реализует интерфейс IAnyService от которого зависят другие слои.
Иное дело, если AnyServiceClassTarget — это зависимость из другого слоя. В этом случае да, тебе придется протащить ее через все классы своего слоя, где она нужна.
Main() {
IAnyServiceClassTargetFactory target = ...
IAnyService srv = new AnyService(IAnyServiceClassTargetFactory dep) // -> AnyServiceClass1 -> AnyServiceClass2 -> ...
// Или, если зависимость нужна только в конкретном юзе-кейсе
srv.RunSpecificUseCase(target); // передаем зависимость не в конктрукторе, а в параметре конкретного метода
}
Ну, а уж если цепочка получается слишком длинная — то, скорей всего, что-то неправильно спроектировано в структуре классов.
Z>Почитал про контейнеры. Z>Мнения разделяются, некоторые пишут что контейнеры — это зло.
Ну для кого-то ORM зло, а кто-то ООП не признает...
Имхо, как и с любым инструментом, DI-контейнеры надо уметь правильно готовить.
Ну и, возможно, дело в том, что в java-мире, когда говорят от контейнерах, почему-то многие представляют монструозные xml-конфиги, где все эти зависимости конфигурируется в строковом виде. Ошибся в одной букве и у тебя приложение упало в неожиданном месте, в неожиданный момент.
В .NET с этим проще: во всех популярных DI конфигурирование уже давно делается через fluent API непосредственно в коде.
Re[5]: Точка создания и инициализации конкретики приложения
Здравствуйте, zelenprog, Вы писали:
Z>>>Некоторые пишут, что это зло, и что лучше все-таки использовать "статические" зависимости. То есть создавать зависимости явным созданием объектов, когда они нужны с помощью new, чтобы была доступна проверка типов компилятором. Q>>Не очень понятно о чем это. Здесь все проверяется компилятором.
Z>Ну вот например: Z>https://www.yegor256.com/2014/10/03/di-containers-are-evil.html
Q>>Регистрируем в контейнере другой контейнер, который уже заряжен для создания нужных нам объектов и передаем его AnyServiceClassClient, а тот уже будет использовать контейнер для создания нужных ему объектов в процессе работы программы. Это если я правильно понял суть проблемы.
Z>Можете пример кода показать?
internal class Program
{
static void Main(string[] args)
{
var f1 = new Factory1();
var a = f1.Resolve<A>();
a.Foo();
}
}
public class Factory1 : Ioc
{
public Factory1()
{
Register<Factory2>((x) => new Factory2());
Register<A>((x) => new A(x.Resolve<Factory2>()));
}
}
public class Factory2 : Ioc
{
public Factory2()
{
Register<B>((x) => new B());
}
}
public class A
{
private readonly Factory2 factory2;
public A(Factory2 factory2)
{
this.factory2 = factory2;
}
public void Foo()
{
var b = factory2.Resolve<B>();
Console.WriteLine(b);
}
}
public class B
{
public B() { }
}
Класс A получает контейнер который может создавать только ему нужные зависимости.