refactor(domain): lift UnifiedDataRepository port into domain layer (swamp-club#229)#1297
refactor(domain): lift UnifiedDataRepository port into domain layer (swamp-club#229)#1297
Conversation
…er (swamp-club#229) Move the UnifiedDataRepository interface, OwnershipValidationError class, and GarbageCollectionResult type from src/infrastructure/persistence/ into a new src/domain/data/repositories.ts so the domain layer owns the contract. The infra file imports them back and re-exports for non-domain consumers (cli, libswamp, integration tests). FileSystemUnifiedDataRepository keeps its implements clause against the now-domain-side interface. Switch all 13 production domain importers (and 10 domain test files) of UnifiedDataRepository to import from the new domain path. Update the two data services (rename, delete) to also depend on the domain-side DefinitionRepository instead of the concrete YamlDefinitionRepository — findDefinitionByIdOrName already accepts the interface, so no other call-site changes are needed. Decrement KNOWN_DOMAIN_INFRA_VIOLATIONS in integration/ddd_layer_rules_test.ts from 31 to 22 (down 9 — files where UnifiedDataRepository was the sole infrastructure import). Files that retain other infra imports (CatalogStore, CatalogRow, YamlOutputRepository, FileSystemUnifiedDataRepository concrete class) update their UnifiedDataRepository import path for canonical-source correctness but stay on the violations list — those are deeper refactors out of scope here. Add src/infrastructure/persistence/unified_data_repository_re_export_test.ts to lock the value re-export contract: asserts class identity is preserved across both import paths and that instanceof works against either side. Guards against a future regression that converts the value re-export to a type-only re-export or duplicates the class definition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
Clean, well-structured dependency-inversion refactor. The PR correctly lifts the UnifiedDataRepository interface, OwnershipValidationError class, and GarbageCollectionResult type from the infrastructure layer into src/domain/data/repositories.ts, following the established repositories.ts convention used by definitions, models, workflows, and telemetry.
Blocking Issues
None.
Suggestions
None — this is a textbook DDD port extraction:
- Domain ownership is correct: The interface (port) lives in the domain; the implementation stays in infrastructure. The
FileSystemUnifiedDataRepositorykeeps itsimplementsclause against the domain-side interface. - Re-export contract is locked: The infra file re-exports the moved symbols for backward compatibility, and the new
unified_data_repository_re_export_test.tsverifies class identity andinstanceofacross both import paths — a thoughtful guard against a future regression to type-only re-exports. - Constructor type narrowing is safe:
DataDeleteServiceandDataRenameServicenow accept the abstractDefinitionRepositoryinstead of the concreteYamlDefinitionRepository.findDefinitionByIdOrNamealready accepts the interface, and the call sites in libswamp passYamlDefinitionRepository(which implements the interface), so this is a sound widening. - Ratchet math checks out: 9 files where
UnifiedDataRepositorywas the sole infrastructure import now import from the domain layer. The remaining 22 violations retain other infra imports (CatalogStore,CatalogRow,YamlOutputRepository,FileSystemUnifiedDataRepository) — correctly left for future PRs. - No libswamp import boundary violations: CLI and libswamp code continue to import these types through the infra re-export path, not from the new domain internal path.
- License headers present on both new files.
- Naming matches convention:
src/domain/data/repositories.tsaligns withsrc/domain/definitions/repositories.ts,src/domain/models/repositories.ts, etc.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None found.
Medium
None found.
Low
-
src/domain/data/data_query_service.ts:97-100 — This file still importsCatalogRowandCatalogStorefrom the infra layer (../../infrastructure/persistence/catalog_store.ts). The PR description explicitly calls this out as intentionally out of scope, but it's worth noting that the domainrepositories.tsfile could eventually absorb aCatalogStoreport too. No action needed now — just confirming the tech debt is tracked. -
integration/ddd_layer_rules_test.ts:84 — The ratchet constantKNOWN_DOMAIN_INFRA_VIOLATIONS = 22is correct for this PR, but the comment above it still says "added 4 transitional domain→infrastructure imports" referencing issue #223. If another PR in flight also changes the count, whoever merges second will get a test failure. This is the existing ratchet design working as intended — not a bug. Mentioning because the PR body itself flags this possibility.
Verdict
PASS — This is a textbook dependency-inversion refactor. The UnifiedDataRepository interface, OwnershipValidationError class, and GarbageCollectionResult type move cleanly from infrastructure to domain with zero behavioral change. Key things verified:
- The new
src/domain/data/repositories.tsis a byte-identical lift of the declarations from the infra file (confirmed via diff). - The infra module correctly re-exports
OwnershipValidationErroras a value re-export (not type-only), preservinginstanceofidentity across both import paths. The newunified_data_repository_re_export_test.tslocks this contract. DataDeleteServiceandDataRenameServiceconstructor types widen from concreteYamlDefinitionRepositoryto abstractDefinitionRepository. All 4 instantiation sites (2 production in libswamp, 2 in tests) pass compatible arguments.findDefinitionByIdOrNameonly uses methods defined on theDefinitionRepositoryinterface (findByNameGlobal,findById), so no hidden method-resolution failures.- The DDD ratchet count of 22 was independently verified by enumerating all remaining domain→infra imports (excluding logging/tracing cross-cutting concerns).
- All 27 changed files are mechanical single-line import-path swaps with no logic changes.
Summary
Resolves swamp-club#229. The DDD dependency-inversion fix for the data services added in swamp-club#181 — the
UnifiedDataRepositoryinterface,OwnershipValidationErrorclass, andGarbageCollectionResulttype now live in the domain layer (src/domain/data/repositories.ts), and the infrastructure file imports them back and re-exports for non-domain consumers.The issue body scoped this to the two new data services (rename, delete) and a
−2ratchet decrement. After review we expanded scope to include all production-code domain importers of the interface — the move is mechanical and the canonical-source correctness is worth the slightly larger blast radius. Net ratchet decrement: 31 → 22 (−9).What changed
src/domain/data/repositories.ts— the lifted interface, exception, and result type. Naming matches the existingsrc/domain/definitions/repositories.tsconvention.src/infrastructure/persistence/unified_data_repository_re_export_test.ts— locks the value re-export contract: assertsOwnershipValidationErrorclass identity is preserved across both import paths and thatinstanceofworks from either side. Guards against a future regression that converts the value re-export to a type-only one or duplicates the class definition.src/infrastructure/persistence/unified_data_repository.ts— imports the moved symbols from domain and re-exports them.FileSystemUnifiedDataRepositorykeepsimplements UnifiedDataRepositoryagainst the now-domain-side interface. Drops 354 lines of moved declarations.src/domain/data/data_rename_service.tsanddata_delete_service.ts— switch to domainUnifiedDataRepositoryandDefinitionRepository. The constructor parameter type changes from concreteYamlDefinitionRepositoryto the abstractDefinitionRepository;findDefinitionByIdOrNamealready accepts the interface so no other call-site changes are needed.UnifiedDataRepositoryinterface. Test files are excluded from the DDD ratchet (skip pattern), so they don't affect the count, but updating them keeps the canonical source consistent.integration/ddd_layer_rules_test.ts—KNOWN_DOMAIN_INFRA_VIOLATIONS31 → 22 and removed the now-resolvedswamp-club#229tracking comment.Out of scope
CatalogStore,CatalogRow,YamlOutputRepository, the concreteFileSystemUnifiedDataRepositoryclass) update theirUnifiedDataRepositoryimport path for canonical-source correctness but stay on the violations list. Those are deeper refactors that need an injection point at the construction call-site.Note for reviewers
src/domain/workflows/execution_service.tsends up with two import lines targeting the infra/domain split:import type { UnifiedDataRepository } from "../data/repositories.ts"(interface from domain) andimport { FileSystemUnifiedDataRepository } from "../../infrastructure/persistence/unified_data_repository.ts"(concrete class from infra). This is intentional — interface and implementation belong on opposite sides of the boundary.The DDD ratchet baseline of 31 was current at branch time. If another PR lands first and bumps the count, the target moves to (new baseline − 9). The −9 delta is what this PR delivers.
Test Plan
deno fmt --check,deno lint,deno checkall cleandeno run test integration/ddd_layer_rules_test.ts— passes; reports 22 violationsdeno run test src/infrastructure/persistence/unified_data_repository_re_export_test.ts— class identity and instanceof both verifieddeno run test integration/data_ownership_test.ts integration/unified_data_test.ts— 27/27 pass; confirmsinstanceof OwnershipValidationErrorstill works through the infra re-exportdeno run test integration/data_delete_test.ts— 4/4 pass; exercises the rebuiltDataDeleteServicewith theDefinitionRepositoryconstructor typedeno run compilesucceedsswamp repo init,data list,data delete,data versions,data rename,data gc,workflow run,doctor audit— all parse, route through the changed services, and return clean JSONextensions/does not reference these types