Conversation
…аб. роботы В решение добавлены проекты CreditApplication.AppHost (Aspire-хост), CreditApplication.Generator (API генерации кредитных заявок с кэшированием в Redis) и CreditApplication.ServiceDefaults (общие настройки сервисов: логирование, OpenTelemetry, resilience, health checks). Реализована модель заявки, генератор на Bogus, сервис с кэшированием, API-эндпоинт /credit-application.
There was a problem hiding this comment.
Pull request overview
This pull request implements Lab 1 for a student assignment, creating a microservices-based credit application generator with caching functionality. The solution demonstrates modern .NET 8 development practices including Bogus for data generation, Redis for distributed caching, Serilog for structured logging, OpenTelemetry for observability, and .NET Aspire for orchestration.
Changes:
- Implemented a credit application generator service using Bogus to create synthetic credit application data with Russian localization
- Added Redis-based distributed caching to improve performance and reduce redundant data generation
- Configured .NET Aspire orchestration to manage the generator service, Redis cache, and Blazor WebAssembly client
- Set up comprehensive logging and telemetry infrastructure using Serilog and OpenTelemetry
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Replaced generic lab instructions with project-specific documentation including architecture diagrams and screenshots |
| CreditApplication.ServiceDefaults/Extensions.cs | Implemented shared service configuration for logging, OpenTelemetry, health checks, and service discovery |
| CreditApplication.ServiceDefaults/CreditApplication.ServiceDefaults.csproj | Defined dependencies for Serilog, OpenTelemetry, and Aspire service discovery |
| CreditApplication.Generator/Services/CreditApplicationGenerator.cs | Core generator using Bogus to create credit applications with locale-specific data |
| CreditApplication.Generator/Services/CreditApplicationService.cs | Service layer implementing caching logic with IDistributedCache |
| CreditApplication.Generator/Models/CreditApplicationModel.cs | Data model for credit applications with XML documentation |
| CreditApplication.Generator/Program.cs | API endpoint configuration with validation, error handling, and CORS |
| CreditApplication.Generator/CreditApplication.Generator.csproj | Project dependencies including Bogus and Aspire Redis integration |
| CreditApplication.Generator/appsettings.json | Logging configuration for the generator service |
| CreditApplication.Generator/appsettings.Development.json | Development-specific logging configuration |
| CreditApplication.Generator/Properties/launchSettings.json | Launch profiles for local development |
| CreditApplication.AppHost/Program.cs | Aspire orchestration configuration for Redis and microservices |
| CreditApplication.AppHost/CreditApplication.AppHost.csproj | Aspire hosting dependencies |
| CreditApplication.AppHost/appsettings.json | AppHost logging configuration |
| CreditApplication.AppHost/appsettings.Development.json | Development-specific AppHost configuration |
| CreditApplication.AppHost/Properties/launchSettings.json | AppHost launch profiles with Aspire dashboard endpoints |
| CloudDevelopment.sln | Added three new projects to the solution |
| Client.Wasm/wwwroot/appsettings.json | Updated API endpoint URL to credit application service |
| Client.Wasm/Properties/launchSettings.json | Updated client port configuration |
| Client.Wasm/Components/StudentCard.razor | Updated student information and lab details |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| policy.AllowAnyOrigin() | ||
| .AllowAnyMethod() | ||
| .AllowAnyHeader(); |
There was a problem hiding this comment.
The CORS policy allows any origin, method, and header which is insecure for production environments. While this may be acceptable for a development/lab environment, consider restricting CORS to specific origins in production scenarios or documenting this as a development-only configuration.
| policy.AllowAnyOrigin() | |
| .AllowAnyMethod() | |
| .AllowAnyHeader(); | |
| if (builder.Environment.IsDevelopment()) | |
| { | |
| // In development, allow any origin/method/header for ease of testing. | |
| policy.AllowAnyOrigin() | |
| .AllowAnyMethod() | |
| .AllowAnyHeader(); | |
| } | |
| else | |
| { | |
| // In production, restrict CORS to specific trusted origins and limit methods/headers as needed. | |
| policy.WithOrigins("https://your-production-frontend.example") | |
| .WithMethods("GET") | |
| .WithHeaders("Content-Type", "Authorization"); | |
| } |
| { | ||
| _logger.LogInformation("Credit application {Id} found in cache", id); | ||
| var cachedApplication = JsonSerializer.Deserialize<CreditApplicationModel>(cachedData); | ||
| return cachedApplication!; |
There was a problem hiding this comment.
The deserialized object could be null if the cached JSON is invalid or malformed. The null-forgiving operator (!) suppresses this warning but doesn't handle the case where deserialization returns null. Consider adding explicit null checking and appropriate error handling, or at least returning a fallback value.
| return cachedApplication!; | |
| if (cachedApplication is not null) | |
| { | |
| return cachedApplication; | |
| } | |
| _logger.LogWarning( | |
| "Failed to deserialize cached credit application {Id}. Cached data will be ignored and a new application will be generated.", | |
| id); |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| .RuleFor(x => x.RequestedAmount, f => Math.Round(f.Random.Decimal(50_000m, 10_000_000m), 2)) | ||
| .RuleFor(x => x.TermInMonths, f => f.Random.Int(6, 360)) | ||
| .RuleFor(x => x.InterestRate, f => Math.Round(f.Random.Double(MinInterestRate, 35.0), 2)) | ||
| .RuleFor(x => x.ApplicationDate, f => GenerateApplicationDate(f)) |
There was a problem hiding this comment.
Есть PastDateOnly
.RuleFor(x => x.ApplicationDate, f => f.Date.PastDateOnly(2))| var applicationDateTime = app.ApplicationDate.ToDateTime(TimeOnly.MinValue); | ||
| var decisionDateTime = faker.Date.Between(applicationDateTime, DateTime.Today); | ||
| app.DecisionDate = DateOnly.FromDateTime(decisionDateTime); |
| { | ||
| _logger.LogInformation("Credit application {Id} found in cache", id); | ||
| var cachedApplication = JsonSerializer.Deserialize<CreditApplicationModel>(cachedData); | ||
| return cachedApplication!; |
There was a problem hiding this comment.
Согласен с копилотом, теоретически может быть ситуация, что в кэш запишется null и лучше проверять здесь, если не null, возвращаем, если null идем дальше и генерируем новый объект
| private readonly IDistributedCache _cache = cache; | ||
| private readonly ILogger<CreditApplicationService> _logger = logger; | ||
|
|
||
| private static readonly TimeSpan _cacheExpiration = TimeSpan.FromMinutes(5); |
| builder.Services.AddCors(options => | ||
| { | ||
| options.AddDefaultPolicy(policy => | ||
| { | ||
| policy.AllowAnyOrigin() | ||
| .AllowAnyMethod() | ||
| .AllowAnyHeader(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Здесь копилот тоже прав, но в принципе пока можно так оставить
| <PackageReference Include="Aspire.Hosting" Version="8.2.2" /> | ||
| <PackageReference Include="Aspire.Hosting.AppHost" Version="8.2.2" /> | ||
| <PackageReference Include="Aspire.Hosting.Redis" Version="8.2.2" /> |
There was a problem hiding this comment.
Лучше апнуть aspire до последней версии доступной на .net 8 - 9.5.2
потому что сейчас у меня не запускается на 8.2.2
вот как примерно будет выглядеть этот файл после:
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.2" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="9.5.2" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.5.2" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.5.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CreditApplication.Generator\CreditApplication.Generator.csproj" />
<ProjectReference Include="..\Client.Wasm\Client.Wasm.csproj" />
</ItemGroup>
</Project>| @@ -14,7 +14,7 @@ | |||
| "dotnetRunMessages": true, | |||
| "launchBrowser": true, | |||
There was a problem hiding this comment.
Возможно стоит поднастроить настройки запуска клиента, чтобы не открывался отдельно браузер с клиентом
|
|
||
| private static readonly string[] _terminalStatuses = ["Одобрена", "Отклонена"]; | ||
|
|
||
| private readonly ILogger<CreditApplicationGenerator> _logger = logger; |
There was a problem hiding this comment.
Можно эту строчку удалить и везде использовать logger
| private readonly CreditApplicationGenerator _generator = generator; | ||
| private readonly IDistributedCache _cache = cache; | ||
| private readonly ILogger<CreditApplicationService> _logger = logger; |
| .FinishWith((f, app) => | ||
| { | ||
| SetStatusDependentFields(f, app); | ||
| }); |
There was a problem hiding this comment.
Здесь можно немного попроще:
.FinishWith(SetStatusDependentFields);There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var cachedApplication = JsonSerializer.Deserialize<CreditApplicationModel>(cachedData); | ||
|
|
||
| if (cachedApplication is not null) | ||
| { | ||
| return cachedApplication; | ||
| } | ||
|
|
||
| _logger.LogWarning("Cached data for credit application {Id} deserialized to null, regenerating", id); |
There was a problem hiding this comment.
JsonSerializer.Deserialize<CreditApplicationModel>(cachedData) can throw (e.g., corrupted/partial cache entry). Right now this will bubble up and turn the request into a 500 instead of regenerating. Consider catching JsonException, logging it, deleting the bad cache key, and then proceeding to generate/cache a fresh value.
| var cachedApplication = JsonSerializer.Deserialize<CreditApplicationModel>(cachedData); | |
| if (cachedApplication is not null) | |
| { | |
| return cachedApplication; | |
| } | |
| _logger.LogWarning("Cached data for credit application {Id} deserialized to null, regenerating", id); | |
| try | |
| { | |
| var cachedApplication = JsonSerializer.Deserialize<CreditApplicationModel>(cachedData); | |
| if (cachedApplication is not null) | |
| { | |
| return cachedApplication; | |
| } | |
| _logger.LogWarning("Cached data for credit application {Id} deserialized to null, regenerating", id); | |
| } | |
| catch (JsonException ex) | |
| { | |
| _logger.LogWarning(ex, | |
| "Failed to deserialize cached credit application {Id}. Removing corrupted cache entry and regenerating.", | |
| id); | |
| try | |
| { | |
| await _cache.RemoveAsync(cacheKey, cancellationToken); | |
| } | |
| catch (System.Exception removeEx) | |
| { | |
| _logger.LogError(removeEx, | |
| "Failed to remove corrupted cache entry for credit application {Id}", | |
| id); | |
| } | |
| } |
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" /> |
There was a problem hiding this comment.
The Microsoft.Extensions.* package versions are inconsistent here (Microsoft.Extensions.Http.Resilience is 9.0.0 while Microsoft.Extensions.ServiceDiscovery is 9.5.2). These packages commonly share dependencies, and mismatched major/minor versions can lead to restore/runtime conflicts. Align them to the same version line (or introduce central package management) to avoid binding issues.
| <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" /> | |
| <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.5.2" /> |
| <UnorderedListItem><Link To="https://puginarug.com/">Ссылка на форк</Link></UnorderedListItem> | ||
| <UnorderedListItem>Номер <Strong>№1 «Кэширование»</Strong></UnorderedListItem> | ||
| <UnorderedListItem>Вариант <Strong>№4 «Кредитная заявка»</Strong></UnorderedListItem> | ||
| <UnorderedListItem>Выполнена <Strong>Горшениным Дмитрием 6511</Strong> </UnorderedListItem> |
There was a problem hiding this comment.
Grammatically this reads as feminine (“Выполнена”), while the sentence is about the work/assignment (“задание/лабораторная”) and is usually phrased as “Выполнено …”. Consider adjusting the wording to avoid the agreement error.
| <UnorderedListItem>Выполнена <Strong>Горшениным Дмитрием 6511</Strong> </UnorderedListItem> | |
| <UnorderedListItem>Выполнено <Strong>Горшениным Дмитрием 6511</Strong> </UnorderedListItem> |
ФИО: Горшенин Дмитрий
Номер группы: 6511
Номер лабораторной: 1
Номер варианта: 4
Краткое описание предметной области: Генератор кредитной заявки
Краткое описание добавленных фич: Добавлен сервис генерации и кэширования кредитных заявок