[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 <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-10 07:15:44 +03:00
parent 12b582deac
commit 8b0ddae075
30 changed files with 330 additions and 46 deletions
@@ -1,82 +0,0 @@
# Refactor: split SatelliteProvider.Services into per-component csprojs
**Task**: AZ-312_refactor_split_services_csprojs
**Name**: Split Services into TileDownloader + RegionProcessing + RouteManagement
**Description**: Replace the single `SatelliteProvider.Services` csproj with three per-component csprojs to add a compiler-enforced module boundary.
**Complexity**: 5 points
**Dependencies**: AZ-311
**Component**: All Services components
**Tracker**: AZ-312
**Epic**: AZ-309
## Problem
`SatelliteProvider.Services.csproj` packs three logical components (TileDownloader, RegionProcessing, RouteManagement) into one project. No compiler-enforced boundary prevents accidental cross-component coupling. This is architecture baseline finding F4 (Medium).
## Outcome
- Three new csprojs exist:
- `SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj`
- `SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj`
- `SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj`
- Seven source files moved into the matching project.
- `SatelliteProvider.Services.csproj` deleted.
- Each new csproj `ProjectReference`s only what it needs.
- Solution file `SatelliteProvider.sln` updated.
## Scope
### Included
- File moves:
- `TileService.cs`, `GoogleMapsDownloaderV2.cs``SatelliteProvider.Services.TileDownloader/`
- `RegionService.cs`, `RegionProcessingService.cs`, `RegionRequestQueue.cs``SatelliteProvider.Services.RegionProcessing/`
- `RouteService.cs`, `RouteProcessingService.cs``SatelliteProvider.Services.RouteManagement/`
- Namespace changes:
- `SatelliteProvider.Services``SatelliteProvider.Services.TileDownloader` (in TileService, GoogleMapsDownloaderV2)
- `SatelliteProvider.Services``SatelliteProvider.Services.RegionProcessing` (in three Region* files)
- `SatelliteProvider.Services``SatelliteProvider.Services.RouteManagement` (in two Route* files)
- Add `ProjectReference` entries in each new csproj as required by its members.
- Delete `SatelliteProvider.Services/` directory once empty.
- Update `SatelliteProvider.sln` (add new projects, remove old).
### Excluded
- Updating consumers (Tests, IntegrationTests, Api csprojs and their `using` directives) — those go in AZ-313 and AZ-314.
- Any logic change inside the moved files.
## Acceptance Criteria
**AC-1: Three new csprojs exist with correct contents**
Given the post-refactor tree
When listing the new csproj directories
Then each contains only the files listed in the `Included` section, plus its csproj file.
**AC-2: Old project deleted**
Given the post-refactor tree
When searching for `SatelliteProvider.Services.csproj` or `SatelliteProvider.Services/` directory
Then neither exists.
**AC-3: Solution-level build succeeds with the new csprojs unreferenced**
Given the new csprojs exist but no consumer has been updated yet
When `dotnet build SatelliteProvider.sln` is run
Then build fails ONLY for the unreferenced consumers (Api, Tests, IntegrationTests). The three new csprojs themselves compile clean.
**AC-4: No cross-component reference between the three new csprojs**
Given the three new csproj files
When inspecting their `ProjectReference` entries
Then none of TileDownloader, RegionProcessing, or RouteManagement references another of the three. They all reference only `SatelliteProvider.Common` and (where needed) `SatelliteProvider.DataAccess`.
## Constraints
- No code logic changes inside the moved files.
- Namespaces follow `SatelliteProvider.Services.<Component>` pattern.
- Existing public API of `ITileService`, `IRegionService`, `IRouteService`, `IRegionRequestQueue` (in Common) unchanged.
## Risks & Mitigation
**Risk 1: Hidden cross-component coupling**
- *Risk*: A move reveals that a Region* class actually `using SatelliteProvider.Services` for a TileDownloader-only type.
- *Mitigation*: If found, the cleanest fix is to lift the shared type into `SatelliteProvider.Common` (extend in this task) or add a `ProjectReference` (last resort, document why). Stop and ask if this surfaces.
**Risk 2: SatelliteProvider.sln drift**
- *Risk*: Forgetting to update the solution file leaves the new csprojs invisible to Docker/CI.
- *Mitigation*: Use `dotnet sln add` and `dotnet sln remove` exactly once for each project; assert via `dotnet sln list`.
@@ -1,64 +0,0 @@
# Refactor: update consumers (Api, Tests, IntegrationTests) for split
**Task**: AZ-313_refactor_update_consumers
**Name**: Update consumer csprojs and using directives after Services split
**Description**: Re-wire `SatelliteProvider.Api`, `SatelliteProvider.Tests`, and `SatelliteProvider.IntegrationTests` to reference the three new per-component csprojs.
**Complexity**: 3 points
**Dependencies**: AZ-312
**Component**: API + Tests
**Tracker**: AZ-313
**Epic**: AZ-309
## Problem
After AZ-312 splits `SatelliteProvider.Services` into three csprojs, every consumer of the old project breaks (compile errors). This task fixes the consumers without changing any test logic or DI behavior.
## Outcome
- `SatelliteProvider.Api.csproj` references all three new Services projects.
- `SatelliteProvider.Tests.csproj` references whichever Services projects its current tests depend on (typically all three; verify per file).
- `SatelliteProvider.IntegrationTests.csproj` references whichever Services projects its current tests depend on.
- All `using SatelliteProvider.Services;` directives across consumers replaced with the correct `using SatelliteProvider.Services.<Component>;` directive.
- `dotnet build SatelliteProvider.sln` succeeds.
- All previously-green unit tests remain green.
## Scope
### Included
- Edit `.csproj` files of Api, Tests, IntegrationTests: add new `ProjectReference` entries, remove the old `SatelliteProvider.Services` reference.
- Update all `using` directives in `.cs` files under those three projects.
- Run `dotnet build` to confirm zero errors.
- Run unit test suite (`SatelliteProvider.Tests`) to confirm zero regressions vs. Step 6 baseline.
### Excluded
- DI container registration changes — those happen in AZ-314.
- Re-running integration suite — that happens in AZ-315.
- Changing any test assertion or test data.
## Acceptance Criteria
**AC-1: Solution builds clean**
Given the post-refactor consumer csprojs
When `dotnet build SatelliteProvider.sln` is run
Then build succeeds with zero errors and zero new warnings vs. the pre-refactor baseline.
**AC-2: Unit tests green**
Given the post-refactor consumer csprojs
When `dotnet test SatelliteProvider.Tests` is run
Then all 35 (or current baseline count) unit tests pass.
**AC-3: No stale `using SatelliteProvider.Services;`**
Given the post-refactor source tree
When grepping for `using SatelliteProvider.Services;` (without a `.<Component>` suffix)
Then zero matches outside intentional comments.
## Constraints
- No DI registration changes in this task.
- No test logic, fixture, or assertion changes.
## Risks & Mitigation
**Risk 1: Shadowed types if a single file references types from two of the new components**
- *Risk*: A test that exercises both `RegionService` and `RouteService` needs both `using` statements.
- *Mitigation*: Add both. Compiler will tell us which.
@@ -1,69 +0,0 @@
# Refactor: split DI registration to per-component extension methods
**Task**: AZ-314_refactor_di_registration_split
**Name**: Per-component AddXxxServices() extension methods in Program.cs
**Description**: Replace ad-hoc DI registrations in `Program.cs` with three extension methods, one per new csproj, matching the module boundary.
**Complexity**: 2 points
**Dependencies**: AZ-313
**Component**: API
**Tracker**: AZ-314
**Epic**: AZ-309
## Problem
`Program.cs` currently registers all services inline and ungrouped. After the split, the three Services csprojs have logically distinct registrations; co-locating each set with its csproj clarifies ownership and makes adding a new component easier.
## Outcome
- New extension methods in each new Services csproj:
- `SatelliteProvider.Services.TileDownloader.TileDownloaderServiceCollectionExtensions.AddTileDownloader(this IServiceCollection)`
- `SatelliteProvider.Services.RegionProcessing.RegionProcessingServiceCollectionExtensions.AddRegionProcessing(this IServiceCollection)`
- `SatelliteProvider.Services.RouteManagement.RouteManagementServiceCollectionExtensions.AddRouteManagement(this IServiceCollection)`
- Each method registers exactly the services its csproj owns (concrete types + `IHostedService` registrations).
- `Program.cs` calls the three methods and removes the duplicated lines.
- DI container behavior is byte-equivalent (same lifetimes, same concrete types, same hosted services).
## Scope
### Included
- Three new `*ServiceCollectionExtensions.cs` files (one per csproj).
- Edit `Program.cs` to call them.
- Verify via integration smoke run that DI resolves end-to-end.
### Excluded
- Changing service lifetimes or replacing concrete types.
- Adding `IOptions<>` bindings that aren't already there.
## Acceptance Criteria
**AC-1: Each extension method exists and registers the expected services**
Given the post-refactor source
When inspecting each `*ServiceCollectionExtensions.cs`
Then it registers all services owned by its csproj and nothing else.
**AC-2: Program.cs uses extension methods**
Given `Program.cs` after refactor
When inspecting service registration block
Then `services.AddTileDownloader();`, `services.AddRegionProcessing();`, `services.AddRouteManagement();` appear and the previously-inlined registrations are gone.
**AC-3: DI graph unchanged**
Given the running app
When the smoke integration test profile is executed
Then it passes end-to-end (proving every required service still resolves).
**AC-4: Consumers updated**
Given the post-refactor source
When grepping consumers
Then all references to old service-registration code are removed.
## Constraints
- No service lifetime changes (Singleton stays Singleton, Scoped stays Scoped).
- Hosted services stay hosted services.
- DI registration order preserved where ordering matters.
## Risks & Mitigation
**Risk 1: Hosted-service registration order regression**
- *Risk*: `IHostedService` execution order is registration-order; an accidental reorder could change startup behavior.
- *Mitigation*: Inspect Program.cs Git diff carefully; keep the same call-site order in the three extension methods as in the original file.