Skip to content

Docs (RU)

Pavel Roslyakov edited this page Dec 2, 2024 · 14 revisions

Вводная информация

  • Для быстрого разворачивания микросервиса, можно воспользоваться моим проектом для инициализации .NET 8 Web Api Onion Architecture Microservice: NET8-Onion-Architecture-Microservice-Template.
  • Для удобного использования советую сразу в проект устанавливать все 3 библиотеки, т.к. каждая из них может пригодиться в той или иной степени.
  • Перед добавлением библиотек советую удалить автоматически добавляемый NuGet Swashbuckle.AspNetCore в проекте ASP .NET 8 Web API, т.к. данный NuGet пакет содержится в AspNetCoreMicroserviceInitializer.TradingDesk.
  • Для настройки микросервиса (его серверной части) и добавления модулей из библиотеки используется новый класс - WebApplicationFacade, который служит "оберткой" для WebApplication и WebApplicationBuilder и позволяет гибко настроить желаемые модули.
public class Program
{
    public static void Main(string[] args)
    {
        var modules = new List<WebApplicationModules>
        {
            WebApplicationModules.Database,
            WebApplicationModules.Settings,
            WebApplicationModules.Services,
            WebApplicationModules.Serilog,
            WebApplicationModules.HealthChecks
        };

        var dockerComposeModules = new List<DockerComposeFileModules>
        {
            DockerComposeFileModules.Server,
            DockerComposeFileModules.PostgreSql,
            DockerComposeFileModules.Adminer
        };

        var app = new WebApplicationFacade(modules)
            .InitBaseConfig()
            .InitBaseDockerComposeFiles(dockerComposeModules)
            .AddAdditionalModules(builder =>
            {
                builder.Services.AddGrpc();
                builder.Services.AddScoped<ICatsFactRepository, CatsFactRepository>();
            })
            .CreateApplication();

        app.MapGrpcServices();

        app.Run();
    }
}
  • Все модули, которые можно добавить в микросервис с помощью библиотек, а так же условия их добавления ("summary" и "remarks") описаны ниже:
/// <summary>
/// Модули веб-приложения.
/// </summary>
public enum WebApplicationModules
{
    /// <summary>
    /// Модуль для автоматической регистрации настроек конфига.
    ///
    /// Для корректной работы данного модуля необходимо:
    /// 1. Добавить в приложение модель настроек конфига.
    /// 2. Создать в конфиге элемент модели настроек (название класса и название элемента конфига должны совпадать).
    /// 3. Присвоить моделям настроек атрибут <see cref="AutoRegisterConfigSettingsAttribute"/>.
    /// </summary>
    /// <remarks>Регистрация моделей будет произведена автоматически, используя атрибут <see cref="AutoRegisterConfigSettingsAttribute"/>.</remarks>
    Settings = 0,

    /// <summary>
    /// Модуль для автоматической регистрации сервисов.
    /// 
    /// Для корректной работы данного модуля необходимо:
    /// 1. Создать сервис и присвоить ему атрибут <see cref="AutoRegisterServiceAttribute"/>.
    /// </summary>
    /// <remarks>
    /// При необходимости добавления фабричной функции во время регистрации сервиса, необходимо унаследовать созданный сервис от <see cref="ServiceBase"/>,
    /// и переопределить в созданном сервисе метод <see cref="ServiceBase.ImplementationFactory"/>.
    /// необходимо
    /// </remarks>
    Services = 1,

    /// <summary>
    /// Модуль добавления в приложение модуля для работы с Sql базами данных.
    ///
    /// Для корректной работы данного модуля необходимо:
    /// 1. Создать модели <see cref="DbContext"/>.
    /// 2. Создать репозитории для работы с <see cref="DbContext"/>.
    /// 3. Присвоить моделям <see cref="DbContext"/> атрибут <see cref="AutoRegisterDbContextAttribute"/>.
    /// 4. Присвоить моделям репозиториев <see cref="AutoRegisterRepositoryAttribute"/>.
    /// </summary>
    /// <remarks>Регистрация моделей будет произведена автоматически, используя атрибуты <see cref="AutoRegisterDbContextAttribute"/> и <see cref="AutoRegisterRepositoryAttribute"/> (регистрация репозиториев производится как AddScoped).</remarks>
    SqlDatabase = 2,

    /// <summary>
    /// Модуль для автоматической регистрации HealthChecks.
    ///
    /// Для корректной работы данного модуля необходимо:
    /// 1. Добавить в приложение классы HealthChecks, унаследованные от <see cref="IHealthCheck"/> и присвоить им атрибут <see cref="AutoRegisterHealthCheckAttribute"/>.
    /// 2. Создать в конфиге элемент модели настроек Health Checks <see cref="HealthChecksSettings"/>.
    /// </summary>
    /// <remarks>1. Регистрация моделей будет произведена автоматически, используя реализацию интерфейса <see cref="IHealthCheck"/> и атрибут <see cref="AutoRegisterHealthCheckAttribute"/>.
    /// 2. Если в настройках конфига включен параметр <see cref="HealthChecksSettings.UIEnable"/>, то получить доступ к UI оболочке можно по URL: /healthchecks-ui</remarks>
    HealthChecks = 3,

    /// <summary>
    /// Модуль автоматической регистрации <see cref="AutoMapper"/>.
    ///
    /// Для корректной работы данного модуля необходимо:
    /// 1. Создать базовую модель.
    /// 2. Создать модель DTO.
    /// 3. Создать профиль, унаследованный от <see cref="Profile"/> для маппинга моделей.
    /// 4. Присвоить модели DTO атрибут <see cref="AutoRegisterProfileAttribute"/> и передать в его параметры необходимые типы моделей.
    /// </summary>
    /// <remarks>Регистрация моделей будет произведена автоматически, используя атрибут <see cref="AutoRegisterProfileAttribute"/>.</remarks>
    AutoMappers = 4,

    /// <summary>
    /// Модуль автоматической регистрации политики Cors.
    ///
    /// Для корректной работы данного модуля необходимо:
    /// 1. Создать в конфиге элемент модели настроек <see cref="CorsSettings"/>.
    /// </summary>
    Cors = 5,

    /// <summary>
    /// Модуль для работы с фоновыми задачами Hangfire.
    ///
    /// Для корректной работы данного модуля необходимо:
    /// 1. Создать фоновые задачи, реализующие интерфейс <see cref="IHangfireBackgroundTask"/>.
    /// 2. Создать в конфиге элементы моделей настроек задач, которые должны быть унаследованны от <see cref="HangfireTaskSettingsBase"/> для каждой задачи.
    /// 3. Создать в конфиге элемент модели настроек Hangfire <see cref="HangfireSettings"/>.
    /// 4. Создать в конфиге элемент модели настроек дашборда Hangfire <see cref="HangfireDashboardSettings"/>.
    /// 5. При необходимости создать фильтр авторизации для дашборда Hangfire, унаследованный от <see cref="IDashboardAuthorizationFilter"/> или же использовать существующие фильтры (<see cref="AllAuthorizationFilter"/>).
    /// 6. Присвоить задачам атрибут <see cref="AutoRegisterHangfireTaskAttribute"/> и передать в его параметры необходимый типы модели настроек.
    /// </summary>
    /// <remarks>Регистрация моделей будет произведена автоматически, используя атрибут <see cref="AutoRegisterHangfireTaskAttribute"/>.</remarks>
    Hangfire = 6,
    
    /// <summary>
    /// Модуль Swagger.
    /// </summary>
    Swagger = 7,

    /// <summary>
    /// Модуль Serilog.
    /// 
    /// Настроить модуль можно в конфиге appsettings.json. Базовый конфиг для настроек Serilog можно проинициализировать, используя метод .InitBaseConfig() у WebApplicationFacade.
    /// Обратиться к логгеру можно как используя интерфейс <see cref="ILogger{TCategoryName}"/>, так и используя статический класс <see cref="Serilog.Log"/>.
    /// </summary>
    Serilog = 8,

    /// <summary>
    /// Модуль переменных окружения.
    /// </summary>
    EnvironmentVariables = 9,

    /// <summary>
    /// Модуль конфигурации ApiExplorer (служба Minimal APIs).
    /// </summary>
    EndpointsApiExplorer = 10,

    /// <summary>
    /// Модуль инициализации мигратора <see cref="Migrator"/> (применяет созданные миграции к БД) и проведения миграций при старте приложения с использованием <see cref="MigrationHostedService"/>.
    ///
    /// Для корректной работы мигратора необходимо:
    /// 1. Создать модели <see cref="DbContext"/>.
    /// 2. Присвоить моделям <see cref="DbContext"/> атрибут <see cref="AutoRegisterDbContextAttribute"/>.
    /// 3. Создать миграции, используя команду <code>dotnet ef migrations add InitialCreate --project your-project/your-project.csproj --startup-project your-project/your-project.csproj --output-dir Migrations</code>.
    /// </summary>
    EFMigrations = 11,

    /// <summary>
    /// Модуль контроллеров.
    /// </summary>
    Controllers = 12,

    /// <summary>
    /// Модуль базы данных MongoDb.
    /// 
    /// Для корректной работы модуля необходимо:
    /// 1. Создать модели репозиториев, унаследованных от MongoRepositoryBase.cs
    /// 2. Создать модели настроек для каждого репозитория. Модели необходимо унаследовать от <see cref="MongoSettingsBase"/> и присвоить им атрибут <see cref="AutoRegisterConfigSettingsAttribute"/>.
    /// 3. Создать автоматически или заполнить вручную модели настроек MongoDb в файле appsettings.json.
    /// </summary>
    MongoDatabase = 13,

    /// <summary>
    /// Модуль базы данных Redis.
    ///
    /// 1. Создать модели репозиториев, унаследованных от RedisRepositoryBase.cs
    /// 2. Создать модели настроек для каждого репозитория. Модели необходимо унаследовать от <see cref="RedisSettingsBase"/> и присвоить им атрибут <see cref="AutoRegisterConfigSettingsAttribute"/>.
    /// 3. Создать автоматически или заполнить вручную модели настроек Redis в файле appsettings.json.
    /// </summary>
    RedisDatabase = 14
}
  • Новые атрибуты, которые используются для инициализации модулей:
[AutoRegisterConfigSettingsAttribute] - атрибут для автоматической регистрации настроек конфига в DI для дальнейшего получения их с использованием IOptions.

[AutoRegisterDbContextAttribute] - атрибут для автоматической регистрации контекста БД.

[AutoRegisterHangfireTaskAttribute] - атрибут для регистрации фоновых задач Hangfire.

[AutoRegisterHealthCheckAttribute] - атрибут для автоматической регистрации IHealthCheck.

[AutoRegisterProfileAttribute] - атрибут автоматической регистрации профилей Profile в маппере IMapper.

[AutoRegisterRepositoryAttribute] - атрибут для автоматической регистрации репозиториев в DI.

[AutoRegisterServiceAttribute] - атрибут для автоматической регистрации сервисов в DI.
  • Конкретные примеры использования модулей можно посмотреть в проекте AspNetCoreMicroserviceInitializer.Examples. Данный проект делался просто для тестов модулей, поэтому о "красоте кода" в нем я не задумывался. Как будет дописан мой MVP-проект с 3-мя микросервисами, которые используют эту библиотеку, то обновлю документацию и приложу здесь на него ссылку, т.к. там код намного чище и красивей.

AspNetCoreMicroserviceInitializer.Registrations

Что делает эта библиотека?

Эта библиотека добавляет в Ваш проект класс WebApplicationFacade. Он расширяет возможности стандартного WebApplicationBuilder, добавляя новый функционал, такой как:

  • Добавление любого модуля из WebApplicationModules (enum, который описан в самом начале), который производит автоматическую регистрацию какого-либо элемента.
var modules = new List<WebApplicationModules>
{
    WebApplicationModules.Database,
    WebApplicationModules.Settings,
    WebApplicationModules.Services,
    WebApplicationModules.Serilog,
    WebApplicationModules.HealthChecks
};

var app = new WebApplicationFacade(modules)
    .CreateApplication();

app.Run();
  • Добавление дополнительной конфигурации к WebApplicationBuilder, с помощью использования метода .AddAdditionalModules().
var app = new WebApplicationFacade(modules)
    .AddAdditionalModules(builder =>
    {
        builder.Services.AddGrpc();
        builder.Services.AddScoped<ICatsFactRepository, CatsFactRepository>();
    })
    .CreateApplication();
  • Добавление дополнительной конфигурации Serilog, если используется данный модуль с помощью метода .AddAdditionalSerilogConfiguration().
var app = new WebApplicationFacade(modules)
    .AddAdditionalSerilogConfiguration((builder, serviceProvider, configuration) =>
    {
        configuration.Filter.ByExcluding(Matching.WithProperty<string>("RequestPath", path =>
            "/health".Equals(path, StringComparison.OrdinalIgnoreCase)));
    })
    .CreateApplication();
  • Инициализация базового конфига appsettings.json с помощью метода .InitBaseConfig(). При использовании данного метода, в конфиг добавятся секции моделей настроек со значениями по умолчанию, если этой секции не существует. У моделей настроек должен быть атрибут <see cref="AutoRegisterConfigSettingsAttribute"/>.

По умолчанию используется путь до appsetting.json, но если Ваша конфигурация находится в другом файле, вы можете изменить путь, передав его в параметр метода. Путь должен быть либо абсолютным, либо относительным от исполняемого файла (*{your-project-path}\bin\Debug\net8.0*).

ВАЖНО! Не используйте этот метод при разворачивании приложения внутри Docker. Предполагается, что сначала поучаются файлы, настраиваются и после уже контейнер разворачивается в Docker, а там не требуется использование этого метода, т.к. все уже должно быть настроено.

var app = new WebApplicationFacade(modules)
    .InitBaseConfig()
    .CreateApplication();
/// <summary>
/// Модель настроек для Cats Facts Api.
/// </summary>
[AutoRegisterConfigSettings]
public class CatsFactsApiSettings
{
    /// <summary>
    /// Endpoint для Health Check.
    /// </summary>
    public required string HealthCheckEndpoint { get; set; }

    /// <summary>
    /// Endpoint для получения информации.
    /// </summary>
    public required string Endpoint { get; set; }

    /// <summary>
    /// Endpoint для получения страницы.
    /// </summary>
    public required string PageEndpoint { get; set; }

    /// <summary>
    /// Индекс максимальной страницы (для генерации списка случайных фактов).
    /// </summary>
    public required int MaxPage { get; set; }

    /// <summary>
    /// Максимальный лимит страницы (для генерации списка случайных фактов).
    /// </summary>
    public required int MaxLimit { get; set; }
}
"CatsFactsApiSettings": {
  "Endpoint": "https://catfact.ninja/fact",
  "HealthCheckEndpoint": "https://catfact.ninja",
  "PageEndpoint": "https://catfact.ninja/facts",
  "MaxPage": 20,
  "MaxLimit": 10
}
  • Инициализация стандартных файлов docker-compose с помощью метода .InitBaseDockerComposeFiles().

Данный метод инициализирует файлы:

  • develop.env - файл конфигурации для Docker. Инициализируется с помощью конфига appsettings.json. Метод переносит все json объекты из файла appsettings.json и записывает их в виде конфигурации, воспринимаемой Docker.
  • docker-compose.yml - файл конфигурации docker-compose. Добавляет шаблонные блоки для инициализации нужных сервисов в docker-compose.

Созданные файлы, после запуска приложения сохраняются рядом с исполняемым файлом: {your-project-path}\bin\Debug\net8.0\DockerTemplates. После запуска приложения, можно скопировать сформированные файлы в удобное место.

var dockerComposeModules = new List<DockerComposeFileModules>
{
    DockerComposeFileModules.Server,
    DockerComposeFileModules.PostgreSql,
    DockerComposeFileModules.Adminer
};

var app = new WebApplicationFacade(modules)
    .InitBaseConfig()
    .InitBaseDockerComposeFiles(dockerComposeModules)
    .CreateApplication();
/// <summary>
/// Модули файлов docker-compose.
/// </summary>
public enum DockerComposeFileModules
{
    /// <summary>
    /// Модуль сервера (само приложение ASP.NET API).
    /// </summary>
    Server = 0,
    
    /// <summary>
    /// Модуль клиента (если для приложения будет создано frontend-приложение).
    /// </summary>
    Client = 1,
    
    /// <summary>
    /// Модуль Adminer для обращения к базам данных внутри docker-compose.
    /// </summary>
    Adminer = 2,
    
    /// <summary>
    /// Модуль базы данных MongoDB.
    /// </summary>
    MongoDb = 3,
    
    /// <summary>
    /// Модуль MongoExpress для обращения к MongoDb внутри docker-compose.
    /// </summary>
    MongoExpress = 4,
    
    /// <summary>
    /// Модуль базы данных ClickHouse.
    /// </summary>
    ClickHouse = 5,
    
    /// <summary>
    /// Модуль базы данных MySql.
    /// </summary>
    MySql = 6,
    
    /// <summary>
    /// Модуль базы данных Redis.
    /// </summary>
    Redis = 7,
    
    /// <summary>
    /// Модуль базы данных Elasticsearch.
    /// </summary>
    Elasticsearch = 8,
    
    /// <summary>
    /// Модуль Kibana для обращения к Elasticsearch внутри docker-compose.
    /// </summary>
    Kibana = 9,
    
    /// <summary>
    /// Модуль базы данных Cassandra.
    /// </summary>
    Cassandra = 10,
    
    /// <summary>
    /// Модуль брокера распределенных сообщений RabbitMq.
    /// </summary>
    RabbitMq = 11,
    
    /// <summary>
    /// Модуль системы мониторинга Prometheus.
    /// </summary>
    Prometheus = 12,
    
    /// <summary>
    /// Модуль системы мониторинга Grafana.
    /// </summary>
    Grafana = 13,
    
    /// <summary>
    /// Модуль веб-сервера Nginx.
    /// </summary>
    Nginx = 14,
    
    /// <summary>
    /// Модуль базы данных PostgreSql.
    /// </summary>
    PostgreSql = 15
}
# Путь до .env файла относительно docker-compose (используется для прописывания .env файлов в сервисы docker-compose).
ENV_FILE=develop.env

# Временная зона приложений.
TIME_ZONE=Europe/Moscow

FactsMicroserviceDbContextSettings__ConnectionString=Host=localhost:5432; Database=microservice; Username=postgres; Password=postgres
FactsMicroserviceDbContextSettings__Schema=FactsMicroservice
FactsMicroserviceDbContextSettings__MigrationsTableName=__EFMigrationsHistory
FactsMicroserviceDbContextSettings__MigrationsSchema=FactsMicroservice

CatsFactsApiSettings__Endpoint=https://catfact.ninja/fact
CatsFactsApiSettings__HealthCheckEndpoint=https://catfact.ninja
CatsFactsApiSettings__PageEndpoint=https://catfact.ninja/facts
CatsFactsApiSettings__MaxPage=20
CatsFactsApiSettings__MaxLimit=10

HealthChecksSettings__Endpoint=/health
HealthChecksSettings__UIEnable=True
HealthChecksSettings__EndpointFullUrl=https://localhost:7071/health
HealthChecksSettings__UIEvaluationTimeInSeconds=15
HealthChecksSettings__UIApiMaxActiveRequests=1

Serilog__Using__0=Serilog.Sinks.Console
Serilog__Using__1=Serilog.Sinks.PostgreSQL.Alternative
Serilog__MinimumLevel__Default=Information
Serilog__MinimumLevel__Override__Microsoft=Warning
Serilog__MinimumLevel__Override__System=Warning
Serilog__MinimumLevel__Override__HealthChecks=Warning
Serilog__MinimumLevel__Override__AspNetCore.HealthChecks.UI=Warning
Serilog__MinimumLevel__Override__AspNetCore.HealthChecks.UI.Client=Warning
Serilog__MinimumLevel__Override__AspNetCore.HealthChecks.UI.InMemory.Storage=Warning
Serilog__WriteTo__0__Name=Console
Serilog__WriteTo__0__OutputTemplate=[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}
Serilog__WriteTo__0__Args=null
Serilog__WriteTo__1__Name=PostgreSQL
Serilog__WriteTo__1__OutputTemplate=null
Serilog__WriteTo__1__Args__connectionString=Host=localhost:5432; Database=microservice; Username=postgres; Password=postgres
Serilog__WriteTo__1__Args__schemaName=FactsMicroservice
Serilog__WriteTo__1__Args__tableName=ServerLogs
Serilog__WriteTo__1__Args__needAutoCreateTable=True
Serilog__Properties__ApplicationName=FactsMicroservice
version: "3.9"

networks:
  app-network:

services:

  server:
    build:
      # Можно либо обратиться к конкретному image, либо к Dockerfile.
      #image:
      #context: 
      #dockerfile: 
    container_name: server
    environment:
      TZ: ${TIME_ZONE}
    ports:
      - "8000:8000"
    env_file: ${ENV_FILE}
    networks:
      - app-network

  adminer:
    image: adminer:latest
    container_name: adminer
    ports:
      - "8002:8002"
    environment:
      TZ: ${TIME_ZONE}
    networks:
      - app-network

  postgres:
    image: postgres:latest
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_DB: postgres
      TZ: ${TIME_ZONE}
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
      interval: 5s
      timeout: 5s
      retries: 5
    env_file: ${ENV_FILE}
    networks:
      - app-network

Основные модули для работы

  • enum WebApplicationModules
  • class WebApplicationFacade
  • enum DockerComposeFileModules

Примеры использования

public class Program
{
    public static void Main(string[] args)
    {
        var modules = new List<WebApplicationModules>
        {
            WebApplicationModules.Database,
            WebApplicationModules.Settings,
            WebApplicationModules.Services,
            WebApplicationModules.Serilog,
            WebApplicationModules.HealthChecks
        };

        var dockerComposeModules = new List<DockerComposeFileModules>
        {
            DockerComposeFileModules.Server,
            DockerComposeFileModules.PostgreSql,
            DockerComposeFileModules.Adminer
        };

        var app = new WebApplicationFacade(modules)
            .InitBaseConfig()
            .InitBaseDockerComposeFiles(dockerComposeModules)
            .AddAdditionalModules(builder =>
            {
                builder.Services.AddGrpc();
                builder.Services.AddScoped<ICatsFactRepository, CatsFactRepository>();
            })
            .CreateApplication();

        app.MapGrpcServices();

        app.Run();
    }
}
/// <summary>
/// Модель настроек контекста БД.
/// </summary>
[AutoRegisterConfigSettings]
public class FactsMicroserviceDbContextSettings : DbContextSettings
{
}
/// <summary>
/// Health Check для <see cref="ICatsFactsApiClient"/>.
/// </summary>
[AutoRegisterHealthCheck]
public class CatsFactsApiHealthCheck : IHealthCheck
{
    /// <summary>
    /// Клиент.
    /// </summary>
    private readonly ICatsFactsApiClient _catsFactsApiClient;

    public CatsFactsApiHealthCheck(
        ICatsFactsApiClient catsFactsApiClient)
    {
        _catsFactsApiClient = catsFactsApiClient;
    }

    /// <summary>
    /// Асинхронный метод проверки.
    /// </summary>
    /// <param name="context">Контекст проверки работоспособности.</param>
    /// <param name="cancellationToken">Токен отмены.</param>
    /// <returns>Результат проверки работоспособности.</returns>
    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    { 
        if (await _catsFactsApiClient.Ping())
        {
            return HealthCheckResult.Healthy();
        }
        else
        {
            return HealthCheckResult.Unhealthy();
        }
    }
}
/// <summary>
/// Сервис для взаимодействия с Cats Facts Api.
/// </summary>
[AutoRegisterService(ServiceLifetime.Transient, typeof(ICatsFactsApiClient))]
public class CatsFactsApiClient : ICatsFactsApiClient
{
    // Implementation
}

Особенности использования

  • Метод .InitBaseConfig() инициализирует Json Configuration в момент запуска приложения. Так что, если вы хотите, чтобы Ваша конфигурация была автоматически проинициализирована, нужно: 1) Запустить приложение. 2) Выключить его. 3) Заполнить конфигурацию, после инициализации. 4) Запустить приложение снова.
  • Метод .InitBaseDockerComposeFiles() создает файлы при каждом запуске приложения, так что после первого использования стоит вынести эти файлы из {your-project-path}\bin\Debug\net8.0\DockerTemplates в то место, где они не могут быть изменены автоматически.
  • Некоторые модули имеют зависимости друг от друга и если, к примеру, вы добавите WebApplicationModule.EFMigrations и не добавите модуль WebApplicationModule.SqlDatabase и WebApplicationModule.Settings, они будут добавлены автоматически (внутренняя логика библиотеки, для того, чтобы избежать возможные ошибки).
  • Модуль WebApplicationModules.Swagger работает только при сборке в Debug.
  • Модули сортируются в WebApplicationFacade, поэтому порядок их добавления не важен.
  • Логика добавления модулей в IServiceCollection скрыта за рамками WebApplicationFacade и ее нельзя изменить снаружи.

Зависимости

AspNetCoreMicroserviceInitializer.TradingDesk

Эта библиотека содержит инвентарь для AspNetCoreMicroserviceInitializer.Registrations, а именно:

  • Атрибуты, описанные в начале документации.
  • Enums, описанные в начале документации и в разделе "Что делает эта библиотека" у AspNetCoreMicroserviceInitializer.Registrations.
  • Exceptions - AttributeException, которое выбрасывается в случае ошибки в каком-либо атрибуте.
  • HangfireFilters - AllAuthorizationFilter (фильтр для автоматической авторизации всех запросов, при запросе дашборда Hangfire. Заполняется в конфиге appsettings.json).
  "HangfireDashboardSettings": {
    "EnableCustomAuthorization": true,
    "FilterName": "AllAuthorizationFilter"
  }
/// <summary>
/// Фильтр для автоматической авторизации всех запросов, при запросе дашборда <see cref="Hangfire"/>.
/// </summary>
public class AllAuthorizationFilter : IDashboardAuthorizationFilter
{
    /// <summary>
    /// Метод авторизации.
    /// </summary>
    /// <param name="context">Контекст дашборда.</param>
    /// <returns><see langword="true"/> для всех случаев.</returns>
    public bool Authorize([NotNull] DashboardContext context) => true;
}
  • Helpers - различные помощники для работы с модулями. Среди них: DockerComposeFilesHelper, AssemblyHelper, JsonHelper.
  • Interfaces - IHangfireBackgroundTask, IMigrator (используется только для внутренней реализации мигратора), IServiceImplementationFactory{TService} - интерфейс добавления фабричной функции для создания экземпляра сервиса внутри Dependency Injection контейнера.
[AutoRegisterHangfireTask(typeof(DateTimeTaskSettings))]
public class DateTimeTask : IHangfireBackgroundTask
{
    private readonly DateTimeService _dateTimeService;
    private readonly ILogger<DateTimeTask> _logger;

    public DateTimeTask(
        DateTimeService dateTimeService,
        ILogger<DateTimeTask> logger)
    {
        _dateTimeService = dateTimeService;
        _logger = logger;
    }

    public async Task ExecuteAsync()
    {
        var currentDateTime = await _dateTimeService.GetDateTimeWithMessageAsync();

        _logger.LogInformation(currentDateTime);
    }
}
[AutoRegisterService(ServiceLifetime.Singleton, typeof(IRandomWordService))]
public class RandomWordService : IRandomWordService, IServiceImplementationFactory<RandomWordService>
{
    ...
    /// <summary>
    /// A factory function,which is added using <see cref="IServiceImplementationFactory{TService}"/> that will be used when registering the service and return <see cref="RandowShortWordService"/>.
    /// </summary>
    /// <remarks>
    /// For a test, you can comment out this line and remove the interface from the class. And you will be able to observe the generation of random words of more than 5 characters.
    /// </remarks>
    public Func<IServiceProvider, RandomWordService> ImplementationFactory => (sp) => new RandowShortWordService();
    ...
}
public class RandowShortWordService : RandomWordService
{
    protected override int MaxWordLength => 5;

    public override string GetRandomWord()
    {
        return base.GetRandomWord();
    }
}
  • Migrations - классы и HostedService для модуля миграций.
  • Settings - абстрактные классы настроек (DbContextSettingsBase, DbSettingsBase, HangfireTaskSettingsBase, MongoSettingsBase, RedisSettingsBase) для наследования и удобного использования и определения настроек, а также модели настроек для модулей (содержат четкие настройки какого-либо модуля). Абстрактные настройки можно расширить по усмотрению, конкретные настройки нужно будет только заполнить в файле appsettings.json.
/// <summary>
/// Модель настроек Health Checks из конфига appsettings.
/// </summary>
[AutoRegisterConfigSettings]
[ConfigSettingsModule(WebApplicationModules.HealthChecks)]
public class HealthChecksSettings
{
    /// <summary>
    /// Endpoint Health Checks.
    /// 
    /// Пример: /health.
    /// </summary>
    public required string Endpoint { get; init; } = "/health";

    /// <summary>
    /// Включить UI для отслеживание Health Checks.
    /// 
    /// Пример: true/false.
    /// </summary>
    public bool UIEnable { get; init; } = true;

    /// <summary>
    /// Полный Url Health Check endpoint'а для запросов из Health Checks UI.
    /// 
    /// Пример: http://mydomain:80/health.
    /// </summary>
    public string? EndpointFullUrl { get; init; } = "http://localhost:8000/health";

    /// <summary>
    /// Частота опроса в секундах Health Checks для UI.
    /// 
    /// Пример: 10.
    /// </summary>
    public int? UIEvaluationTimeInSeconds { get; init; } = 5;

    /// <summary>
    /// Максимальное кол-во активных (одновременных) запросов на UI Health Checks Api.
    /// 
    /// Пример: 2.
    /// </summary>
    public int? UIApiMaxActiveRequests { get; init; } = 2;
}
/// <summary>
/// Настройки мигратора.
/// </summary>
[AutoRegisterConfigSettings]
[ConfigSettingsModule(WebApplicationModules.EFMigrations)]
public class MigratorSettings
{
    /// <summary>
    /// Флаг, отвечающий за то, нужно ли выключить приложение после выполнения миграций.
    /// </summary>
    public bool IsStopApplicationAfterApplyMigrations { get; init; } = false;
}
/// <summary>
/// Настройки Cors.
/// </summary>
[AutoRegisterConfigSettings]
[ConfigSettingsModule(WebApplicationModules.Cors)]
public class CorsSettings
{
    /// <summary>
    /// Флаг включения Cors с именованной политикой.
    ///
    /// Пример: true/false.
    /// </summary>
    public bool EnableCors { get; init; } = true;

    /// <summary>
    /// Наименование политики Cors.
    ///
    /// Пример: AllowAccessFrontendQueries.
    /// </summary>
    public string? PolicyName { get; init; } = "AllowAccessFrontendQueries";

    /// <summary>
    /// Список доменов для политики Cors.
    ///
    /// Пример: http://localhost:8082.
    /// </summary>
    public string[ ]? AllowedOrigins { get; init; }
}

Что делает эта библиотека?

Данная библиотека не имеет функциональных особенностей, а просто содержит инвентарь для работы основной библиотеки: AspNetCoreMicroserviceInitializer.Registrations.

Основные модули для работы

Перечислены в разделе "Что делает эта библиотека?"

Примеры использования

Перечислены в разделе "Что делает эта библиотека?"

Особенности использования

  • Абстрактные настройки можно расширить по усмотрению, конкретные настройки нужно будет только заполнить в файле appsettings.json.
  • Атрибуты необходимо использовать в соответствии с добавленными модулями. Одно без другого работать не будет.

Зависимости

AspNetCoreMicroserviceInitializer.Database

Эта библиотека содержит инвентарь для AspNetCoreMicroserviceInitializer.Registrations, который нужен для взаимодействия с базами данных. В ней описаны базовые репозитории, от которых можно наследоваться и использовать методы базового репозитория в репозиториях своих проектов.

Что делает эта библиотека?

  • Содержит интерфейсы сущностей.
public interface IEntity
{
    /// <summary>
    /// Дата создания сущности.
    /// </summary>
    DateTime CreationDate { get; set; }

    /// <summary>
    /// Дата последнего обновления сущности.
    /// </summary>
    DateTime LastUpdateDate { get; set; }

    /// <summary>
    /// Флаг показывающий, удалена ли сущность.
    /// </summary>
    bool IsDeleted { get; set; }
}
/// <summary>
/// Интерфейс базовой Sql сущности.
/// </summary>
/// <typeparam name="TId">Тип Id.</typeparam>
public interface ISqlEntity<TId> : IEntity
    where TId : struct
{
    /// <summary>
    /// Идентификатор.
    /// </summary>
    TId Id { get; set; }
}
/// <summary>
/// Интерфейс базовой сущности Redis.
/// </summary>
public interface IRedisEntity
{
    /// <summary>
    /// Ключ.
    /// </summary>
    string Key { get; set; }

    /// <summary>
    /// Значение в формате строки или Json.
    /// </summary>
    string Value { get; set; }
}
/// <summary>
/// Интерфейс базовой MongoDb сущности.
/// </summary>
public interface IMongoEntity : IEntity
{
    /// <summary>
    /// Идентификатор.
    /// </summary>
    [BsonId]
    ObjectId Id { get; set; }
}
  • Содержит интерфейсы репозиториев.
/// <summary>
/// Интерфейс репозитория для чтения данных из Redis.
/// </summary>
/// <typeparam name="TEntity">Тип сущности.</typeparam>
public interface IRedisReadRepository<TEntity>
    where TEntity : IRedisEntity
{
    /// <summary>
    /// Получить записи по ключам.
    /// </summary>
    /// <param name="keys">Массив ключей типа <see cref="IEnumerable{string}"/>.</param>
    /// <returns>Коллекцию типа <see cref="IEnumerable{TEntity}"/>.</returns>
    Task<IEnumerable<TEntity>> GetByKeysAsync(
        IEnumerable<string> keys,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Получить запись по ключу.
    /// </summary>
    /// <param name="key">Ключ.</param>
    /// <returns>Модель типа <see cref="TEntity"/>, <see langword="null"/> если не найдена.</returns>
    Task<TEntity?> GetByKeyAsync(
        string key,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Проверить существование элемента.
    /// </summary>
    /// <param name="key">Ключ.</param>
    /// <returns><see langword="true"/> если элемент существует, <see langword="false"/> если элемент не существует.</returns>
    Task<bool> IsExistsAsync(
        string key,
        CancellationToken cancellationToken = default);
}
/// <summary>
/// Интерфейс репозитория записи данных в MongoDb.
/// </summary>
/// <typeparam name="TEntity">Тип сущности.</typeparam>
public interface IMongoWriteRepository<TEntity>
    where TEntity : IMongoEntity
{
    /// <summary>
    /// Метод вставки записи в таблицу.
    /// </summary>
    /// <param name="entity">Сущность типа <see cref="TEntity"/>.</param>
    Task InsertAsync(
        TEntity entity,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Метод вставки нескольких записей в таблицу.
    /// </summary>
    /// <param name="entities">Массив сущностей типа <see cref="IEnumerable{TEntity}"/>.</param>
    Task InsertBatchAsync(
        IEnumerable<TEntity> entities,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Обновить запись в таблице.
    /// </summary>
    /// <param name="entity">Сущность типа <see cref="TEntity"/>.</param>
    Task UpdateAsync(
        TEntity entity,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Метод обновления нескольких записей в таблице.
    /// </summary>
    /// <param name="entities">Массив значений типа <see cref="IEnumerable{TEntity}"/>.</param>
    Task UpdateBatchAsync(
        IEnumerable<TEntity> entities,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Метод удаления записи по идентификатору.
    /// </summary>
    /// <param name="id">Идентификатор.</param>
    Task DeleteByIdAsync(
        ObjectId id,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Метод удаления записей по их идентификаторам.
    /// </summary>
    /// <param name="ids">Массив идентификаторов типа <see cref="IEnumerable{ObjectId}"/>.</param>
    /// <returns>Количество успешно удаленных записей.</returns>
    Task DeleteByIdsAsync(
        IEnumerable<ObjectId> ids,
        CancellationToken cancellationToken = default);
}
/// <summary>
/// Базовый интерфейс репозитория MongoDb.
/// </summary>
public interface IMongoRepository<TEntity> :
    IMongoReadRepository<TEntity>,
    IMongoWriteRepository<TEntity>
    where TEntity : IMongoEntity
{
}
/// <summary>
/// Интерфейс базового репозитория Redis.
/// </summary>
public interface IRedisRepository<TEntity> : 
    IRedisReadRepository<TEntity>, 
    IRedisWriteRepository<TEntity>
    where TEntity : IRedisEntity
{
}
/// <summary>
/// Интерфейс базового репозитория.
/// </summary>
/// <typeparam name="TEntity">Тип сущности.</typeparam>
public interface ISqlRepository<TEntity> :
    ISqlReadRepository<TEntity>,
    ISqlWriteRepository<TEntity>
    where TEntity : ISqlEntity<long>
{
}
  • Содержит базовые абстрактные классы репозиториев.
/// <summary>
/// Абстрактный класс базового MongoDb репозитория.
/// </summary>
public abstract class MongoRepositoryBase<TEntity> : IMongoRepository<TEntity>
    where TEntity : class, IMongoEntity
{
    /// <summary>
    /// Клиент для рабооты с MongoDb.
    /// </summary>
    protected readonly IMongoClient _client;

    /// <summary>
    /// База данных для рабооты с MongoDb.
    /// </summary>
    protected readonly IMongoDatabase _database;

    /// <summary>
    /// Коллекция MongoDb.
    /// </summary>
    protected readonly IMongoCollection<TEntity> _collection;

    /// <summary>
    /// Конструктор <see cref="MongoRepositoryBase{TEntity}"/>.
    /// </summary>
    /// <param name="clientFactory">Фабрика для получения клиента MongoDb.</param>
    /// <param name="connectionString">Строка подключения к базе данных.</param>
    /// <param name="databaseName">Имя базы данных.</param>
    protected MongoRepositoryBase(
        IMongoClientFactory clientFactory, 
        string connectionString,
        string databaseName)
    {
        _client = clientFactory.GetClientByConnectionString(connectionString);
        _database = _client.GetDatabase(databaseName);

        var collectionName = MongoCollectionNameResolver.GetCollectionName<TEntity>();
        _collection = _database.GetCollection<TEntity>(collectionName);
    }


    // Implementation
}
/// <summary>
/// Абстрактный класс базового Redis репозитория.
/// </summary>
public abstract class RedisRepositoryBase<TEntity> : IRedisRepository<TEntity>
    where TEntity : class, IRedisEntity, new()
{
    /// <summary>
    /// База данных.
    /// </summary>
    protected readonly IDatabase _database;

    /// <summary>
    /// Префикс ключа для унификации.
    /// </summary>
    protected readonly string _keyPrefix;

    /// <summary>
    /// Модель для подключения к БД Redis.
    /// </summary>
    protected readonly IConnectionMultiplexer _multiplexer;

    /// <summary>
    /// Конструктор репозитория.
    /// </summary>
    /// <param name="clientFactory">Фабрика для получения клиента Redis.</param>
    /// <param name="connectionString">Строка подключения.</param>
    /// <param name="keyPrefix">Префикс ключа для унификации.</param>
    protected RedisRepositoryBase(
        IRedisClientFactory clientFactory,
        string connectionString,
        string keyPrefix)
    {
        _multiplexer = clientFactory.GetClientByConnectionString(connectionString);
        _database = _multiplexer.GetDatabase();
        _keyPrefix = keyPrefix;
    }

    //Implementation
}
/// <summary>
/// Абстрактный класс базового Sql репозитория.
/// </summary>
public abstract class SqlRepositoryBase<TEntity> : ISqlRepository<TEntity>
    where TEntity : class, ISqlEntity<long>
{
    /// <summary>
    /// Контекст БД.
    /// </summary>
    protected readonly DbContext _dbContext;

    /// <summary>
    /// Конструктор <see cref="SqlRepositoryBase{TEntity}"/>.
    /// </summary>
    /// <param name="dbContext">Контекст БД.</param>
    protected SqlRepositoryBase(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    // Implementation
}
  • Содержит атрибуты для сущностей.
/// <summary>
/// Атрибут для указания имени коллекции в строчном формате.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class MongoCollectionAttribute : Attribute
{
    /// <summary>
    /// Имя коллекции.
    /// </summary>
    public string CollectionName { get; }

    /// <summary>
    /// Конструктор атрибута.
    /// </summary>
    /// <param name="collectionName">Имя коллекции.</param>
    public MongoCollectionAttribute(string collectionName)
    {
        CollectionName = collectionName;
    }
}
  • Содержит сервисы для внутреннего использования, но они с модификатором public: MongoClientWithConnectionString, RedisClientWithConnectionString, MongoClientFactory, RedisClientFactory.

Основные модули для работы

  • Entities interfaces.
  • Repositories interfaces.
  • Abstract repositories classes.
  • Attributes.

Примеры использования

/// <summary>
/// Внутренняя модель факта для сохранения в БД.
/// </summary>
public class CatsFact : IEntity<long>
{
    /// <summary>
    /// Идентификатор модели.
    /// </summary>
    public long Id { get; set; }

    /// <summary>
    /// Текст факта.
    /// </summary>
    public required string Text { get; set; }

    /// <summary>
    /// Дата создания модели.
    /// </summary>
    public DateTime CreationDate { get; set ; }

    /// <summary>
    /// Флаг, удалена ли модель.
    /// </summary>
    public bool IsDeleted { get; set; }
}
/// <summary>
/// Репозиторий для работы с <see cref="CatsFact"/>.
/// </summary>
[AutoRegisterRepository]
public class CatsFactRepository : 
    RepositoryBase<CatsFact>, 
    ICatsFactRepository
{
    /// <summary>
    /// Контекст БД.
    /// </summary>
    private readonly FactsMicroserviceDbContext _microserviceDbContext;

    public CatsFactRepository(FactsMicroserviceDbContext dbContext) : base(dbContext)
    {
        _microserviceDbContext = dbContext;
    }

    /// <summary>
    /// Метод удаления записи по идентификатору.
    /// </summary>
    /// <param name="id">Идентификатор записи.</param>
    public override async Task DeleteByIdAsync(long id)
    {
        var model = await GetByIdAsync(id);

        if (model is null)
        {
            throw new Exception("Couldn't get the model by ID to delete.");
        }

        model.IsDeleted = true;

        await UpdateAsync(model);
    }

    /// <summary>
    /// Метод получения количества записей в таблице.
    /// </summary>
    /// <returns>Количество записей в таблице.</returns>
    public async Task<int> GetCountAsync()
    {
        return await _microserviceDbContext.Facts.CountAsync();
    }
}
[MongoCollection("TestCollection")]
public class DummyMongoDbEntity : IMongoEntity
{
    [BsonId]
    public ObjectId Id { get; set; }
    public DateTime CreationDate { get; set; }
    public DateTime LastUpdateDate { get; set; }
    public bool IsDeleted { get; set; }
}
[AutoRegisterRepository(interfaceType: typeof(IMongoRepository<DummyMongoDbEntity>))]
public class DummyFirstMongoDbRepository : MongoRepositoryBase<DummyMongoDbEntity>
{
    public DummyFirstMongoDbRepository(IMongoClientFactory factory, IOptions<DummyFirstMongoSettings> settings) 
        : base(factory, settings.Value.ConnectionString, settings.Value.DatabaseName)
    {
    }
}
[AutoRegisterRepository]
public class DummyRedisSecondRepository : RedisRepositoryBase<DummyRedisEntity>
{
    public DummyRedisSecondRepository(IRedisClientFactory factory, IOptions<DummyRedisSecondSettings> settings) 
        : base(factory, settings.Value.ConnectionString, "test_repository_redis_second")
    {
    }
}
[AutoRegisterRepository(interfaceType: typeof(ISqlRepository<DummyModel>))]
public class DummyRepository : SqlRepositoryBase<DummyModel>
{
    public DummyRepository(DummyDbContext dbContext) : base(dbContext)
    {
    }
}
public class DatabaseController : ControllerBase
{
    private readonly ISqlRepository<DummyModel> _dummyRepository;
    
    // Implementation.
}

Особенности использования

  • Если при инициализации WebApplicationFacade Вы используете WebApplicationModules.MongoDatabase или WebApplicationModules.RedisDatabase, то сервисы IRedisClientFactory и IMongoClientFactory автоматически зарегистрируются в контейнере DI и Вам не нужно будет их как-то дополнительно регистрировать, достаточно будет просто передать в конструктор репозитория. Если же вы не используете WebApplicationFacade, то Вам самостоятельно нужно будет зарегистрировать эти сервисы в DI.
services.AddSingleton<IMongoClientWithConnectionString>(sp =>
{
    return new MongoClientWithConnectionString(mongoSettings.ConnectionString);
});

services.AddTransient<IMongoClientFactory, MongoClientFactory>();
services.AddSingleton<IRedisClientWithConnectionString>(sp =>
{
    return new RedisClientWithConnectionString(redisSettings.ConnectionString);
});

services.AddTransient<IRedisClientFactory, RedisClientFactory>();
  • Для автоматической регистрации IMongoClientFactory и IRedisClientFactory необходимо следовать инструкциям у WebApplicationModules на соответствующих модулях.
    /// <summary>
    /// Модуль базы данных MongoDb.
    /// 
    /// Для корректной работы модуля необходимо:
    /// 1. Создать модели репозиториев, унаследованных от MongoRepositoryBase.cs
    /// 2. Создать модели настроек для каждого репозитория. Модели необходимо унаследовать от <see cref="MongoSettingsBase"/> и присвоить им атрибут <see cref="AutoRegisterConfigSettingsAttribute"/>.
    /// 3. Создать автоматически или заполнить вручную модели настроек MongoDb в файле appsettings.json.
    /// </summary>
    MongoDatabase = 13,

    /// <summary>
    /// Модуль базы данных Redis.
    ///
    /// 1. Создать модели репозиториев, унаследованных от RedisRepositoryBase.cs
    /// 2. Создать модели настроек для каждого репозитория. Модели необходимо унаследовать от <see cref="RedisSettingsBase"/> и присвоить им атрибут <see cref="AutoRegisterConfigSettingsAttribute"/>.
    /// 3. Создать автоматически или заполнить вручную модели настроек Redis в файле appsettings.json.
    /// </summary>
    RedisDatabase = 14
  • Фабричный метод у репозиториев используется для тех случаев, когда в рамках одного микросервиса нужно подключаться к разным базам одного типа. К примеру к 2-ум MongoDB, которые расположены по разным Url'ам. В таком случае нужно создать соответствующее количество настроек,
    1. Унаследовать их от RedisSettingsBase или MongoSettingsBase
    2. Следовать инструкциям из прошлого пункта.
[AutoRegisterConfigSettings]
public class DummyFirstMongoSettings : MongoSettingsBase
{
}

[AutoRegisterConfigSettings]
public class DummySecondMongoSettings : MongoSettingsBase
{
}
var modulens = new List<WebApplicationModules>
{
    WebApplicationModules.MongoDatabase
};

var app = new WebApplicationFacade(modulens)
    .CreateApplication();

app.Run();
"DummyFirstMongoSettings": {
  "ConnectionString": "mongodb://localhost:27017",
  "DatabaseName": "TestDatabaseFirst"
},
"DummySecondMongoSettings": {
  "ConnectionString": "mongodb://localhost:27018",
  "DatabaseName": "TestDabaseSecond"
}
  • Коллекция в MongoDb будет называться также, как и класс сущности IMongoEntity, если на ней не используется атрибут MongoCollectionAttribute.

Зависимости