# Code Review Report **Batch**: 4 (refactor 02-coupling-refactoring · Phase A) **Tasks**: AZ-310, AZ-311 **Date**: 2026-05-10 **Verdict**: PASS ## Scope Changed files: - `SatelliteProvider.Common/DTO/TileBytes.cs` (new) - `SatelliteProvider.Common/Interfaces/ITileService.cs` (added 2 methods) - `SatelliteProvider.Services/SatelliteProvider.Services.csproj` (added `Microsoft.Extensions.Caching.Memory` 9.0.10) - `SatelliteProvider.Services/TileService.cs` (added IMemoryCache dependency + 2 methods + private `BuildTileEntity` helper) - `SatelliteProvider.Api/Program.cs` (thinned `ServeTile` and `GetTileByLatLon`; dropped `IMemoryCache`/`ITileRepository`/`ISatelliteDownloader` injections + redundant `using` directives) - `SatelliteProvider.Tests/SatelliteProvider.Tests.csproj` (added `Microsoft.Extensions.Caching.Memory` 9.0.10) - `SatelliteProvider.Tests/TileServiceTests.cs` (5 new tests; constructor helper takes optional `IMemoryCache`) - `SatelliteProvider.Tests/InfrastructureTests.cs` (constructor signature update) ## Phase 1 — Context Both tasks target architecture-baseline F3 (endpoints bypass `ITileService`). Phase A of `02-coupling-refactoring` consolidates tile-serving logic in `TileService`. ## Phase 2 — Spec Compliance | AC | Status | Evidence | |----|--------|----------| | AZ-310 AC-1 | Pass | Route preserved (`Program.cs:113`); response keeps `image/jpeg`, `ETag`, `Cache-Control: public, max-age=86400` | | AZ-310 AC-2 | Pass | `GetOrDownloadTileAsync_CacheHit_ReturnsCachedBytes_AZ310_AC2` (downloader+repo strict-mock asserted untouched) | | AZ-310 AC-3 | Pass | `GetOrDownloadTileAsync_RepoHit_ReadsFromDisk_NoDownloader_AZ310_AC3` (writes a real temp file, asserts no downloader call, no insert) | | AZ-310 AC-4 | Pass | `GetOrDownloadTileAsync_DownloaderFallback_InsertsAndReturnsBytes_AZ310_AC4` (asserts insert + downloader call) | | AZ-310 AC-5 | Pass | `ServeTile(int z, int x, int y, HttpContext, ITileService, ILogger)` — no `ISatelliteDownloader`/`ITileRepository`/`IMemoryCache` | | AZ-311 AC-1 | Pass | Same route, same query params, same `DownloadTileResponse` shape; field-by-field projection in `Program.cs:155-172` | | AZ-311 AC-2 | Pass | `DownloadAndStoreSingleTileAsync_HappyPath_…` + `…_DownloaderThrows_DoesNotInsert_…` cover both branches | | AZ-311 AC-3 | Pass | `GetTileByLatLon(... ITileService, ILogger)` — no `ISatelliteDownloader`/`ITileRepository` | **Constraints check (AZ-310)**: - Cache lifetime: `TimeSpan.FromHours(1)` absolute + `TimeSpan.FromMinutes(30)` sliding — preserved verbatim. - Cache key `tile_{z}_{x}_{y}`, ETag `"{z}_{x}_{y}"` — preserved verbatim. - `Cache-Control: public, max-age=86400` — built from `TileResponseMaxAge = TimeSpan.FromDays(1)` (`(long)TotalSeconds` = 86400). **Constraints check (AZ-311)**: query string and JSON shape unchanged. ## Phase 3 — Code Quality - SOLID: `TileService` now has 5 public methods, all single-responsibility. Private `BuildTileEntity` removes 3-fold duplication of `TileEntity` construction. - Error handling: cancellation token propagates through every async boundary in the new methods. Endpoints still wrap with try/catch + `Results.Problem`. - Tests use `MockBehavior.Strict` where it matters (cache-hit path) and explicit `VerifyNoOtherCalls()` to assert untouched dependencies. - Naming: `GetOrDownloadTileAsync` and `DownloadAndStoreSingleTileAsync` accurately describe behavior. - DRY: deduplication of `TileEntity` construction is a net win. Cache-key + ETag string formats are localized to one method (`GetOrDownloadTileAsync`); externalizing as constants would not improve readability for two-use values. ## Phase 4 — Security Quick-Scan No new SQL string concatenation, no new external input parsing, no secrets. `TileBytes` only carries bytes/headers. ## Phase 5 — Performance Scan Identical I/O pattern as pre-refactor — same number of cache lookups, DB queries, downloads, and file reads. ## Phase 6 — Cross-Task Consistency - AZ-310 and AZ-311 share `BuildTileEntity` — consistent entity construction. - Both new methods accept `CancellationToken cancellationToken = default` — consistent signature style. - Both endpoints follow same flat shape: validate (route binding) → call service → project → return. ## Phase 7 — Architecture Compliance - Layer direction: `TileService` (Layer 2) imports `Microsoft.Extensions.Caching.Memory` (system) — fine. `ITileService` stays in Common (Layer 1). - Public API: no cross-component internal imports added. - No new cycles. - Layer 4 (`Program.cs`) no longer reaches into Layer 1 (`ITileRepository`) for tile serving / single-tile flow — the very thing baseline F3 flagged. - Baseline F3 (`Program.cs/ServeTile bypasses ITileService`) is **resolved by this batch**. Documentation update happens in AZ-315. ## Findings None. ## Verdict **PASS** — no Critical, High, Medium, or Low findings. Build clean (0/0). Unit suite 40/40 green.