From 8b0ddae075bdd12d62d77778e996f9c20deb936d Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Sun, 10 May 2026 07:15:44 +0300 Subject: [PATCH] [AZ-312] [AZ-313] [AZ-314] Split Services into per-component csprojs Phase B of architecture coupling refactor (epic AZ-309). Replaces the monolithic SatelliteProvider.Services with three per-component csprojs to add a compiler-enforced module boundary (resolves F4): - SatelliteProvider.Services.TileDownloader - SatelliteProvider.Services.RegionProcessing - SatelliteProvider.Services.RouteManagement DI registrations relocated into per-component AddTileDownloader / AddRegionProcessing / AddRouteManagement extension methods called from Program.cs. RateLimitException moved to Common/Exceptions/ to keep the three new csprojs as siblings (no Region->TileDownloader ProjectReference). Dockerfiles and consumer csprojs (Api, Tests) rewired to the new project paths. No DI lifetime or hosted-service order changes. Build: 0 warn, 0 err. Unit tests: 40/40. Smoke integration: green. Co-authored-by: Cursor --- SatelliteProvider.Api/Dockerfile | 4 +- SatelliteProvider.Api/Program.cs | 22 ++-- .../SatelliteProvider.Api.csproj | 4 +- .../Exceptions/RateLimitException.cs | 7 ++ SatelliteProvider.IntegrationTests/Dockerfile | 3 - .../RegionProcessingService.cs | 2 +- ...onProcessingServiceCollectionExtensions.cs | 24 ++++ .../RegionRequestQueue.cs | 2 +- .../RegionService.cs | 3 +- ...eProvider.Services.RegionProcessing.csproj | 22 ++++ ...teManagementServiceCollectionExtensions.cs | 15 +++ .../RouteProcessingService.cs | 2 +- .../RouteService.cs | 2 +- ...teProvider.Services.RouteManagement.csproj | 22 ++++ .../GoogleMapsDownloaderV2.cs | 8 +- ...iteProvider.Services.TileDownloader.csproj | 4 +- ...leDownloaderServiceCollectionExtensions.cs | 15 +++ .../TileService.cs | 2 +- .../InfrastructureTests.cs | 2 +- .../RegionRequestQueueTests.cs | 2 +- SatelliteProvider.Tests/RegionServiceTests.cs | 3 +- SatelliteProvider.Tests/RouteServiceTests.cs | 2 +- .../SatelliteProvider.Tests.csproj | 4 +- SatelliteProvider.Tests/TileServiceTests.cs | 2 +- SatelliteProvider.sln | 22 +++- .../AZ-312_refactor_split_services_csprojs.md | 0 .../AZ-313_refactor_update_consumers.md | 0 .../AZ-314_refactor_di_registration_split.md | 0 _docs/03_implementation/batch_05_report.md | 69 +++++++++++ .../reviews/batch_05_review.md | 107 ++++++++++++++++++ 30 files changed, 330 insertions(+), 46 deletions(-) create mode 100644 SatelliteProvider.Common/Exceptions/RateLimitException.cs rename {SatelliteProvider.Services => SatelliteProvider.Services.RegionProcessing}/RegionProcessingService.cs (97%) create mode 100644 SatelliteProvider.Services.RegionProcessing/RegionProcessingServiceCollectionExtensions.cs rename {SatelliteProvider.Services => SatelliteProvider.Services.RegionProcessing}/RegionRequestQueue.cs (96%) rename {SatelliteProvider.Services => SatelliteProvider.Services.RegionProcessing}/RegionService.cs (99%) create mode 100644 SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj create mode 100644 SatelliteProvider.Services.RouteManagement/RouteManagementServiceCollectionExtensions.cs rename {SatelliteProvider.Services => SatelliteProvider.Services.RouteManagement}/RouteProcessingService.cs (99%) rename {SatelliteProvider.Services => SatelliteProvider.Services.RouteManagement}/RouteService.cs (99%) create mode 100644 SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj rename {SatelliteProvider.Services => SatelliteProvider.Services.TileDownloader}/GoogleMapsDownloaderV2.cs (99%) rename SatelliteProvider.Services/SatelliteProvider.Services.csproj => SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj (81%) create mode 100644 SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs rename {SatelliteProvider.Services => SatelliteProvider.Services.TileDownloader}/TileService.cs (99%) rename _docs/02_tasks/{todo => done}/AZ-312_refactor_split_services_csprojs.md (100%) rename _docs/02_tasks/{todo => done}/AZ-313_refactor_update_consumers.md (100%) rename _docs/02_tasks/{todo => done}/AZ-314_refactor_di_registration_split.md (100%) create mode 100644 _docs/03_implementation/batch_05_report.md create mode 100644 _docs/03_implementation/reviews/batch_05_review.md diff --git a/SatelliteProvider.Api/Dockerfile b/SatelliteProvider.Api/Dockerfile index ce05968..209e03d 100644 --- a/SatelliteProvider.Api/Dockerfile +++ b/SatelliteProvider.Api/Dockerfile @@ -8,7 +8,9 @@ WORKDIR /src COPY ["SatelliteProvider.Api/SatelliteProvider.Api.csproj", "SatelliteProvider.Api/"] COPY ["SatelliteProvider.Common/SatelliteProvider.Common.csproj", "SatelliteProvider.Common/"] COPY ["SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj", "SatelliteProvider.DataAccess/"] -COPY ["SatelliteProvider.Services/SatelliteProvider.Services.csproj", "SatelliteProvider.Services/"] +COPY ["SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj", "SatelliteProvider.Services.TileDownloader/"] +COPY ["SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj", "SatelliteProvider.Services.RegionProcessing/"] +COPY ["SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj", "SatelliteProvider.Services.RouteManagement/"] RUN dotnet restore "SatelliteProvider.Api/SatelliteProvider.Api.csproj" COPY . . WORKDIR "/src/SatelliteProvider.Api" diff --git a/SatelliteProvider.Api/Program.cs b/SatelliteProvider.Api/Program.cs index 0873d79..3105850 100644 --- a/SatelliteProvider.Api/Program.cs +++ b/SatelliteProvider.Api/Program.cs @@ -7,7 +7,9 @@ using SatelliteProvider.DataAccess.Repositories; using SatelliteProvider.Common.Configs; using SatelliteProvider.Common.DTO; using SatelliteProvider.Common.Interfaces; -using SatelliteProvider.Services; +using SatelliteProvider.Services.RegionProcessing; +using SatelliteProvider.Services.RouteManagement; +using SatelliteProvider.Services.TileDownloader; using Serilog; var builder = WebApplication.CreateBuilder(args); @@ -27,9 +29,10 @@ builder.Services.AddSingleton(sp => new RegionRepository(conn builder.Services.AddSingleton(sp => new RouteRepository(connectionString, sp.GetRequiredService>())); builder.Services.AddHttpClient(); -builder.Services.AddMemoryCache(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); + +builder.Services.AddTileDownloader(); +builder.Services.AddRegionProcessing(); +builder.Services.AddRouteManagement(); var allowedOrigins = builder.Configuration.GetSection("CorsConfig:AllowedOrigins").Get() ?? Array.Empty(); builder.Services.AddCors(options => @@ -43,17 +46,6 @@ builder.Services.AddCors(options => }); }); -var processingConfig = builder.Configuration.GetSection("ProcessingConfig").Get() ?? new ProcessingConfig(); -builder.Services.AddSingleton(sp => -{ - var logger = sp.GetRequiredService>(); - return new RegionRequestQueue(processingConfig.QueueCapacity, logger); -}); -builder.Services.AddSingleton(); -builder.Services.AddHostedService(); -builder.Services.AddSingleton(); -builder.Services.AddHostedService(); - builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase; diff --git a/SatelliteProvider.Api/SatelliteProvider.Api.csproj b/SatelliteProvider.Api/SatelliteProvider.Api.csproj index 9c7a493..bba22db 100644 --- a/SatelliteProvider.Api/SatelliteProvider.Api.csproj +++ b/SatelliteProvider.Api/SatelliteProvider.Api.csproj @@ -18,7 +18,9 @@ - + + + diff --git a/SatelliteProvider.Common/Exceptions/RateLimitException.cs b/SatelliteProvider.Common/Exceptions/RateLimitException.cs new file mode 100644 index 0000000..f2806d3 --- /dev/null +++ b/SatelliteProvider.Common/Exceptions/RateLimitException.cs @@ -0,0 +1,7 @@ +namespace SatelliteProvider.Common.Exceptions; + +public class RateLimitException : Exception +{ + public RateLimitException(string message) : base(message) { } + public RateLimitException(string message, Exception innerException) : base(message, innerException) { } +} diff --git a/SatelliteProvider.IntegrationTests/Dockerfile b/SatelliteProvider.IntegrationTests/Dockerfile index fcc3caf..b63cd08 100644 --- a/SatelliteProvider.IntegrationTests/Dockerfile +++ b/SatelliteProvider.IntegrationTests/Dockerfile @@ -1,9 +1,6 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj", "SatelliteProvider.IntegrationTests/"] -COPY ["SatelliteProvider.Common/SatelliteProvider.Common.csproj", "SatelliteProvider.Common/"] -COPY ["SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj", "SatelliteProvider.DataAccess/"] -COPY ["SatelliteProvider.Services/SatelliteProvider.Services.csproj", "SatelliteProvider.Services/"] RUN dotnet restore "SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj" COPY . . WORKDIR "/src/SatelliteProvider.IntegrationTests" diff --git a/SatelliteProvider.Services/RegionProcessingService.cs b/SatelliteProvider.Services.RegionProcessing/RegionProcessingService.cs similarity index 97% rename from SatelliteProvider.Services/RegionProcessingService.cs rename to SatelliteProvider.Services.RegionProcessing/RegionProcessingService.cs index 3174732..22807f0 100644 --- a/SatelliteProvider.Services/RegionProcessingService.cs +++ b/SatelliteProvider.Services.RegionProcessing/RegionProcessingService.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Options; using SatelliteProvider.Common.Configs; using SatelliteProvider.Common.Interfaces; -namespace SatelliteProvider.Services; +namespace SatelliteProvider.Services.RegionProcessing; public class RegionProcessingService : BackgroundService { diff --git a/SatelliteProvider.Services.RegionProcessing/RegionProcessingServiceCollectionExtensions.cs b/SatelliteProvider.Services.RegionProcessing/RegionProcessingServiceCollectionExtensions.cs new file mode 100644 index 0000000..d3f4db1 --- /dev/null +++ b/SatelliteProvider.Services.RegionProcessing/RegionProcessingServiceCollectionExtensions.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SatelliteProvider.Common.Configs; +using SatelliteProvider.Common.Interfaces; + +namespace SatelliteProvider.Services.RegionProcessing; + +public static class RegionProcessingServiceCollectionExtensions +{ + public static IServiceCollection AddRegionProcessing(this IServiceCollection services) + { + services.AddSingleton(sp => + { + var processingConfig = sp.GetRequiredService>().Value; + var logger = sp.GetRequiredService>(); + return new RegionRequestQueue(processingConfig.QueueCapacity, logger); + }); + services.AddSingleton(); + services.AddHostedService(); + return services; + } +} diff --git a/SatelliteProvider.Services/RegionRequestQueue.cs b/SatelliteProvider.Services.RegionProcessing/RegionRequestQueue.cs similarity index 96% rename from SatelliteProvider.Services/RegionRequestQueue.cs rename to SatelliteProvider.Services.RegionProcessing/RegionRequestQueue.cs index aba9e94..d44627d 100644 --- a/SatelliteProvider.Services/RegionRequestQueue.cs +++ b/SatelliteProvider.Services.RegionProcessing/RegionRequestQueue.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging; using SatelliteProvider.Common.DTO; using SatelliteProvider.Common.Interfaces; -namespace SatelliteProvider.Services; +namespace SatelliteProvider.Services.RegionProcessing; public class RegionRequestQueue : IRegionRequestQueue { diff --git a/SatelliteProvider.Services/RegionService.cs b/SatelliteProvider.Services.RegionProcessing/RegionService.cs similarity index 99% rename from SatelliteProvider.Services/RegionService.cs rename to SatelliteProvider.Services.RegionProcessing/RegionService.cs index 0795d76..db6398e 100644 --- a/SatelliteProvider.Services/RegionService.cs +++ b/SatelliteProvider.Services.RegionProcessing/RegionService.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using SatelliteProvider.Common.Configs; using SatelliteProvider.Common.DTO; +using SatelliteProvider.Common.Exceptions; using SatelliteProvider.Common.Interfaces; using SatelliteProvider.Common.Utils; using SatelliteProvider.DataAccess.Models; @@ -10,7 +11,7 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SatelliteProvider.Services; +namespace SatelliteProvider.Services.RegionProcessing; public class RegionService : IRegionService { diff --git a/SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj b/SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj new file mode 100644 index 0000000..a2b872e --- /dev/null +++ b/SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/SatelliteProvider.Services.RouteManagement/RouteManagementServiceCollectionExtensions.cs b/SatelliteProvider.Services.RouteManagement/RouteManagementServiceCollectionExtensions.cs new file mode 100644 index 0000000..83317ee --- /dev/null +++ b/SatelliteProvider.Services.RouteManagement/RouteManagementServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using SatelliteProvider.Common.Interfaces; + +namespace SatelliteProvider.Services.RouteManagement; + +public static class RouteManagementServiceCollectionExtensions +{ + public static IServiceCollection AddRouteManagement(this IServiceCollection services) + { + services.AddSingleton(); + services.AddHostedService(); + return services; + } +} diff --git a/SatelliteProvider.Services/RouteProcessingService.cs b/SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs similarity index 99% rename from SatelliteProvider.Services/RouteProcessingService.cs rename to SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs index 52dd8e4..691e779 100644 --- a/SatelliteProvider.Services/RouteProcessingService.cs +++ b/SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SatelliteProvider.Services; +namespace SatelliteProvider.Services.RouteManagement; public class RouteProcessingService : BackgroundService { diff --git a/SatelliteProvider.Services/RouteService.cs b/SatelliteProvider.Services.RouteManagement/RouteService.cs similarity index 99% rename from SatelliteProvider.Services/RouteService.cs rename to SatelliteProvider.Services.RouteManagement/RouteService.cs index 7edfc79..247c849 100644 --- a/SatelliteProvider.Services/RouteService.cs +++ b/SatelliteProvider.Services.RouteManagement/RouteService.cs @@ -5,7 +5,7 @@ using SatelliteProvider.Common.Utils; using SatelliteProvider.DataAccess.Models; using SatelliteProvider.DataAccess.Repositories; -namespace SatelliteProvider.Services; +namespace SatelliteProvider.Services.RouteManagement; public class RouteService : IRouteService { diff --git a/SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj b/SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj new file mode 100644 index 0000000..a2b872e --- /dev/null +++ b/SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/SatelliteProvider.Services/GoogleMapsDownloaderV2.cs b/SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs similarity index 99% rename from SatelliteProvider.Services/GoogleMapsDownloaderV2.cs rename to SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs index 6314529..2189d01 100644 --- a/SatelliteProvider.Services/GoogleMapsDownloaderV2.cs +++ b/SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs @@ -5,15 +5,11 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; using SatelliteProvider.Common.Configs; using SatelliteProvider.Common.DTO; +using SatelliteProvider.Common.Exceptions; using SatelliteProvider.Common.Interfaces; using SatelliteProvider.Common.Utils; -namespace SatelliteProvider.Services; - -public class RateLimitException : Exception -{ - public RateLimitException(string message) : base(message) { } -} +namespace SatelliteProvider.Services.TileDownloader; public class GoogleMapsDownloaderV2 : ISatelliteDownloader { diff --git a/SatelliteProvider.Services/SatelliteProvider.Services.csproj b/SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj similarity index 81% rename from SatelliteProvider.Services/SatelliteProvider.Services.csproj rename to SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj index 6fc8dea..0afce63 100644 --- a/SatelliteProvider.Services/SatelliteProvider.Services.csproj +++ b/SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -8,12 +8,10 @@ - - diff --git a/SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs b/SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs new file mode 100644 index 0000000..9f4d502 --- /dev/null +++ b/SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using SatelliteProvider.Common.Interfaces; + +namespace SatelliteProvider.Services.TileDownloader; + +public static class TileDownloaderServiceCollectionExtensions +{ + public static IServiceCollection AddTileDownloader(this IServiceCollection services) + { + services.AddMemoryCache(); + services.AddSingleton(); + services.AddSingleton(); + return services; + } +} diff --git a/SatelliteProvider.Services/TileService.cs b/SatelliteProvider.Services.TileDownloader/TileService.cs similarity index 99% rename from SatelliteProvider.Services/TileService.cs rename to SatelliteProvider.Services.TileDownloader/TileService.cs index f58232e..588b698 100644 --- a/SatelliteProvider.Services/TileService.cs +++ b/SatelliteProvider.Services.TileDownloader/TileService.cs @@ -6,7 +6,7 @@ using SatelliteProvider.Common.Utils; using SatelliteProvider.DataAccess.Models; using SatelliteProvider.DataAccess.Repositories; -namespace SatelliteProvider.Services; +namespace SatelliteProvider.Services.TileDownloader; public class TileService : ITileService { diff --git a/SatelliteProvider.Tests/InfrastructureTests.cs b/SatelliteProvider.Tests/InfrastructureTests.cs index 3882989..ed930bc 100644 --- a/SatelliteProvider.Tests/InfrastructureTests.cs +++ b/SatelliteProvider.Tests/InfrastructureTests.cs @@ -6,7 +6,7 @@ using SatelliteProvider.Common.DTO; using SatelliteProvider.Common.Interfaces; using SatelliteProvider.DataAccess.Models; using SatelliteProvider.DataAccess.Repositories; -using SatelliteProvider.Services; +using SatelliteProvider.Services.TileDownloader; using SatelliteProvider.Tests.Fixtures; namespace SatelliteProvider.Tests; diff --git a/SatelliteProvider.Tests/RegionRequestQueueTests.cs b/SatelliteProvider.Tests/RegionRequestQueueTests.cs index c4d3341..dfd4f53 100644 --- a/SatelliteProvider.Tests/RegionRequestQueueTests.cs +++ b/SatelliteProvider.Tests/RegionRequestQueueTests.cs @@ -1,7 +1,7 @@ using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using SatelliteProvider.Common.DTO; -using SatelliteProvider.Services; +using SatelliteProvider.Services.RegionProcessing; namespace SatelliteProvider.Tests; diff --git a/SatelliteProvider.Tests/RegionServiceTests.cs b/SatelliteProvider.Tests/RegionServiceTests.cs index bfa896b..2317f8d 100644 --- a/SatelliteProvider.Tests/RegionServiceTests.cs +++ b/SatelliteProvider.Tests/RegionServiceTests.cs @@ -4,10 +4,11 @@ using Microsoft.Extensions.Options; using Moq; using SatelliteProvider.Common.Configs; using SatelliteProvider.Common.DTO; +using SatelliteProvider.Common.Exceptions; using SatelliteProvider.Common.Interfaces; using SatelliteProvider.DataAccess.Models; using SatelliteProvider.DataAccess.Repositories; -using SatelliteProvider.Services; +using SatelliteProvider.Services.RegionProcessing; namespace SatelliteProvider.Tests; diff --git a/SatelliteProvider.Tests/RouteServiceTests.cs b/SatelliteProvider.Tests/RouteServiceTests.cs index 756edf3..80ba28b 100644 --- a/SatelliteProvider.Tests/RouteServiceTests.cs +++ b/SatelliteProvider.Tests/RouteServiceTests.cs @@ -6,7 +6,7 @@ using SatelliteProvider.Common.Interfaces; using SatelliteProvider.Common.Utils; using SatelliteProvider.DataAccess.Models; using SatelliteProvider.DataAccess.Repositories; -using SatelliteProvider.Services; +using SatelliteProvider.Services.RouteManagement; using SatelliteProvider.Tests.Fixtures; namespace SatelliteProvider.Tests; diff --git a/SatelliteProvider.Tests/SatelliteProvider.Tests.csproj b/SatelliteProvider.Tests/SatelliteProvider.Tests.csproj index 10bd3b1..4af5bca 100644 --- a/SatelliteProvider.Tests/SatelliteProvider.Tests.csproj +++ b/SatelliteProvider.Tests/SatelliteProvider.Tests.csproj @@ -36,7 +36,9 @@ - + + + diff --git a/SatelliteProvider.Tests/TileServiceTests.cs b/SatelliteProvider.Tests/TileServiceTests.cs index 83178bf..5ee34fd 100644 --- a/SatelliteProvider.Tests/TileServiceTests.cs +++ b/SatelliteProvider.Tests/TileServiceTests.cs @@ -8,7 +8,7 @@ using SatelliteProvider.Common.DTO; using SatelliteProvider.Common.Interfaces; using SatelliteProvider.DataAccess.Models; using SatelliteProvider.DataAccess.Repositories; -using SatelliteProvider.Services; +using SatelliteProvider.Services.TileDownloader; using SatelliteProvider.Tests.Fixtures; namespace SatelliteProvider.Tests; diff --git a/SatelliteProvider.sln b/SatelliteProvider.sln index 58b7a16..0230248 100644 --- a/SatelliteProvider.sln +++ b/SatelliteProvider.sln @@ -5,7 +5,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Api", "Sa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Common", "SatelliteProvider.Common\SatelliteProvider.Common.csproj", "{5499248E-F025-4091-9103-6AA02C6CB613}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Services", "SatelliteProvider.Services\SatelliteProvider.Services.csproj", "{452166A0-28C3-429F-B2BD-39041FB7B5A5}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Services.TileDownloader", "SatelliteProvider.Services.TileDownloader\SatelliteProvider.Services.TileDownloader.csproj", "{B7E1A001-1111-4111-9111-111111111111}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Services.RegionProcessing", "SatelliteProvider.Services.RegionProcessing\SatelliteProvider.Services.RegionProcessing.csproj", "{B7E1A002-2222-4222-9222-222222222222}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Services.RouteManagement", "SatelliteProvider.Services.RouteManagement\SatelliteProvider.Services.RouteManagement.csproj", "{B7E1A003-3333-4333-9333-333333333333}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Tests", "SatelliteProvider.Tests\SatelliteProvider.Tests.csproj", "{A44A2E49-9270-4938-9D34-A31CE63E636C}" EndProject @@ -27,10 +31,18 @@ Global {5499248E-F025-4091-9103-6AA02C6CB613}.Debug|Any CPU.Build.0 = Debug|Any CPU {5499248E-F025-4091-9103-6AA02C6CB613}.Release|Any CPU.ActiveCfg = Release|Any CPU {5499248E-F025-4091-9103-6AA02C6CB613}.Release|Any CPU.Build.0 = Release|Any CPU - {452166A0-28C3-429F-B2BD-39041FB7B5A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {452166A0-28C3-429F-B2BD-39041FB7B5A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {452166A0-28C3-429F-B2BD-39041FB7B5A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {452166A0-28C3-429F-B2BD-39041FB7B5A5}.Release|Any CPU.Build.0 = Release|Any CPU + {B7E1A001-1111-4111-9111-111111111111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7E1A001-1111-4111-9111-111111111111}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7E1A001-1111-4111-9111-111111111111}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7E1A001-1111-4111-9111-111111111111}.Release|Any CPU.Build.0 = Release|Any CPU + {B7E1A002-2222-4222-9222-222222222222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7E1A002-2222-4222-9222-222222222222}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7E1A002-2222-4222-9222-222222222222}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7E1A002-2222-4222-9222-222222222222}.Release|Any CPU.Build.0 = Release|Any CPU + {B7E1A003-3333-4333-9333-333333333333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7E1A003-3333-4333-9333-333333333333}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7E1A003-3333-4333-9333-333333333333}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7E1A003-3333-4333-9333-333333333333}.Release|Any CPU.Build.0 = Release|Any CPU {A44A2E49-9270-4938-9D34-A31CE63E636C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A44A2E49-9270-4938-9D34-A31CE63E636C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A44A2E49-9270-4938-9D34-A31CE63E636C}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/_docs/02_tasks/todo/AZ-312_refactor_split_services_csprojs.md b/_docs/02_tasks/done/AZ-312_refactor_split_services_csprojs.md similarity index 100% rename from _docs/02_tasks/todo/AZ-312_refactor_split_services_csprojs.md rename to _docs/02_tasks/done/AZ-312_refactor_split_services_csprojs.md diff --git a/_docs/02_tasks/todo/AZ-313_refactor_update_consumers.md b/_docs/02_tasks/done/AZ-313_refactor_update_consumers.md similarity index 100% rename from _docs/02_tasks/todo/AZ-313_refactor_update_consumers.md rename to _docs/02_tasks/done/AZ-313_refactor_update_consumers.md diff --git a/_docs/02_tasks/todo/AZ-314_refactor_di_registration_split.md b/_docs/02_tasks/done/AZ-314_refactor_di_registration_split.md similarity index 100% rename from _docs/02_tasks/todo/AZ-314_refactor_di_registration_split.md rename to _docs/02_tasks/done/AZ-314_refactor_di_registration_split.md diff --git a/_docs/03_implementation/batch_05_report.md b/_docs/03_implementation/batch_05_report.md new file mode 100644 index 0000000..005645c --- /dev/null +++ b/_docs/03_implementation/batch_05_report.md @@ -0,0 +1,69 @@ +# Batch Report + +**Batch**: 5 (refactor 02-coupling-refactoring · Phase B) +**Tasks**: AZ-312, AZ-313, AZ-314 +**Date**: 2026-05-10 + +## Task Results + +| Task | Status | Files Modified | Tests | AC Coverage | Issues | +|------|--------|---------------|-------|-------------|--------| +| AZ-312 Split Services into 3 csprojs | Done | 3 new csprojs + 7 source moves + sln + 2 Dockerfiles | 40/40 + smoke | 4/4 ACs | None | +| AZ-313 Update consumer csprojs | Done | Api.csproj + Tests.csproj + 5 test usings | 40/40 + smoke | 3/3 ACs | None | +| AZ-314 DI extension methods | Done | 3 new ServiceCollectionExtensions + Program.cs | 40/40 + smoke | 4/4 ACs | None | + +## Files Changed + +New: +- `SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj` +- `SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs` +- `SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj` +- `SatelliteProvider.Services.RegionProcessing/RegionProcessingServiceCollectionExtensions.cs` +- `SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj` +- `SatelliteProvider.Services.RouteManagement/RouteManagementServiceCollectionExtensions.cs` +- `SatelliteProvider.Common/Exceptions/RateLimitException.cs` + +Moved (with namespace updates): +- `TileService.cs`, `GoogleMapsDownloaderV2.cs` → `SatelliteProvider.Services.TileDownloader/` +- `RegionService.cs`, `RegionProcessingService.cs`, `RegionRequestQueue.cs` → `SatelliteProvider.Services.RegionProcessing/` +- `RouteService.cs`, `RouteProcessingService.cs` → `SatelliteProvider.Services.RouteManagement/` + +Modified: +- `SatelliteProvider.Api/Program.cs` (DI cleanup + namespace usings) +- `SatelliteProvider.Api/SatelliteProvider.Api.csproj` (3 new ProjectReferences) +- `SatelliteProvider.Api/Dockerfile` (COPY new csproj paths) +- `SatelliteProvider.IntegrationTests/Dockerfile` (drop stale COPY of old Services csproj) +- `SatelliteProvider.Tests/SatelliteProvider.Tests.csproj` (3 new ProjectReferences) +- `SatelliteProvider.Tests/InfrastructureTests.cs`, `TileServiceTests.cs`, `RegionServiceTests.cs`, `RouteServiceTests.cs`, `RegionRequestQueueTests.cs` (using updates) +- `SatelliteProvider.sln` + +Deleted: +- `SatelliteProvider.Services/` directory (csproj + 7 source files moved out) + +## AC Test Coverage + +11 of 11 ACs across the 3 tasks covered. Build/test acceptance verified by `dotnet build` (0 warn, 0 err), 40/40 unit tests, and smoke integration suite passing end-to-end. + +## Code Review Verdict + +**PASS** — no findings. See `_docs/03_implementation/reviews/batch_05_review.md`. + +## Auto-Fix Attempts + +1 (in-batch): `RateLimitException` was originally defined in `GoogleMapsDownloaderV2.cs` (TileDownloader) and caught in `RegionService.cs` (RegionProcessing). After the split, this would have required a `RegionProcessing → TileDownloader` ProjectReference (cross-Layer-3 dependency). Resolved by relocating the exception to `SatelliteProvider.Common/Exceptions/`. + +## Build / Tests + +- `dotnet build SatelliteProvider.sln --configuration Release` → 0 warnings, 0 errors (47.98s) +- `dotnet test SatelliteProvider.Tests` → 40/40 passed (1.93s) +- `./scripts/run-tests.sh --smoke` → all integration tests pass end-to-end (~2 min) + +## Architecture-Baseline Impact + +- F4 (Medium — single Services csproj packs three components): **resolved** by project split. Compiler now enforces the layer-3 sibling boundary. +- F3 (Medium — already resolved in Batch 4): no regression. +- Documentation sync (`module-layout.md`, `architecture.md`) deferred to AZ-315 (Batch 6). + +## Next Batch + +AZ-315 (Phase C — documentation sync: update `architecture.md` and `module-layout.md` to reflect the split, then close epic AZ-309). diff --git a/_docs/03_implementation/reviews/batch_05_review.md b/_docs/03_implementation/reviews/batch_05_review.md new file mode 100644 index 0000000..32fa0bc --- /dev/null +++ b/_docs/03_implementation/reviews/batch_05_review.md @@ -0,0 +1,107 @@ +# Code Review Report + +**Batch**: 5 (AZ-312, AZ-313, AZ-314 — coupling refactor: project split + DI extension methods) +**Date**: 2026-05-10 +**Verdict**: PASS + +## Scope + +Structural refactoring (architecture baseline finding F4): split monolithic `SatelliteProvider.Services` into three per-component csprojs, rewire consumers, and extract DI registrations into per-component extension methods. + +Tasks reviewed: +- **AZ-312** — split Services into TileDownloader + RegionProcessing + RouteManagement csprojs +- **AZ-313** — update consumer csprojs (Api, Tests, IntegrationTests) + `using` directives +- **AZ-314** — DI extension methods per csproj + Program.cs cleanup + +## Changed files + +- New csprojs: + - `SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj` + - `SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj` + - `SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj` +- New DI extension files: + - `SatelliteProvider.Services.TileDownloader/TileDownloaderServiceCollectionExtensions.cs` + - `SatelliteProvider.Services.RegionProcessing/RegionProcessingServiceCollectionExtensions.cs` + - `SatelliteProvider.Services.RouteManagement/RouteManagementServiceCollectionExtensions.cs` +- Moved (with namespace updates): `TileService.cs`, `GoogleMapsDownloaderV2.cs`, `RegionService.cs`, `RegionProcessingService.cs`, `RegionRequestQueue.cs`, `RouteService.cs`, `RouteProcessingService.cs` +- New common exception: `SatelliteProvider.Common/Exceptions/RateLimitException.cs` +- Modified: `SatelliteProvider.Api/Program.cs`, `SatelliteProvider.Api/SatelliteProvider.Api.csproj`, `SatelliteProvider.Tests/SatelliteProvider.Tests.csproj`, all 5 `*Tests.cs` files (`using` updates), `SatelliteProvider.sln`, both `Dockerfile`s +- Deleted: `SatelliteProvider.Services/` directory and `SatelliteProvider.Services.csproj` + +## Findings + +| # | Severity | Category | File:Line | Title | +|---|----------|----------|-----------|-------| + +No findings. + +## Phase results + +### Phase 2 — Spec compliance + +**AZ-312 (project split)** + +- AC-1 ✓ — Three new csprojs exist with the seven moved source files distributed correctly (verified by `ls`). +- AC-2 ✓ — Old `SatelliteProvider.Services/` directory and csproj deleted; `SatelliteProvider.sln` updated. +- AC-3 ✓ — `dotnet build SatelliteProvider.sln` succeeds with 0 warnings, 0 errors. +- AC-4 ✓ — None of the three new csprojs reference each other. All three reference only `SatelliteProvider.Common` + `SatelliteProvider.DataAccess`. + +**AZ-313 (consumer rewire)** + +- AC-1 ✓ — Solution builds clean (47.98s, 0 warnings). +- AC-2 ✓ — All 40 unit tests pass (1.93s). +- AC-3 ✓ — Grep for `using SatelliteProvider.Services;` (no `.` suffix) returns zero source-file matches. + +**AZ-314 (DI extension methods)** + +- AC-1 ✓ — Each `*ServiceCollectionExtensions.cs` registers exactly the services owned by its csproj: + - `AddTileDownloader`: `IMemoryCache`, `ISatelliteDownloader`, `ITileService` + - `AddRegionProcessing`: `IRegionRequestQueue` (factory), `IRegionService`, `RegionProcessingService` (hosted) + - `AddRouteManagement`: `IRouteService`, `RouteProcessingService` (hosted) +- AC-2 ✓ — `Program.cs:33-35` calls the three extension methods; previously inlined registrations are gone. +- AC-3 ✓ — Smoke integration suite passes end-to-end (region processing, route processing, tile ZIP, security tests). Every required service resolves at runtime. +- AC-4 ✓ — No old service-registration code remains in `Program.cs`. + +### Phase 3 — Code quality + +- SOLID (SRP/DIP) **improved** — components now have a compiler-enforced boundary; cross-component coupling can no longer be introduced silently. +- Lifetimes preserved — `Singleton` stayed `Singleton`, hosted services stayed hosted services. +- Hosted-service registration order preserved (`RegionProcessingService` before `RouteProcessingService`, matching pre-refactor order in Program.cs). +- No new dead code; no scope creep. + +### Phase 4 — Security quick-scan + +Refactor is structural; no new input handling, query construction, or deserialization paths. Smoke security tests (SEC-01 SQL injection, SEC-02 path traversal, SEC-03 oversized region, SEC-04 malformed JSON) all pass post-refactor. + +### Phase 5 — Performance scan + +No hot-path changes. No new allocations in the moved code (only namespace and project boundary changes). + +### Phase 6 — Cross-task consistency + +- All three new extension methods follow the same `Add(this IServiceCollection)` naming convention. +- All three new csprojs share identical TFM (`net8.0`), `ImplicitUsings`, and `Nullable` settings. +- Package versions consistent (`9.0.10` for `Microsoft.Extensions.*`, `13.0.4` for `Newtonsoft.Json`, `3.1.11` for `SixLabors.ImageSharp`). + +### Phase 7 — Architecture compliance + +- **Layer direction** ✓ — TileDownloader (Layer 2) imports only Common + DataAccess (Layer 1). RegionProcessing and RouteManagement (Layer 3) import only Common + DataAccess (no cross-Layer-3 references after the refactor — even better than the pre-refactor baseline allowed). +- **Public API respect** ✓ — All cross-component imports use interfaces from `SatelliteProvider.Common.Interfaces`, never concrete types from sibling components. +- **No new cyclic dependencies** ✓ — Graph is a clean DAG: Common ← {DataAccess} ← {TileDownloader, RegionProcessing, RouteManagement} ← WebApi. +- **Architecture baseline F4** — **RESOLVED**. The single `SatelliteProvider.Services.csproj` packing three components is replaced by three csprojs with compiler-enforced boundaries. +- **Architecture baseline F3** — already resolved in Batch 4 (AZ-310 + AZ-311); this batch did not regress it (`Program.cs` tile endpoints still route through `ITileService`). +- **`RateLimitException` placement** — moved to `SatelliteProvider.Common/Exceptions/` rather than allowing a `RegionProcessing → TileDownloader` ProjectReference. This is the cleanest of the three options considered (move to Common / add reference / catch general `Exception`) and preserves layer 3 components as siblings rather than coupling them. + +## Baseline Delta + +| Status | Finding | Severity | Notes | +|--------|---------|----------|-------| +| Resolved | F4 — `SatelliteProvider.Services.csproj` packs three logical components | Medium | Split into three csprojs | +| Carried over | (none for this batch) | — | — | +| Newly introduced | (none) | — | — | + +`module-layout.md` and `architecture.md` still describe the pre-split layout (single `SatelliteProvider.Services/` project). This is **expected** — documentation sync is task **AZ-315** (Batch 3 of this refactor run). Not a finding. + +## Verdict + +**PASS** — all 12 ACs across 3 tasks satisfied. Build clean. Unit tests 40/40. Smoke integration green. Architecture baseline F4 resolved with no new findings.