From 92f76a898cd1c4f4c99bf8bf4d2a7ce74f946bd7 Mon Sep 17 00:00:00 2001 From: tgontar Date: Sun, 8 Mar 2026 02:32:33 +0300 Subject: [PATCH 1/5] feat: add persistence docs --- Itmo.Dev.Platform.sln | 5 + docs/persistence.md | 210 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 docs/persistence.md diff --git a/Itmo.Dev.Platform.sln b/Itmo.Dev.Platform.sln index f3aa631..9ced147 100644 --- a/Itmo.Dev.Platform.sln +++ b/Itmo.Dev.Platform.sln @@ -114,6 +114,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Itmo.Dev.Platform.Testing.B EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Itmo.Dev.Platform.Testing.Behavioural.MessagePersistence", "src\Itmo.Dev.Platform.Testing.Behavioural.MessagePersistence\Itmo.Dev.Platform.Testing.Behavioural.MessagePersistence.csproj", "{769371D9-527B-4C23-B45A-04CFC0C653B6}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{18C776DA-A728-44D2-96B1-C79CD8312D9D}" + ProjectSection(SolutionItems) = preProject + docs\persistence.md = docs\persistence.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/docs/persistence.md b/docs/persistence.md new file mode 100644 index 0000000..62026f7 --- /dev/null +++ b/docs/persistence.md @@ -0,0 +1,210 @@ +## Itmo.Dev.Platform.Persistence + +Библиотека для работы с базами данных. Включает в себя абстракции для подключения к БД, выполнения запросов, управления транзакциями и миграциями. + +## Подключение + +Библиотека состоит из двух Nuget пакетов: `Itmo.Dev.Platform.Persistence.Abstractions` с абстракциями и +`Itmo.Dev.Platform.Persistence.Postgres` с реализациями для БД Postgres. + +Для регистрации в DI-контейнере необходимо вызвать метод `AddPlatformPersistence`. +Метод принимает делегат, в котором происходит конфигурация Persistence слоя. + +```csharp +collection.AddPlatformPersistence(persistence => persistence.UsePostgres(postgres => postgres + .WithConnectionOptions(static builder => builder + .BindConfiguration("Infrastructure:DataAccess:PostgresConfiguration")) + .WithMigrationsFrom(typeof(IAssemblyMarker).Assembly) + .WithDataSourcePlugin())); +``` + +У конфигуратора есть метод расширения `UsePostgres`, который настраивает подключение к PostgreSql. В метод так же передаётся +делегат, в котором можно параметризовать работу с базой с помощью методов: + +- `WithConnectionOptions` - настройка конфигурации подключения к БД. +- `WithMigrationsFrom` - указание сборки(-ок) с миграциями. Также можно использовать `WithMigrationsFromItems` для указания конкретных типов +с миграциями. +- `WithDataSourcePlugin` - подключение плагинов для `NpgsqlDataSource` (например, кастомный маппинг enum-ов). + +## Конфигурация + +Параметры подключения к БД задаются через `PostgresConnectionOptions`. + +### Схема конфигурации + +```json +{ + "Host": string, + "Port": int, + "Database": string, + "Username": string, + "Password": string, + "SslMode": string, + "Pooling": bool, + "MaximumPoolSize": int, + "EnableConnectionProviderLogging": bool +} +``` + +- **Host** + Адрес хоста PostgreSql. Обязательный параметр, не может быть пустым. +- **Port** + Порт PostgreSql. Обязательный параметр, не может быть пустым или равным 0. Допустимые значения: `1`-`65535`. +- **Database** + Название базы данных. Обязательный параметр, не может быть пустым. +- **Username** + Имя пользователя. Обязательный параметр, не может быть пустым. +- **Password** + Пароль. Обязательный параметр, не может быть пустым. +- **SslMode** + Режим SSL-соединения. Необязательный параметр, по умолчанию пустая строка. + Допустимые значения аналогичны Npgsql ([документация](https://www.npgsql.org/doc/api/Npgsql.SslMode.html)): `Disable`, `Allow`, `Prefer`, `Require`, `VerifyCA`, `VerifyFull`. +- **Pooling** + Использование пула соединений (connection pooling). Необязательный параметр, по умолчанию `true`. +- **MaximumPoolSize** + Максимальный размер пула соединений. Необязательный параметр, по умолчанию `10`, должен быть >= `1`. +- **EnableConnectionProviderLogging** + Включение логирования. Необязательный параметр, по умолчанию `false`. + +> Валидация значений происходит при старте сервиса. При невалидных значениях сервис не запустится. + +### Configuration example + +```json +{ + "Infrastructure": { + "DataAccess": { + "PostgresConfiguration": { + "Host": "localhost", + "Database": "test-db", + "Port": 6432, + "Username": "postgres", + "Password": "postgres", + "SslMode": "Prefer", + "Pooling": true, + "MaximumPoolSize": 10, + "EnableConnectionProviderLogging": false + } + } + } +} +``` + +## Подключение к БД и выполнение запросов + +### Получение соединения + +Для взаимодействия с БД используется `IPersistenceConnectionProvider`. +Провайдер зарегистрирован в DI как Scoped, поэтому сервисы использующие его должны быть зарегистрированы как Scoped или Transient. + +Чтобы получить объект соединения, необходимо вызвать метод провайдера `GetConnectionAsync`, который возвращает +`IPersistenceConnection`. + +```csharp +await using IPersistenceConnection connection = await _connectionProvider.GetConnectionAsync(cancellationToken); +``` + +### Создание и настройка команды + +Команда создается через метод `CreateCommand` на объекте соединения. Параметры добавляются в fluent стиле: + +```csharp +await using IPersistenceCommand command = connection.CreateCommand(sql) + .AddParameter("parameter_1", value1) + .AddParameter("parameter_2", value2); +``` + +Помимо вышеуказанного `AddParameter(string parameterName, T value)`, у `IPersistenceConnection` существуют другие методы и перегрузки для добавления параметров: + +- `AddParameter(DbParameter parameter)` - добавляет сырой `DbParameter`, фактически параметр должен быть типа `NpgsqlParameter`. +- `AddParameter(string parameterName, T value)` - добавляет именованный параметр произвольного типа. +- `AddParameter(string parameterName, IEnumerable values)` - добавляет именованный параметр-коллекцию, используется для параметров-списков. + В sql коде удобно подставлять в `= any(...)` или `in (...)`. +- `AddJsonParameter(string parameterName, T value, JsonSerializerSettings? serializerSettings = null)` - сериализует переданное значение в json и добавляет как параметр с типом `jsonb`. +- `AddNullableJsonParameter(string parameterName, T? value, JsonSerializerSettings? serializerSettings = null)` - аналогично `AddJsonParameter`, но поддерживает `null` значения. +- `AddJsonArrayParameter(string parameterName, IEnumerable values, JsonSerializerSettings? serializerSettings = null)` - сериализует каждый элемент коллекции в json и добавляет как параметр с типом `jsonb[]`. +- `AddJsonArrayParameter(string parameterName, IEnumerable values)` - добавляет коллекцию строк (уже сериализованных объектов) как параметр с типом `jsonb[]`. +- `AddMultiArrayStringParameter(string parameterName, IEnumerable> values)` - позволяет добавлять многомерные массивы в качестве параметров. + Каждый элемент коллекции 1-го уровня преобразуется в postgres-массив, затем итоговый массив добавляется как параметр с типом `text[]`. +- `AddMultiArrayStringParameter(string parameterName, IEnumerable> values)` - аналогично `AddMultiArrayStringParameter`, но для строковых значений коллекции. + +> При попытке добавить параметр с уже существующим названием будет выброшен `PlatformPersistencePostgresException`. + +### Выполнение запросов + +Чтение данных через реализовано через метод команды `ExecuteReaderAsync`, аналогично `NpgsqlCommand`: + +```csharp +await using DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken); + +while (await reader.ReadAsync(cancellationToken)) +{ + yield return new Model( + Id: reader.GetInt64("field_1"), + Name: reader.GetString("field_2")); +} +``` + +Выполнение не читающих запросов (DML) реализовано через метод `ExecuteNonQueryAsync`, так же аналогично `NpgsqlCommand`: + +```csharp +await command.ExecuteNonQueryAsync(cancellationToken); +``` + +## Транзакции + +Для управления транзакциями используется `IPersistenceTransactionProvider`, зарегистрированный в DI как Scoped. +Соответственно сервисы, использующие его, должны быть зарегистрированы как Scoped или Transient. + +Для открытия транзакции используется метод `BeginTransactionAsync` с указанием уровня изоляции. +Метод возвращает `IPersistenceTransaction` - объект транзакции, который после выполнения операций необходимо закоммитить +(вызвать метод `CommitAsync`). Все операции, выполненные до вызова этого метода, автоматически попадают в транзакцию. + +Если `CommitAsync` не был вызван, транзакция откатывается при `DisposeAsync`. + +```csharp +await using IPersistenceTransaction transaction = await _transactionProvider + .BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); + +// выполнение операций через репозитории + +await transaction.CommitAsync(cancellationToken); +``` + +Фактически реализация `IPersistenceTransaction` использует под капотом `TransactionScope` с указанием параметра scopeOption: `TransactionScopeOption.Required`, +тем самым представляя собой ту же ambient транзакцию. + +## Миграции + +Механизм миграций реализован на базе библиотеки FluentMigrator. +Для написания миграции необходимо наследовать абстрактный класс `SqlMigration`, в котором определены методы `GetUpSql` и `GetUpSql`. + +`GetUpSql` - sql-скрипт самой миграции, `GetDownSql` - скрипт отката + +```csharp +[Migration(1, "Initial")] +public class Initial : SqlMigration +{ + protected override string GetUpSql(IServiceProvider serviceProvider) + { + return """ + create table example + ( + id bigserial not null primary key, + name text not null + ); + """; + } + + protected override string GetDownSql(IServiceProvider serviceProvider) + { + return """ + drop table example; + """; + } +} +``` + +Миграции запускаются автоматически при старте приложения. Сборка с миграциями указывается при регистрации в DI, методе `WithMigrationsFrom`. + +// TODO: возможно что-то написать про WithMigrationsFromItems From 9ea15fb78daea7471a0d034b09b87a5ef4a62e03 Mon Sep 17 00:00:00 2001 From: tgontar Date: Mon, 9 Mar 2026 16:50:08 +0300 Subject: [PATCH 2/5] fix: docs fixes --- docs/persistence.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/persistence.md b/docs/persistence.md index 62026f7..ac535ea 100644 --- a/docs/persistence.md +++ b/docs/persistence.md @@ -13,7 +13,7 @@ ```csharp collection.AddPlatformPersistence(persistence => persistence.UsePostgres(postgres => postgres .WithConnectionOptions(static builder => builder - .BindConfiguration("Infrastructure:DataAccess:PostgresConfiguration")) + .BindConfiguration("Infrastructure:Persistence:Postgres")) .WithMigrationsFrom(typeof(IAssemblyMarker).Assembly) .WithDataSourcePlugin())); ``` @@ -23,8 +23,10 @@ collection.AddPlatformPersistence(persistence => persistence.UsePostgres(postgre - `WithConnectionOptions` - настройка конфигурации подключения к БД. - `WithMigrationsFrom` - указание сборки(-ок) с миграциями. Также можно использовать `WithMigrationsFromItems` для указания конкретных типов -с миграциями. -- `WithDataSourcePlugin` - подключение плагинов для `NpgsqlDataSource` (например, кастомный маппинг enum-ов). + с миграциями. +- `WithDataSourcePlugin` - подключение плагинов для `NpgsqlDataSource`. Используемые плагины + должны реализовывать `IPostgresDataSourcePlugin`, и фактически позволяют дополнительно настроить `NpgsqlDataSourceBuilder` + перед созданием data source. ## Конфигурация @@ -73,7 +75,7 @@ collection.AddPlatformPersistence(persistence => persistence.UsePostgres(postgre ```json { "Infrastructure": { - "DataAccess": { + "Persistence": { "PostgresConfiguration": { "Host": "localhost", "Database": "test-db", @@ -94,7 +96,7 @@ collection.AddPlatformPersistence(persistence => persistence.UsePostgres(postgre ### Получение соединения -Для взаимодействия с БД используется `IPersistenceConnectionProvider`. +Для получения соединений с БД используется `IPersistenceConnectionProvider`. с БД используется `IPersistenceConnectionProvider`. Провайдер зарегистрирован в DI как Scoped, поэтому сервисы использующие его должны быть зарегистрированы как Scoped или Transient. Чтобы получить объект соединения, необходимо вызвать метод провайдера `GetConnectionAsync`, который возвращает @@ -118,21 +120,23 @@ await using IPersistenceCommand command = connection.CreateCommand(sql) - `AddParameter(DbParameter parameter)` - добавляет сырой `DbParameter`, фактически параметр должен быть типа `NpgsqlParameter`. - `AddParameter(string parameterName, T value)` - добавляет именованный параметр произвольного типа. -- `AddParameter(string parameterName, IEnumerable values)` - добавляет именованный параметр-коллекцию, используется для параметров-списков. - В sql коде удобно подставлять в `= any(...)` или `in (...)`. +- `AddParameter(string parameterName, IEnumerable values)` - добавляет именованный параметр-список. + Несмотря на ограничение Npgsql в передаваемом типе коллекции для параметра (`List` или `T[]`), в данный метод можно передавать + любой тип, реализующий `IEnumerable`. В sql коде удобно подставлять в `= any(...)` или `in (...)`. - `AddJsonParameter(string parameterName, T value, JsonSerializerSettings? serializerSettings = null)` - сериализует переданное значение в json и добавляет как параметр с типом `jsonb`. - `AddNullableJsonParameter(string parameterName, T? value, JsonSerializerSettings? serializerSettings = null)` - аналогично `AddJsonParameter`, но поддерживает `null` значения. - `AddJsonArrayParameter(string parameterName, IEnumerable values, JsonSerializerSettings? serializerSettings = null)` - сериализует каждый элемент коллекции в json и добавляет как параметр с типом `jsonb[]`. - `AddJsonArrayParameter(string parameterName, IEnumerable values)` - добавляет коллекцию строк (уже сериализованных объектов) как параметр с типом `jsonb[]`. - `AddMultiArrayStringParameter(string parameterName, IEnumerable> values)` - позволяет добавлять многомерные массивы в качестве параметров. - Каждый элемент коллекции 1-го уровня преобразуется в postgres-массив, затем итоговый массив добавляется как параметр с типом `text[]`. + Каждый элемент коллекции 1-го уровня преобразуется в postgres-массив, затем итоговый список добавляется как параметр с типом `text[]`. + Для корректной работы в sql, необходимо явно привести каждый элемент списка к ожидаемому postgres-типу (например, `integer[]` или `bigint[]`). - `AddMultiArrayStringParameter(string parameterName, IEnumerable> values)` - аналогично `AddMultiArrayStringParameter`, но для строковых значений коллекции. > При попытке добавить параметр с уже существующим названием будет выброшен `PlatformPersistencePostgresException`. ### Выполнение запросов -Чтение данных через реализовано через метод команды `ExecuteReaderAsync`, аналогично `NpgsqlCommand`: +Чтение данных реализовано через метод команды `ExecuteReaderAsync`, аналогично `NpgsqlCommand`: ```csharp await using DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken); @@ -206,5 +210,3 @@ public class Initial : SqlMigration ``` Миграции запускаются автоматически при старте приложения. Сборка с миграциями указывается при регистрации в DI, методе `WithMigrationsFrom`. - -// TODO: возможно что-то написать про WithMigrationsFromItems From d14a6a4647a0485772866cb5d837d697e2b2ca4a Mon Sep 17 00:00:00 2001 From: tgontar Date: Mon, 9 Mar 2026 21:59:25 +0300 Subject: [PATCH 3/5] chore: add required extensions methods descriptions --- docs/persistence.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/persistence.md b/docs/persistence.md index ac535ea..f136909 100644 --- a/docs/persistence.md +++ b/docs/persistence.md @@ -7,7 +7,8 @@ Библиотека состоит из двух Nuget пакетов: `Itmo.Dev.Platform.Persistence.Abstractions` с абстракциями и `Itmo.Dev.Platform.Persistence.Postgres` с реализациями для БД Postgres. -Для регистрации в DI-контейнере необходимо вызвать метод `AddPlatformPersistence`. +Для регистрации в DI-контейнере необходимо вызвать метод `AddPlatformPersistence`, предварительно зарегистрировав саму +платформу (метод `AddPlatform`). Метод принимает делегат, в котором происходит конфигурация Persistence слоя. ```csharp @@ -66,7 +67,9 @@ collection.AddPlatformPersistence(persistence => persistence.UsePostgres(postgre - **MaximumPoolSize** Максимальный размер пула соединений. Необязательный параметр, по умолчанию `10`, должен быть >= `1`. - **EnableConnectionProviderLogging** - Включение логирования. Необязательный параметр, по умолчанию `false`. + Включение логирования. Необязательный параметр, по умолчанию `false`. + Для корректной работы необходимо также добавить платфморменный observability - в DI контейнере + вызвать метод `.AddPlatformObservability` > Валидация значений происходит при старте сервиса. При невалидных значениях сервис не запустится. From 1d2ef3d7fc4b11904c2cc15149aec3e26c772392 Mon Sep 17 00:00:00 2001 From: tgontar Date: Tue, 10 Mar 2026 09:37:18 +0300 Subject: [PATCH 4/5] chore: add docs references --- README.md | 35 +------------------ docs/persistence.md | 2 +- .../README.md | 4 ++- .../README.md | 4 ++- 4 files changed, 8 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index e0be97d..fbe2875 100644 --- a/README.md +++ b/README.md @@ -3,39 +3,6 @@ - [Itmo.Dev.Platform.Common](src/Itmo.Dev.Platform.Common/README.md) - [Itmo.Dev.Platform.YandexCloud](src/Itmo.Dev.Platform.YandexCloud/README.md) - [Itmo.Dev.Platform.Kafka](src/Itmo.Dev.Platform.Kafka/README.md) -- [Itmo.Dev.Platform.Postgres](src/Itmo.Dev.Platform.Postgres/README.md) +- [Itmo.Dev.Platform.Persistence](docs/persistence.md) - [Itmo.Dev.Platform.Logging](src/Itmo.Dev.Platform.Logging/README.md) - [Itmo.Dev.Platform.BackgroundTasks](src/Itmo.Dev.Platform.BackgroundTasks/README.md) - -## Package source installation - -1. Add `nuget.config` file next to your `*.sln` file (if one does not exists) -2. Prepare your GitHub personal access token with `packages:read` permission -3. Add reference to `itmo-is-dev` GitHub nuget repository - ```xml - - - - - - - - - - - - ``` -4. When prompted for authorization, use your github username as username, and generated PAT as password - -Alternatively, you can use CLI to add package source. Code below will add source into your global config. -If you want to add it into local config, you should run it with option `--configfile nuget.config` - -```shell -dotnet nuget add source --username YOUR_USERNAME --password YOUR_GITHUB_PAT --store-password-in-clear-text --name github "https://nuget.pkg.github.com/itmo-is-dev/index.json" -``` - -###

WARNING!

- -Adding source into local config, will result in credentials be written into local file \ -Be aware that it may lead to credentials leaking into VSC \ -You can store credentials separately from source definition, in global nuget.config \ No newline at end of file diff --git a/docs/persistence.md b/docs/persistence.md index f136909..2452c60 100644 --- a/docs/persistence.md +++ b/docs/persistence.md @@ -79,7 +79,7 @@ collection.AddPlatformPersistence(persistence => persistence.UsePostgres(postgre { "Infrastructure": { "Persistence": { - "PostgresConfiguration": { + "Postgres": { "Host": "localhost", "Database": "test-db", "Port": 6432, diff --git a/src/Itmo.Dev.Platform.Persistence.Abstractions/README.md b/src/Itmo.Dev.Platform.Persistence.Abstractions/README.md index ba94965..7a99f35 100644 --- a/src/Itmo.Dev.Platform.Persistence.Abstractions/README.md +++ b/src/Itmo.Dev.Platform.Persistence.Abstractions/README.md @@ -1 +1,3 @@ -# Itmo.Dev.Platform.Persistence.Abstractions \ No newline at end of file +# Itmo.Dev.Platform.Persistence.Abstractions + +Документация: [README.md](https://github.com/itmo-is-dev/platform/docs/persistence.md) \ No newline at end of file diff --git a/src/Itmo.Dev.Platform.Persistence.Postgres/README.md b/src/Itmo.Dev.Platform.Persistence.Postgres/README.md index 51dbf7e..9bb9f7d 100644 --- a/src/Itmo.Dev.Platform.Persistence.Postgres/README.md +++ b/src/Itmo.Dev.Platform.Persistence.Postgres/README.md @@ -1 +1,3 @@ -# Itmo.Dev.Platform.Persistence.Postgres \ No newline at end of file +# Itmo.Dev.Platform.Persistence.Postgres + +Документация: [README.md](https://github.com/itmo-is-dev/platform/docs/persistence.md) \ No newline at end of file From 5165f2dd34bcaaa3ef13878f628bb23359e6a62a Mon Sep 17 00:00:00 2001 From: ronimizy Date: Wed, 11 Mar 2026 22:09:48 +0300 Subject: [PATCH 5/5] fix: links --- src/Itmo.Dev.Platform.Persistence.Abstractions/README.md | 2 +- src/Itmo.Dev.Platform.Persistence.Postgres/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Itmo.Dev.Platform.Persistence.Abstractions/README.md b/src/Itmo.Dev.Platform.Persistence.Abstractions/README.md index 7a99f35..679ec46 100644 --- a/src/Itmo.Dev.Platform.Persistence.Abstractions/README.md +++ b/src/Itmo.Dev.Platform.Persistence.Abstractions/README.md @@ -1,3 +1,3 @@ # Itmo.Dev.Platform.Persistence.Abstractions -Документация: [README.md](https://github.com/itmo-is-dev/platform/docs/persistence.md) \ No newline at end of file +Документация: [README.md](https://github.com/itmo-is-dev/platform/blob/master/docs/persistence.md) \ No newline at end of file diff --git a/src/Itmo.Dev.Platform.Persistence.Postgres/README.md b/src/Itmo.Dev.Platform.Persistence.Postgres/README.md index 9bb9f7d..5353a37 100644 --- a/src/Itmo.Dev.Platform.Persistence.Postgres/README.md +++ b/src/Itmo.Dev.Platform.Persistence.Postgres/README.md @@ -1,3 +1,3 @@ # Itmo.Dev.Platform.Persistence.Postgres -Документация: [README.md](https://github.com/itmo-is-dev/platform/docs/persistence.md) \ No newline at end of file +Документация: [README.md](https://github.com/itmo-is-dev/platform/blob/master/docs/persistence.md) \ No newline at end of file