Реализация сервиса генерации данных о товарах на складе с кэшированием ответов через Redis, оркестрацией через .NET Aspire и Blazor WebAssembly клиентом.
| Технология | Назначение |
|---|---|
| .NET 8 | Серверная часть |
| ASP.NET Core Minimal API | HTTP-сервис |
| .NET Aspire | Оркестрация сервисов |
Redis + IDistributedCache |
Распределённое кэширование |
| Bogus | Генерация тестовых данных |
| Blazor WebAssembly | Клиентское приложение |
Endpoint: GET /api/warehouse-item?id={int}
При первом запросе генерирует товар и кэширует его. При повторном запросе с тем же id возвращает данные из кэша. Время жизни записи в кэше настраивается через CacheExpirationMinutes в appsettings.json (по умолчанию 10 минут).
Пример ответа:
{
"id": 42,
"productName": "Fantastic Metal Shoes",
"category": "Electronics",
"quantity": 317,
"pricePerUnit": 1234.56,
"weightPerUnit": 2.45,
"dimensions": "30х15х10 см",
"isFragile": false,
"lastDeliveryDate": "2024-11-03",
"nextDeliveryDate": "2025-06-18"
}| № | Поле | Тип | Правило генерации |
|---|---|---|---|
| 1 | id |
int |
Передаётся из запроса |
| 2 | productName |
string |
Commerce.ProductName() |
| 3 | category |
string |
Commerce.Categories(1)[0] |
| 4 | quantity |
int |
Случайное [0, 1000] |
| 5 | pricePerUnit |
decimal |
Случайное [1, 10000], 2 знака |
| 6 | weightPerUnit |
double |
Случайное [0.1, 500], 2 знака |
| 7 | dimensions |
string |
Формат **х**х** см |
| 8 | isFragile |
bool |
Случайный bool |
| 9 | lastDeliveryDate |
DateOnly |
Прошедшая дата ≤ сегодня |
| 10 | nextDeliveryDate |
DateOnly |
Дата ≥ lastDeliveryDate |
Реализация API-шлюза на основе Ocelot с кастомным алгоритмом балансировки нагрузки Weighted Random (взвешенный случайный выбор).
- Оркестрация реплик — в
AppHost.csнастроен запуск 5 реплик сервиса генерации (warehouseapp-api-0…warehouseapp-api-4) на портах 7250–7254. - API-шлюз на Ocelot — проект
Api.Gatewayпринимает клиентские запросы и маршрутизирует их к репликам через конфигурациюocelot.json. - Кастомный балансировщик
WeightedRandom— реализован алгоритм взвешенного случайного выбора реплики.
Каждой реплике присваивается целочисленный вес. При поступлении запроса вероятность выбора реплики пропорциональна её весу:
Веса задаются в Api.Gateway/appsettings.json в секции LoadBalancer:Weights — массив целых чисел, где индекс соответствует номеру реплики:
{
"LoadBalancer": {
"Weights": [ 35, 25, 20, 12, 8 ]
}
}Вариант: SNS + Minio. После генерации товар публикуется в SNS-топик в LocalStack, файловый сервис принимает уведомление по HTTP и кладёт JSON в бакет Minio.
- CloudFormation-шаблон
WarehouseApp.AppHost/CloudFormation/warehouse-template-sns.yamlс одним SNS-топикомwarehouse-topic. В AppHost применяется черезAddAWSCloudFormationTemplate. - В
AppHost.csподняты контейнерыwarehouse-localstack(порт 4566) иwarehouse-minio. Реплики API и файловый сервис получают AWS-конфигурацию черезWithReference(awsResources). - В
WarehouseApp.Apiпосле промаха кэшаWarehouseItemServiceвызываетSnsPublisherService.PublishAsync— сериализуетWarehouseItemи публикует в топик. ARN читается изAWS:Resources:SNSTopicArn(заполняется outputs CloudFormation). WarehouseApp.FileService:- на старте создаёт бакет (
S3MinioService.EnsureBucketExists) и подписывает свой HTTP-эндпоинт на топик (SnsSubscriptionService.SubscribeEndpoint); POST /api/sns— вебхук, подтверждаетSubscriptionConfirmationи сохраняетNotificationв Minio под ключомwarehouse_{id}.json;GET /api/s3иGET /api/s3/{key}— список ключей и чтение одного объекта.
- на старте создаёт бакет (
- Интеграционные тесты:
GatewayCall_ProducesFileInS3WithSameId— дёргаетGET /warehouse-item?id={id}у шлюза и поллитGET /api/s3/warehouse_{id}.jsonу файлового сервиса, сравнивая содержимое;MultipleGatewayCalls_ProduceDistinctFilesInS3— делает три запроса с разными id и проверяет, что все три ключа появились вGET /api/s3.