Skip to content

Регламент добавления новых модулей ​

1. Концепция модульного расширения платформы ​

Платформа «UrFU HeatBalance» спроектирована в строгом соответствии с принципом открытости/закрытости (Open-Closed Principle) архитектуры чистых микросервисов: ядро системы открыто для расширения новыми типами теплотехнических агрегатов без необходимости модификации кода существующих расчётных модулей.

Каждый теплотехнический агрегат (камерная, конвейерная, электрическая печь, сушильный барабан или проектируемый новый агрегат — например, туннельная сушилка TunnelKiln или шахтная печь ShaftKiln) функционирует как автономный микросервис со своим независимым жизненным циклом.


2. Пошаговая инструкция добавления нового агрегата ​

В качестве практического примера ниже рассмотрен регламент интеграции туннельной сушильной печи (TunnelKiln).

2.1. Шаг 1: Разработка математического ядра (HeatBalance.Math) ​

  1. В проекте HeatBalance.Math создайте модель входных данных TunnelKilnInput.cs:
csharp
namespace HeatBalance.Math;

public sealed class TunnelKilnInput
{
    public double WetMaterialMassFlowKgPerHour { get; init; }
    public double MoistureInMassFraction { get; init; }
    public double MoistureOutMassFraction { get; init; }
    public double MaterialTempInC { get; init; }
    public double MaterialTempOutC { get; init; }
    public double FuelLowerHeatingValueKJPerKg { get; init; }
    public double FuelMassFlowKgPerHour { get; init; }
    public double TunnelLengthMeters { get; init; }
    public double CarTrolleyMassKg { get; init; }
    public double HeatLossFraction { get; init; } = 0.12;
}
  1. В класс HeatBalanceCalculations.cs добавьте перегрузку статического метода Calculate:
csharp
public static HeatBalanceResult Calculate(TunnelKilnInput input)
{
    ArgumentNullException.ThrowIfNull(input);

    if (input.MoistureOutMassFraction >= input.MoistureInMassFraction)
    {
        throw new ArgumentException("Конечная влажность должна быть строго меньше начальной влажности материала.");
    }

    // Расчёт массы испарённой влаги
    double dryMassFlow = input.WetMaterialMassFlowKgPerHour * (1.0 - input.MoistureInMassFraction);
    double evaporatedMoistureKgPerHour = input.WetMaterialMassFlowKgPerHour - (dryMassFlow / (1.0 - input.MoistureOutMassFraction));

    // Подведённая теплота сгорания топлива
    double heatInput = input.FuelMassFlowKgPerHour * input.FuelLowerHeatingValueKJPerKg;

    // Полезно затраченная теплота на испарение и нагрев материала
    double usefulHeat = evaporatedMoistureKgPerHour * 2490.0 + dryMassFlow * 1.25 * (input.MaterialTempOutC - input.MaterialTempInC);

    // Потери тепла через ограждающие конструкции туннеля и с вагонетками
    double losses = heatInput * input.HeatLossFraction + input.CarTrolleyMassKg * 0.50 * (input.MaterialTempOutC - 20.0);

    // Невязка теплового баланса
    double unaccounted = heatInput - usefulHeat - losses;
    double efficiency = usefulHeat / heatInput;

    return new HeatBalanceResult
    {
        UnitType = DryerUnitType.TunnelKiln,
        TotalInputKJPerHour = heatInput,
        UsefulHeatKJPerHour = usefulHeat,
        LossesKJPerHour = losses,
        UnaccountedKJPerHour = unaccounted,
        Efficiency = efficiency
    };
}

2.2. Шаг 2: Создание модульных тестов (HeatBalance.Math.Tests) ​

В файле UnitTest1.cs создайте обязательный юнит-тест с проверкой сходимости баланса:

csharp
[Fact]
public void TunnelKiln_SampleCase_IsStable()
{
    var input = new TunnelKilnInput
    {
        WetMaterialMassFlowKgPerHour = 2000,
        MoistureInMassFraction = 0.25,
        MoistureOutMassFraction = 0.05,
        MaterialTempInC = 15,
        MaterialTempOutC = 95,
        FuelLowerHeatingValueKJPerKg = 41800,
        FuelMassFlowKgPerHour = 45,
        TunnelLengthMeters = 60,
        CarTrolleyMassKg = 800,
        HeatLossFraction = 0.12
    };

    var result = HeatBalanceCalculations.Calculate(input);

    Assert.Equal(DryerUnitType.TunnelKiln, result.UnitType);
    Assert.True(result.TotalInputKJPerHour > 0);
    Assert.True(result.UsefulHeatKJPerHour > 0);
    Assert.True(result.LossesKJPerHour > 0);
    Assert.True(result.Efficiency > 0.0 && result.Efficiency < 1.0);
    Assert.True(result.UnaccountedKJPerHour >= 0.0);
}

Убедитесь в успешном прохождении тестов через консоль:

bash
dotnet test --filter "FullyQualifiedName~TunnelKiln"

2.3. Шаг 3: Обновление контрактов системы (HeatBalance.Contracts) ​

  1. Откройте файл src/Shared/HeatBalance.Contracts/CalculationType.cs и дополните перечисление:
csharp
public enum CalculationType
{
    ConveyorKiln = 1,
    ChamberKiln = 2,
    ElectricKiln = 3,
    DrumDryer = 4,
    TunnelKiln = 5 // Новый тип агрегата
}

public static class CalculationTypeExtensions
{
    public static string ToRoutePrefix(this CalculationType type) => type switch
    {
        CalculationType.ConveyorKiln => "conveyor",
        CalculationType.ChamberKiln => "chamber",
        CalculationType.ElectricKiln => "electric",
        CalculationType.DrumDryer => "drum",
        CalculationType.TunnelKiln => "tunnel",
        _ => throw new ArgumentOutOfRangeException(nameof(type))
    };

    public static string ToDisplayNameRu(this CalculationType type) => type switch
    {
        CalculationType.ConveyorKiln => "Конвейерная сушильная печь",
        CalculationType.ChamberKiln => "Камерная сушильная печь",
        CalculationType.ElectricKiln => "Электрическая сушильная печь",
        CalculationType.DrumDryer => "Сушильный барабан",
        CalculationType.TunnelKiln => "Туннельная сушильная печь",
        _ => type.ToString()
    };
}
  1. В AuthConstants.cs зарегистрируйте политику авторизации AuthPolicies.TunnelCalculate = "tunnel:calculate".

2.4. Шаг 4: Разработка микросервиса (HeatBalance.TunnelKilnService) ​

  1. Создайте проект Web API в каталоге src/Services/HeatBalance.TunnelKilnService:
bash
cd src/Services
dotnet new webapi -n HeatBalance.TunnelKilnService
  1. Подключите зависимости в .csproj:
xml
<ItemGroup>
  <ProjectReference Include="..\..\Shared\HeatBalance.Contracts\HeatBalance.Contracts.csproj" />
  <ProjectReference Include="..\..\Shared\HeatBalance.ServiceDefaults\HeatBalance.ServiceDefaults.csproj" />
  <ProjectReference Include="..\..\HeatBalance.Math\HeatBalance.Math.csproj" />
</ItemGroup>
  1. Настройте файл Program.cs:
csharp
using HeatBalance.Contracts;
using HeatBalance.ServiceDefaults;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHeatBalanceApiDefaults();
builder.Services.AddHeatBalanceJwtAuth(builder.Configuration);

var app = builder.Build();
app.UseHeatBalanceApiDefaults();
app.MapGet("/", () => Results.Ok(new { service = "TunnelKiln", policy = "tunnel:calculate" }));
app.Run();
  1. Создайте контроллер Controllers/CalculateController.cs:
csharp
using HeatBalance.Math;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace HeatBalance.TunnelKilnService.Controllers;

[ApiController]
[Route("api/v1")]
[AllowAnonymous]
public sealed class CalculateController : ControllerBase
{
    [HttpPost("calculate")]
    public ActionResult<HeatBalanceResult> Calculate([FromBody] TunnelKilnInput input)
    {
        try
        {
            return HeatBalanceCalculations.Calculate(input);
        }
        catch (Exception ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }
}
  1. Создайте оптимизированный Dockerfile для сервиса:
dockerfile
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
WORKDIR /app
COPY . .
RUN dotnet restore src/Services/HeatBalance.TunnelKilnService/HeatBalance.TunnelKilnService.csproj
RUN dotnet publish src/Services/HeatBalance.TunnelKilnService/HeatBalance.TunnelKilnService.csproj -c Release -o /out --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS runtime
WORKDIR /app
COPY --from=build /out .
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "HeatBalance.TunnelKilnService.dll"]

2.5. Шаг 5: Маршрутизация в YARP Gateway ​

В файле src/Gateway/HeatBalance.Gateway/appsettings.json зарегистрируйте новый маршрут и кластер:

json
{
  "ReverseProxy": {
    "Routes": {
      "tunnel": {
        "ClusterId": "tunnel",
        "Match": { "Path": "/api/tunnel/{**catch-all}" },
        "Transforms": [ { "PathPattern": "/api/v1/{**catch-all}" } ]
      }
    },
    "Clusters": {
      "tunnel": {
        "Destinations": {
          "d1": { "Address": "http://tunnel:8080/" }
        }
      }
    }
  }
}

В dev-режиме локального запуска адрес назначения меняется на http://127.0.0.1:5105/.


2.6. Шаг 6: Интеграция в оркестрацию и CI/CD ​

  1. Docker Compose (docker-compose.yml):
yaml
  tunnel:
    image: heatbalance-tunnel:latest
    container_name: heatbalance-tunnel
    restart: unless-stopped
    networks:
      - heatbalance_network
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
  1. Конвейер сборки (Jenkinsfile): В стадию Docker Build Services добавьте команду:
groovy
sh "docker build -t heatbalance-tunnel:${DOCKER_TAG} -t heatbalance-tunnel:latest -f src/Services/HeatBalance.TunnelKilnService/Dockerfile ."
  1. Скрипт локального запуска (run-microservices.ps1): Добавьте сервис в массив $services:
powershell
@{ Name = "TunnelKiln"; Project = "src\Services\HeatBalance.TunnelKilnService\HeatBalance.TunnelKilnService.csproj"; Port = 5105 }

2.7. Шаг 7: Разработка клиентского интерфейса (React SPA) ​

  1. В каталоге frontend/src/pages/ создайте компонент формы ввода TunnelKilnPage.jsx с компонентами Ant Design (Form, InputNumber, Card, Button, Table).
  2. В файле frontend/src/App.jsx добавьте роут:
jsx
<Route path="/calculate/tunnel" element={<TunnelKilnPage />} />
  1. Реализуйте отправку расчётного запроса на шлюз:
javascript
const response = await axios.post('/api/tunnel/calculate', payload);
setCalculationResult(response.data);

3. Архитектурный чек-лист готовности модуля (Definition of Done) ​

Перед слиянием рабочей ветки с новым модулем в основную ветку main убедитесь в выполнении всех 10 обязательных критериев:

№Критерий готовностиМетод верификации
1Математическая модель полностью изолирована в HeatBalance.Math.Отсутствие внешних платформенных зависимостей в проекте библиотеки.
2Сходимость теплового баланса покрыта модульными тестами xUnit.dotnet test HeatBalance.Math.Tests выполняется без ошибок.
3Перечисление CalculationType обновлено в контрактах.Наличие строковых маппингов в CalculationTypeExtensions.
4Контроллер микросервиса обрабатывает физические исключения и возвращает HTTP 400.Проверка негативных сценариев через Swagger UI.
5Маршрут зарегистрирован в конфигурации YARP API Gateway.Успешный сквозной вызов POST /api/<type>/calculate.
6Сборка Dockerfile протестирована локально.docker build выполняется без предупреждений, образ протегирован.
7Контейнер добавлен в docker-compose.yml и подключён к heatbalance_network.docker compose config валидирует корректность синтаксиса YAML.
8Сборка нового сервиса включена в декларативный конвейер Jenkinsfile.Успешная тестовая сборка на сервере CI/CD.
9Экранная форма React содержит валидацию числовых диапазонов.Проверка ввода отрицательных чисел и граничных условий в браузере.
10Экспорт печатных протоколов в QuestPDF поддерживает новый тип агрегата.Успешная генерация файла .pdf со статьями нового теплового баланса.

Разработано в рамках проекта моделирования тепловых балансов и промышленного мониторинга