Настройка приложения с контейнером DI - QualitySolution/QSProjects GitHub Wiki

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

  1. Необходимо заменить все вызовы в UoW:
    TryDelete -> Delete
    TrySave -> Save

  2. Все вызовы методов статического класса UnitOfWorkFactory необходимо заменить на вызовы в переданный как зависимость объект IUnitOfWorkFactory, если это невозможно сделать в одном из легаси проектов, то можно вызвать в ServicesConfig.UnitOfWorkFactory

  3. Все вызовы методов статического класса OrmConfig надо заменять на вызовы экземпляра IOrmConfig

II. Описание изменений:

Статика

  • В ServicesConfig собраны глобальные зависимости, которые все еще используются в легаси проектах. Эти зависимости резолвятся из Scope, который устанавливаается при первом подключении к бд. Чтобы он установился при подключении к бд необходимо вызвать метод AddStaticServicesConfig при регистрации зависимостей в контейнере

  • Сейчас доступны следующие статические регистрации в контейнере:
    AddStaticServicesConfig() - глобальные сервисы для легаси проектов
    AddStaticHistoryTracker() - включение мониторинга изменений, замена HistoryMain.Enable()

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

Класс OnDatabaseInitialization

Специальный класс для помощи в регистрации действий со статикой в момент первого подключения к бд. Использовать только в легаси проектах в которых есть сложности с заменой на контейнер. Как использовать:

public static IServiceCollection AddSomethingStatic(this IServiceCollection services) {
	services.AddSingleton<OnDatabaseInitialization>((provider) => {
        //Получение зависимостей по необходимости
		var someDependency = provider.GetRequiredService<SomeDependency>();
        //Настройка статики
        //ВАЖНО! В методе Config ниже, не должно быть обращений к бд!
		StaticClass.Config(someDependency);
		return new OnDatabaseInitialization();
	});
	return services;
}

Регистрация зависимостей

Реализованы некоторые методы для настройки приложения с помошью контейнера (описаны ниже).
Их можно заменить на свои реализации частично, или по примеру написать свои.
Если реализовывать свои методы на замену существующих, то необходимо учитывать какой класс регистрируется в контейнере, скорее всего этот класс запрашивается далее в других настройках.

Часто используемые методы можно объединить в один сервис, например:

//Объединение часто используемых настроек для подключения к бд
public static IServiceCollection AddDatabaseConnection(this IServiceCollection services)
{
	services
        //Свой проект с маппингами
		.AddCoreDataNHibernate()
		.AddDatabaseConnectionSettings()
		.AddDatabaseConnectionString()
        //Своя реализация настройки SQL
		.AddSpatialSqlConfiguration()
		.AddNHibernateConfiguration()
		.AddDatabaseInfo()
		.AddDatabaseSingletonSettings()
		;

	services.AddStaticServicesConfig();

	return services;
}

Методы для настройки подключении к бд

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

  1. Указания сборок с мапингами для Nhibernate
    Можно вызвать в исполняемом проекте метод services.AddMappingAssemblies(), передав в него все сборки с мапингами Но логичнее в каждой сборке создать метод который регистрирует зависимости этого проекта и в нем уже вызвать services.AddMappingAssemblies(Assembly.GetExecutingAssembly())
    Регистрируется как IMappingAssembliesProvider

  2. Настройки подключения
    Настройки для подключения организованы в IDatabaseConnectionSettings и настроен биндинг из конфигурации на секцию DatabaseConnectionSettings (но можно указать свое имя). Эта настройка вызывается методом services.AddDatabaseConnectionSettings()

  3. Строка подключения
    Метод services.AddDatabaseConnectionString() берет настройки подключения из IDatabaseConnectionSettings и регистрирует MySqlConnectionStringBuilder

  4. Настройка SQL
    Метод services.AddSqlConfiguration() берет строку подключения из MySqlConnectionStringBuilder, указывает драйвер, диалект и регистрирует MySQLConfiguration

  5. Донастройка Nhibernate (ExposeConfiguration)
    Метод services.AddDatabaseConfigurationExposer() указывает функцию для изменения конфигурации NHibernate в момент сборки конфигурации Nhibernate.
    Это способ вызвать настройку fluentConfig.ExposeConfiguration()
    Регистрируется как IDatabaseConfigurationExposer

  6. Сборка конфигурации Nhibernate
    Метод services.AddNHibernateConfiguration() делает:

    • добавляет все сборки с маппингами из зарегистрированных объектов IMappingAssembliesProvider
    • использует SQL конфигурацию из MySQLConfiguration
    • (опционально) применяет зарегистрированные конвенции IConvention
    • (опционально) применяет метод донастройки конфигурации из IDatabaseConfigurationExposer
    • (опционально) включает трекер изменений из GlobalUowEventsTracker

    И по итогу собирает конфигурацию Nhibernate в Configuration

  7. Выбор фабрики UoW
    Доступны 3 варианта фабрик UoW в зависимости от трекера изменений:

    • services.AddNotTrackedUoW() - без отслеживания изменений
    • services.AddTrackedUoW() - с отслеживанием изменений для проектов без GUI
    • services.AddGuiTrackedUoW() - с отслеживанием изменений для проектов с GUI

    Регистрации с трекерами так же регистрируют и трекеры: GlobalUowEventsTracker, SingleUowEventsTracker и ITrackerActionInvoker для вызова кода в GUI
    В итоге регистрируется фабрика IUnitOfWorkFactory

  8. Фабрика сессий Nhibernate
    Метод services.AddSessionFactory() получает конфигурацию Configuration и собирает из нее фабрику сессий регистрируя ее как ISessionFactory.
    В момент когда в первый раз будет запрошен ISessionFactory происходит первое подключение к БД, и до возврата ISessionFactory будут вызваны все регистрации OnDatabaseInitialization, вызывая действия прописанные при их регистрации.
    ВАЖНО! OnDatabaseInitialization не должны содержать код который обращается к бд, иначе будет зацикливание и StackOverflowException

Дополнительные методы для настройки системы

  1. AddDatabaseInfo
    Регистрирует IDatabaseInfo, информацию о базе получает из MySqlConnectionStringBuilder

  2. AddUserService
    Регистрирует IUserService, информацию загружает из бд используя логин из IDatabaseConnectionSettings

  3. AddCore
    Является объединением необходимых регистраций из проекта QS.Project.Core:

    • Маппинги из этого проекта
    • Фабрику сессий
    • Сервис пользователя
    • IOrmConfig
    • ISessionProvider
  4. AddDesktop
    Является объединением необходимых регистраций из проекта QS.Project.Gtk:

    • IGuiDispatcher
  5. AddGuiInteracive
    Регистрирует сервисы диалогов пользователя с GUI:

    • IInteractiveMessage
    • IInteractiveQuestion
    • IInteractiveService
  6. AddObjectValidatorWithGui
    Регистрирует IValidator с выводом GUI результатов валидации

Трекер изменений

Так как проекты с GUI подписываются на отслеживание изменений для обновления данных в GUI, необъодимо реализовать выполнение подписок на зменения в главном потоке приложения. Чтобы можно было отслеживать изменения в проектах с GUI и при этом чтобы сам трекер не имел зависимостей от GUI, функция выполнения уведомлений об изменениях (ITrackerActionInvoker) вынесена в листенеры например IIUowPostUpdateEventListener (с реализацией в UowTracker), чтобы выбор того как выполнять требуемый код лежал на том классе который ждет эти изменения и знает как их правильно выполнять. Сам же глобальный трекер не лезет в сравнение потоков и UI.

Async Unit of Work

В IUnitOfWork и его реализации добавлены асинхронные методы для работы с бд, методы Try* были удалены.

Валидатор

В валидатор ObjectValidator добавлена возможность установить ServiceProvider для того, чтобы можно было резолвить глобальные зависимости в методе валидации. Устанавливается методом services.AddObjectValidatorWithGui(), либо самостоятельно в свойство ObjectValidator.ServiceProvider.

III. Предложения

Предложение описывать регистрации в проектах (модулях) в виде:

В каждом (по необходимости) проекте создавать статический класс DependencyInjection, в котором делать методы добавления в контейнер того или иного функционала из этого проекта, например:

public static class DependencyInjection
{
    public static IServiceCollection AddSomeService(this IServiceCollection services)
    {
        services.AddSingleton<ISingletonService, SingletonService>();
        services.AddScoped<IScopedService, ScopedService>();
        services.AddTransient<IServicePerDependency, ServicePerDependency>();
        return services;
    }
}

Такие регистрации можно будет использовать в Autofac, но не наоборот. Регистрировать с помощью Autofac можно будет так:

var services = new ServiceCollection();
services.AddSomeService();
builder.Populate(services);
⚠️ **GitHub.com Fallback** ⚠️