Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Практическая работа №2: Модульная архитектура и внедрение зависимостей

Описание

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

Быстрый старт

cd csharp
dotnet run --project src/Pr2.ModulesAndDi/Pr2.ModulesAndDi.csproj

Архитектура решения

Разделение ответственности: Ядро vs Модули

Ядро (Core)

Файлы: Core/IAppModule.cs, Core/IAppAction.cs, Core/ModuleCatalog.cs, Core/ModuleLoadException.cs

Ответственность:

  • Определяет контракты для модулей (IAppModule, IAppAction)
  • Обнаруживает модули через рефлексию
  • Загружает внешние модули из папки modules
  • Строит граф зависимостей и вычисляет порядок загрузки (топологическая сортировка)
  • Проверяет корректность конфигурации (отсутствующие модули, циклические зависимости)
  • НЕ знает о конкретных модулях и их реализации

Почему ядро не нужно менять при добавлении модуля:

  • Ядро работает только с интерфейсами IAppModule и IAppAction
  • Обнаружение модулей происходит автоматически через рефлексию
  • Порядок загрузки вычисляется динамически на основе зависимостей
  • Регистрация сервисов делегируется самим модулям

Модули

Ответственность:

  • Реализуют бизнес-логику
  • Объявляют свои зависимости декларативно
  • Регистрируют свои сервисы в DI-контейнере
  • Взаимодействуют друг с другом только через DI-контейнер

Добавление нового модуля:

  1. Создать класс, реализующий IAppModule
  2. Указать имя и зависимости
  3. Зарегистрировать сервисы в RegisterServices()
  4. Добавить имя модуля в appsettings.json

Ядро автоматически:

  • Обнаружит новый модуль
  • Проверит его зависимости
  • Загрузит в правильном порядке
  • Вызовет его методы инициализации

Главная программа (Program.cs)

Ответственность:

  • Читает конфигурацию из appsettings.json
  • Использует ядро для обнаружения и загрузки модулей
  • Создает DI-контейнер
  • Запускает действия модулей

Реализованные возможности

1. Контракт модуля (IAppModule)

public interface IAppModule
{
    string Name { get; }                           // Имя модуля
    IReadOnlyCollection<string> Requires { get; }  // Зависимости
    void RegisterServices(IServiceCollection services);  // Регистрация сервисов
    Task InitializeAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken);  // Инициализация
}

2. Обнаружение модулей

Из текущей сборки (через рефлексию):

var discovered = ModuleCatalog.DiscoverFromAssembly(Assembly.GetExecutingAssembly());

Из внешней папки modules (загрузка DLL):

var modulesPath = Path.Combine(AppContext.BaseDirectory, "modules");
var externalModules = ModuleCatalog.DiscoverFromDirectory(modulesPath);

Приложение автоматически находит все классы, реализующие IAppModule, и создает их экземпляры.

3. Управление зависимостями

Алгоритм Кана (топологическая сортировка):

  • Строит граф зависимостей между модулями
  • Вычисляет корректный порядок загрузки
  • Обнаруживает циклические зависимости
  • Проверяет наличие всех требуемых модулей

Пример порядка загрузки:

Core → Logging → Validation → Export → Report

4. Обработка ошибок

Отсутствующий модуль

Unhandled exception. Pr2.ModulesAndDi.Core.ModuleLoadException: 
Модуль не найден, имя модуля NonExistent

Отсутствующая зависимость

Unhandled exception. Pr2.ModulesAndDi.Core.ModuleLoadException: 
Не хватает модуля для зависимости, модуль Logging требует Core

Циклическая зависимость

Unhandled exception. Pr2.ModulesAndDi.Core.ModuleLoadException: 
Обнаружена циклическая зависимость модулей, проблемные модули TestCycleA, TestCycleB

5. Внедрение зависимостей

Все зависимости регистрируются через Microsoft.Extensions.DependencyInjection:

  • Модули регистрируют свои сервисы в методе RegisterServices()
  • Сервисы внедряются через конструкторы
  • Поддерживаются разные времена жизни: Singleton, Transient, Scoped

6. Демонстрация времен жизни объектов

Модуль Lifetime демонстрирует разницу между Singleton и Transient:

=== Демонстрация времен жизни ===
Singleton #1: 66d0c3e9-3f36-4dfb-8978-ef9e7447ce68
Singleton #2: 66d0c3e9-3f36-4dfb-8978-ef9e7447ce68
Singleton одинаковые: True

Transient #1: baac0481-3d73-470d-a47c-a3b394dc3c4d
Transient #2: 528a9381-6a6d-44d0-a22b-110ba0364ada
Transient разные: True

Singleton - один экземпляр на всё приложение (ID одинаковый)
Transient - новый экземпляр при каждом запросе (ID разные)

Реализованные модули

1. CoreModule

Зависимости: нет
Сервисы:

  • IClock / SystemClock - получение текущего времени
  • IStorage / InMemoryStorage - хранилище данных в памяти

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

2. LoggingModule

Зависимости: Core
Сервисы:

  • ILogger через Microsoft.Extensions.Logging
  • Консольный логгер

Действие: Выводит тестовое сообщение в журнал.

3. ValidationModule

Зависимости: Core
Действие:

  • Проверяет правила валидации данных (минимальная длина строки)
  • Добавляет валидированные данные в хранилище

4. ExportModule

Зависимости: Core, Validation
Действие:

  • Читает все данные из хранилища
  • Экспортирует их в файл export.txt

5. ReportModule

Зависимости: Core, Export
Действие:

  • Формирует отчет с текущим временем
  • Выводит количество записей в хранилище

6. LifetimeModule

Зависимости: Core
Сервисы:

  • SingletonService (Singleton)
  • TransientService (Transient)

Действие: Демонстрирует разницу между временами жизни объектов.

Конфигурация

Основная конфигурация (appsettings.json)

{
  "Modules": [
    "Core",
    "Logging",
    "Validation",
    "Export",
    "Report",
    "Lifetime"
  ]
}

Демонстрационные сценарии

Проект включает несколько готовых конфигураций:

  1. appsettings.scenario1.json - минимальный набор (Core, Logging, Validation)
  2. appsettings.scenario2.json - модули с экспортом (Core, Validation, Export)
  3. appsettings.scenario3.json - все модули включая Lifetime
  4. appsettings.error-missing.json - демонстрация ошибки отсутствующего модуля
  5. appsettings.error-dependency.json - демонстрация ошибки отсутствующей зависимости

Запуск демонстрационных сценариев

Windows (PowerShell)

cd csharp
.\run-scenarios.ps1

Linux/macOS (Bash)

cd csharp
chmod +x run-scenarios.sh
./run-scenarios.sh

Скрипт автоматически запустит все сценарии и покажет результаты работы приложения с разными конфигурациями.

Тестирование

Запуск тестов

cd csharp
dotnet test

Реализованные тесты

Файл: tests/Pr2.ModulesAndDi.Tests/ModuleCatalogTests.cs

  1. Порядок_запуска_учитывает_зависимости

    • Проверяет корректную топологическую сортировку
    • Тест: A → B → C загружаются именно в этом порядке
  2. Отсутствующий_модуль_даёт_понятную_ошибку

    • Проверяет выброс ModuleLoadException
    • Проверяет наличие понятного сообщения "Модуль не найден"
  3. Цикл_зависимостей_обнаруживается

    • Проверяет обнаружение циклических зависимостей (A → B → A)
    • Проверяет наличие понятного сообщения "циклическая"
  4. Внедрение_зависимостей_работает

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

Загрузка внешних модулей

Приложение поддерживает загрузку модулей из внешних DLL файлов.

Создание внешнего модуля

  1. Создайте новый проект библиотеки классов:
dotnet new classlib -n MyExternalModule
  1. Добавьте ссылку на основной проект:
dotnet add reference ../Pr2.ModulesAndDi/Pr2.ModulesAndDi.csproj
  1. Реализуйте IAppModule:
public sealed class MyExternalModule : IAppModule
{
    public string Name => "MyExternal";
    public IReadOnlyCollection<string> Requires => new[] { "Core" };
    
    public void RegisterServices(IServiceCollection services)
    {
        services.AddSingleton<IAppAction, MyAction>();
    }
    
    public Task InitializeAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken)
        => Task.CompletedTask;
}
  1. Скомпилируйте и скопируйте DLL в папку modules:
dotnet build
mkdir -p ../Pr2.ModulesAndDi/bin/Debug/net8.0/modules
cp bin/Debug/net8.0/MyExternalModule.dll ../Pr2.ModulesAndDi/bin/Debug/net8.0/modules/
  1. Добавьте модуль в appsettings.json:
{
  "Modules": ["Core", "MyExternal"]
}

Приложение автоматически обнаружит и загрузит внешний модуль.

Выполненные требования

Основные требования

1. Контракт модуля - имя, зависимости, регистрация служб, инициализация
2. Чтение списка модулей из файла настроек (appsettings.json)
2. Загрузка из каталога modules - поддержка внешних DLL
3. Построение порядка запуска с учетом зависимостей (топологическая сортировка)
4. Обработка отсутствующего модуля с понятным сообщением
5. Обработка циклических зависимостей с понятным сообщением
6. Регистрация через DI-контейнер (Microsoft.Extensions.DependencyInjection)
7. Модули выполняют действия - видимое изменение поведения программы

Требования к испытаниям

1. Проверки корректного порядка на нескольких наборах зависимостей
2. Проверка ошибки отсутствующего модуля с понятным сообщением
3. Проверка ошибки циклических зависимостей с понятным сообщением
4. Проверка внедрения зависимостей через контейнер (не вручную)

Усложнения ([*])

Загрузка модулей из отдельной папки как внешних библиотек
Демонстрация времен жизни объектов (Singleton vs Transient) с доказательством через журнал

Технологии

  • .NET 8.0
  • Microsoft.Extensions.DependencyInjection - DI-контейнер
  • Microsoft.Extensions.Configuration - конфигурация
  • Microsoft.Extensions.Logging - журналирование
  • xUnit - тестирование
  • System.Reflection - обнаружение модулей
  • System.Runtime.Loader - загрузка внешних сборок

Структура проекта

csharp/
├── src/
│   └── Pr2.ModulesAndDi/
│       ├── Core/                    # Ядро системы
│       │   ├── IAppModule.cs        # Контракт модуля
│       │   ├── IAppAction.cs        # Контракт действия
│       │   ├── ModuleCatalog.cs     # Каталог модулей
│       │   └── ModuleLoadException.cs
│       ├── Modules/                 # Реализованные модули
│       │   ├── CoreModule.cs
│       │   ├── LoggingModule.cs
│       │   ├── ValidationModule.cs
│       │   ├── ExportModule.cs
│       │   ├── ReportModule.cs
│       │   └── LifetimeModule.cs
│       ├── Services/                # Сервисы
│       │   ├── IClock.cs
│       │   ├── SystemClock.cs
│       │   ├── IStorage.cs
│       │   ├── InMemoryStorage.cs
│       │   ├── ILifetimeDemo.cs
│       │   ├── SingletonService.cs
│       │   └── TransientService.cs
│       ├── Program.cs               # Главная программа
│       ├── appsettings.json         # Основная конфигурация
│       ├── appsettings.scenario*.json  # Демо-сценарии
│       └── appsettings.error-*.json    # Сценарии ошибок
├── tests/
│   └── Pr2.ModulesAndDi.Tests/
│       └── ModuleCatalogTests.cs    # Тесты
├── run-scenarios.ps1                # Скрипт демонстрации (Windows)
└── run-scenarios.sh                 # Скрипт демонстрации (Linux/macOS)

Примеры вывода

Успешный запуск (все модули)

Запуск действий модулей
Действие Проверка журнала событий
Действие Проверка правил данных
Действие Демонстрация времен жизни объектов

=== Демонстрация времен жизни ===
info: Pr2.ModulesAndDi.Modules.LoggingModule.LoggingAction[0]
      Сообщение из модуля журналирования
Singleton #1: 66d0c3e9-3f36-4dfb-8978-ef9e7447ce68
Singleton #2: 66d0c3e9-3f36-4dfb-8978-ef9e7447ce68
Singleton одинаковые: True

Transient #1: baac0481-3d73-470d-a47c-a3b394dc3c4d
Transient #2: 528a9381-6a6d-44d0-a22b-110ba0364ada
Transient разные: True
Действие Экспорт данных в файл
Действие Формирование отчёта
Отчёт сформирован, время 5/11/2026 9:39:12 PM +03:00, записей 1

Ошибка отсутствующего модуля

Unhandled exception. Pr2.ModulesAndDi.Core.ModuleLoadException: 
Модуль не найден, имя модуля NonExistent
   at Pr2.ModulesAndDi.Core.ModuleCatalog.BuildExecutionOrder(...)

Ошибка отсутствующей зависимости

Unhandled exception. Pr2.ModulesAndDi.Core.ModuleLoadException: 
Не хватает модуля для зависимости, модуль Logging требует Core
   at Pr2.ModulesAndDi.Core.ModuleCatalog.BuildExecutionOrder(...)

Что сдавать преподавателю

  1. Исходный код - весь проект в папке csharp/
  2. Инструкция запуска - этот README.md
  3. Набор проверок - тесты в tests/Pr2.ModulesAndDi.Tests/
  4. Демонстрационные сценарии - файлы appsettings.scenario*.json и скрипты run-scenarios.*
  5. Пояснение архитектуры - раздел "Архитектура решения" в этом документе

Заключение

Проект демонстрирует полноценную модульную архитектуру с:

  • Четким разделением ответственности между ядром и модулями
  • Автоматическим обнаружением модулей
  • Управлением зависимостями через топологическую сортировку
  • Внедрением зависимостей через DI-контейнер
  • Поддержкой внешних модулей
  • Демонстрацией разных времен жизни объектов
  • Полным набором тестов и демонстрационных сценариев

Ключевое преимущество: Добавление нового модуля не требует изменения ядра или логики запуска - достаточно создать класс, реализующий IAppModule, и добавить его имя в конфигурацию.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages