refactor(backend): restructure from layer-based to domain-based packaging #407

Closed
opened 2026-05-04 16:13:40 +02:00 by marcel · 10 comments
Owner

Context

Part of Epic #406 — Big-bang restructure. This is REFACTOR-1: move every backend Java file from layer-based packages (controller/, service/, repository/, model/, dto/, exception/, security/, config/) into per-domain packages following the canonical domain set in #387 first comment.

This is a single big-bang PR per the user's decision (LLM-assisted refactor is fast enough that staging isn't worth the per-PR overhead). All tests must remain green throughout.

Target package structure

backend/src/main/java/org/raddatz/familienarchiv/
├── document/                          # Tier 1 domain
│   ├── DocumentController.java
│   ├── DocumentService.java
│   ├── DocumentRepository.java
│   ├── DocumentSpecifications.java
│   ├── Document.java                  # @Entity, table = documents
│   ├── DocumentVersion.java           # @Entity, table = document_versions
│   ├── DocumentVersionService.java
│   ├── DocumentVersionRepository.java
│   ├── DocumentUpdateDTO.java
│   ├── ThumbnailService.java
│   ├── ThumbnailBackfillService.java
│   ├── ThumbnailAsyncRunner.java
│   ├── annotation/                    # sub-package per D-OQ-4
│   │   ├── AnnotationController.java
│   │   ├── AnnotationService.java
│   │   ├── AnnotationRepository.java
│   │   └── DocumentAnnotation.java
│   ├── comment/                       # sub-package per D-OQ-3
│   │   ├── CommentController.java
│   │   ├── CommentService.java
│   │   ├── CommentRepository.java
│   │   └── DocumentComment.java
│   └── transcription/                 # sub-package per D-OQ-2
│       ├── TranscriptionBlockController.java
│       ├── TranscriptionService.java
│       ├── TranscriptionBlock.java
│       ├── TranscriptionBlockVersion.java
│       ├── TranscriptionBlockRepository.java
│       ├── TranscriptionBlockVersionRepository.java
│       ├── TranscriptionBlockQueryService.java
│       └── PersonMention.java         # @ElementCollection on TranscriptionBlock
├── person/                            # Tier 1 domain
│   ├── PersonController.java
│   ├── PersonService.java
│   ├── PersonRepository.java
│   ├── PersonNameAliasRepository.java
│   ├── Person.java
│   ├── PersonNameAlias.java
│   ├── PersonNameParser.java
│   ├── PersonTypeClassifier.java
│   └── relationship/                  # sub-package per D-OQ-7
│       ├── RelationshipController.java
│       ├── RelationshipService.java
│       ├── RelationshipInferenceService.java
│       ├── PersonRelationship.java
│       └── PersonRelationshipRepository.java
├── tag/                               # Tier 1 domain
│   ├── TagController.java
│   ├── TagService.java
│   ├── TagRepository.java
│   └── Tag.java
├── user/                              # Tier 1 domain
│   ├── AuthController.java
│   ├── UserController.java
│   ├── UserService.java
│   ├── UserSearchService.java
│   ├── GroupController.java
│   ├── InviteController.java
│   ├── InviteService.java
│   ├── PasswordResetService.java
│   ├── CustomUserDetailsService.java
│   ├── AppUser.java                   # table = users
│   ├── UserGroup.java
│   ├── InviteToken.java
│   ├── PasswordResetToken.java
│   ├── AppUserRepository.java
│   ├── UserGroupRepository.java
│   ├── InviteTokenRepository.java
│   └── PasswordResetTokenRepository.java
├── geschichte/                        # Tier 1 domain
│   ├── GeschichteController.java
│   ├── GeschichteService.java
│   ├── GeschichteRepository.java
│   ├── GeschichteSpecifications.java
│   ├── Geschichte.java
│   └── GeschichteStatus.java
├── notification/                      # Tier 1 domain per D-OQ-6
│   ├── NotificationController.java
│   ├── NotificationService.java
│   ├── NotificationRepository.java
│   ├── Notification.java
│   └── NotificationType.java
├── ocr/                               # Tier 1 domain per D-OQ-5
│   ├── OcrController.java
│   ├── OcrService.java
│   ├── OcrBatchService.java
│   ├── OcrProgressService.java
│   ├── OcrTrainingService.java
│   ├── OcrAsyncRunner.java
│   ├── OcrClient.java
│   ├── RestClientOcrClient.java
│   ├── OcrHealthClient.java
│   ├── SenderModelService.java
│   ├── TrainingDataExportService.java
│   ├── SegmentationTrainingExportService.java
│   ├── OcrJob.java
│   ├── OcrJobDocument.java
│   ├── OcrTrainingRun.java
│   ├── TrainingLabel.java
│   ├── SenderModel.java
│   ├── OcrJobRepository.java
│   ├── OcrJobDocumentRepository.java
│   ├── OcrTrainingRunRepository.java
│   ├── SenderModelRepository.java
│   ├── OcrJobStatus.java
│   ├── OcrDocumentStatus.java
│   ├── TrainingStatus.java
│   └── ScriptType.java
├── conversation/                      # Tier 2 derived per D-OQ-1
│   └── (currently empty — backend has no conversation entities; if any
│        thin query module exists for /briefwechsel data, place it here
│        with a README explaining the "derived domain" pattern)
├── activity/                          # Tier 2 derived per D-OQ-1
│   └── (similarly empty — derived from audit/notification/document)
└── shared/                            # Cross-cutting per D-OQ-8
    ├── audit/
    │   ├── AuditService.java
    │   ├── AuditLog.java
    │   ├── AuditLogRepository.java
    │   ├── AuditLogQueryRepository.java
    │   └── AuditKind.java
    ├── filestorage/
    │   ├── FileService.java
    │   └── MinioConfig.java
    ├── importing/
    │   └── MassImportService.java
    ├── dashboard/
    │   ├── DashboardController.java
    │   └── DashboardService.java
    ├── transcriptionqueue/
    │   ├── TranscriptionQueueController.java
    │   ├── TranscriptionQueueService.java
    │   ├── TranscriptionQueueProjection.java
    │   └── TranscriptionWeeklyStatsProjection.java
    ├── stats/
    │   └── StatsController.java
    ├── admin/
    │   └── AdminController.java
    ├── security/
    │   ├── SecurityConfig.java
    │   ├── Permission.java
    │   ├── RequirePermission.java
    │   ├── PermissionAspect.java
    │   └── SecurityUtils.java
    ├── exception/
    │   ├── DomainException.java
    │   ├── ErrorCode.java
    │   └── GlobalExceptionHandler.java
    ├── config/
    │   ├── AsyncConfig.java
    │   ├── WebConfig.java
    │   ├── FlywayConfig.java
    │   └── RateLimitInterceptor.java
    ├── sse/
    │   └── SseEmitterRegistry.java
    └── util/
        ├── DisplayNameFormatter.java
        ├── PolygonConverter.java
        └── (other genuine utilities)

Process

  1. Create the new packages first (empty)
  2. Move classes one domain at a time using IntelliJ's Move Class refactoring (preserves imports and references) OR use git mv + global search-and-replace on package declarations
  3. After each domain move, run ./mvnw test — must be green
  4. Update package-info.java files where present
  5. Move the corresponding test classes to mirror packages under backend/src/test/java/
  6. Delete the empty old packages (controller/, service/, repository/, model/, dto/)
  7. Update backend/CLAUDE.md "Package Structure" section to reflect the new layout
  8. Single PR with all moves; commit messages can be per-domain ("refactor(backend): move document domain to package", "refactor(backend): move person domain to package", etc.) but the PR is one unit

Anti-patterns

  • Do NOT change behavior. No method signatures, no method bodies, no DTO shapes, no API endpoints.
  • Do NOT introduce new abstractions. Move only.
  • Do NOT skip the ./mvnw test after each domain move. Catch breakage early.
  • Do NOT mix the move with the ArchUnit rule addition (REFACTOR-3 is a separate issue).

Acceptance criteria

  • Every controller/service/repository/entity/DTO is in its target domain package per the structure above
  • Old layered packages (controller/, service/, repository/, model/, dto/, exception/, security/, config/) no longer exist
  • All Tier-1 domains have at least their controller, service, repository, entity, and any DTOs co-located
  • Sub-packages exist for document.{annotation,comment,transcription} and person.relationship
  • shared/ contains only items meeting the admission criteria (no entity, no user-facing CRUD, ≥2 consumers OR framework infra)
  • No method signature, body, or behavior has changed (verified by full test suite passing AND OpenAPI spec diff being zero except for type FQNs)
  • ./mvnw test is green
  • backend/CLAUDE.md reflects the new structure
  • Frontend npm run generate:api runs cleanly against the rebuilt backend (consumer-side smoke)
  • PR opened, reviewed, merged

Dependency

Hard blocked by: Epic 1 (#387) — need audit verdict; Epic 3 (#402) — need test trustworthiness; AUDIT-6 (#393) refactor-readiness verdict 🟢 or 🟡-with-prereqs-met.

Definition of Done

PR merged. Closing comment lists the final domain count, sub-package list, and shared/ member list for the human-readable docs (DOC-2, DOC-6) to consume.

## Context Part of **Epic #406** — Big-bang restructure. This is **REFACTOR-1**: move every backend Java file from layer-based packages (`controller/`, `service/`, `repository/`, `model/`, `dto/`, `exception/`, `security/`, `config/`) into per-domain packages following the canonical domain set in #387 first comment. This is a **single big-bang PR** per the user's decision (LLM-assisted refactor is fast enough that staging isn't worth the per-PR overhead). All tests must remain green throughout. ## Target package structure ``` backend/src/main/java/org/raddatz/familienarchiv/ ├── document/ # Tier 1 domain │ ├── DocumentController.java │ ├── DocumentService.java │ ├── DocumentRepository.java │ ├── DocumentSpecifications.java │ ├── Document.java # @Entity, table = documents │ ├── DocumentVersion.java # @Entity, table = document_versions │ ├── DocumentVersionService.java │ ├── DocumentVersionRepository.java │ ├── DocumentUpdateDTO.java │ ├── ThumbnailService.java │ ├── ThumbnailBackfillService.java │ ├── ThumbnailAsyncRunner.java │ ├── annotation/ # sub-package per D-OQ-4 │ │ ├── AnnotationController.java │ │ ├── AnnotationService.java │ │ ├── AnnotationRepository.java │ │ └── DocumentAnnotation.java │ ├── comment/ # sub-package per D-OQ-3 │ │ ├── CommentController.java │ │ ├── CommentService.java │ │ ├── CommentRepository.java │ │ └── DocumentComment.java │ └── transcription/ # sub-package per D-OQ-2 │ ├── TranscriptionBlockController.java │ ├── TranscriptionService.java │ ├── TranscriptionBlock.java │ ├── TranscriptionBlockVersion.java │ ├── TranscriptionBlockRepository.java │ ├── TranscriptionBlockVersionRepository.java │ ├── TranscriptionBlockQueryService.java │ └── PersonMention.java # @ElementCollection on TranscriptionBlock ├── person/ # Tier 1 domain │ ├── PersonController.java │ ├── PersonService.java │ ├── PersonRepository.java │ ├── PersonNameAliasRepository.java │ ├── Person.java │ ├── PersonNameAlias.java │ ├── PersonNameParser.java │ ├── PersonTypeClassifier.java │ └── relationship/ # sub-package per D-OQ-7 │ ├── RelationshipController.java │ ├── RelationshipService.java │ ├── RelationshipInferenceService.java │ ├── PersonRelationship.java │ └── PersonRelationshipRepository.java ├── tag/ # Tier 1 domain │ ├── TagController.java │ ├── TagService.java │ ├── TagRepository.java │ └── Tag.java ├── user/ # Tier 1 domain │ ├── AuthController.java │ ├── UserController.java │ ├── UserService.java │ ├── UserSearchService.java │ ├── GroupController.java │ ├── InviteController.java │ ├── InviteService.java │ ├── PasswordResetService.java │ ├── CustomUserDetailsService.java │ ├── AppUser.java # table = users │ ├── UserGroup.java │ ├── InviteToken.java │ ├── PasswordResetToken.java │ ├── AppUserRepository.java │ ├── UserGroupRepository.java │ ├── InviteTokenRepository.java │ └── PasswordResetTokenRepository.java ├── geschichte/ # Tier 1 domain │ ├── GeschichteController.java │ ├── GeschichteService.java │ ├── GeschichteRepository.java │ ├── GeschichteSpecifications.java │ ├── Geschichte.java │ └── GeschichteStatus.java ├── notification/ # Tier 1 domain per D-OQ-6 │ ├── NotificationController.java │ ├── NotificationService.java │ ├── NotificationRepository.java │ ├── Notification.java │ └── NotificationType.java ├── ocr/ # Tier 1 domain per D-OQ-5 │ ├── OcrController.java │ ├── OcrService.java │ ├── OcrBatchService.java │ ├── OcrProgressService.java │ ├── OcrTrainingService.java │ ├── OcrAsyncRunner.java │ ├── OcrClient.java │ ├── RestClientOcrClient.java │ ├── OcrHealthClient.java │ ├── SenderModelService.java │ ├── TrainingDataExportService.java │ ├── SegmentationTrainingExportService.java │ ├── OcrJob.java │ ├── OcrJobDocument.java │ ├── OcrTrainingRun.java │ ├── TrainingLabel.java │ ├── SenderModel.java │ ├── OcrJobRepository.java │ ├── OcrJobDocumentRepository.java │ ├── OcrTrainingRunRepository.java │ ├── SenderModelRepository.java │ ├── OcrJobStatus.java │ ├── OcrDocumentStatus.java │ ├── TrainingStatus.java │ └── ScriptType.java ├── conversation/ # Tier 2 derived per D-OQ-1 │ └── (currently empty — backend has no conversation entities; if any │ thin query module exists for /briefwechsel data, place it here │ with a README explaining the "derived domain" pattern) ├── activity/ # Tier 2 derived per D-OQ-1 │ └── (similarly empty — derived from audit/notification/document) └── shared/ # Cross-cutting per D-OQ-8 ├── audit/ │ ├── AuditService.java │ ├── AuditLog.java │ ├── AuditLogRepository.java │ ├── AuditLogQueryRepository.java │ └── AuditKind.java ├── filestorage/ │ ├── FileService.java │ └── MinioConfig.java ├── importing/ │ └── MassImportService.java ├── dashboard/ │ ├── DashboardController.java │ └── DashboardService.java ├── transcriptionqueue/ │ ├── TranscriptionQueueController.java │ ├── TranscriptionQueueService.java │ ├── TranscriptionQueueProjection.java │ └── TranscriptionWeeklyStatsProjection.java ├── stats/ │ └── StatsController.java ├── admin/ │ └── AdminController.java ├── security/ │ ├── SecurityConfig.java │ ├── Permission.java │ ├── RequirePermission.java │ ├── PermissionAspect.java │ └── SecurityUtils.java ├── exception/ │ ├── DomainException.java │ ├── ErrorCode.java │ └── GlobalExceptionHandler.java ├── config/ │ ├── AsyncConfig.java │ ├── WebConfig.java │ ├── FlywayConfig.java │ └── RateLimitInterceptor.java ├── sse/ │ └── SseEmitterRegistry.java └── util/ ├── DisplayNameFormatter.java ├── PolygonConverter.java └── (other genuine utilities) ``` ## Process 1. Create the new packages first (empty) 2. Move classes one domain at a time using IntelliJ's Move Class refactoring (preserves imports and references) OR use `git mv` + global search-and-replace on package declarations 3. After each domain move, run `./mvnw test` — must be green 4. Update package-info.java files where present 5. Move the corresponding test classes to mirror packages under `backend/src/test/java/` 6. Delete the empty old packages (`controller/`, `service/`, `repository/`, `model/`, `dto/`) 7. Update `backend/CLAUDE.md` "Package Structure" section to reflect the new layout 8. Single PR with all moves; commit messages can be per-domain ("refactor(backend): move document domain to package", "refactor(backend): move person domain to package", etc.) but the PR is one unit ## Anti-patterns - Do NOT change behavior. No method signatures, no method bodies, no DTO shapes, no API endpoints. - Do NOT introduce new abstractions. Move only. - Do NOT skip the `./mvnw test` after each domain move. Catch breakage early. - Do NOT mix the move with the ArchUnit rule addition (REFACTOR-3 is a separate issue). ## Acceptance criteria - [ ] Every controller/service/repository/entity/DTO is in its target domain package per the structure above - [ ] Old layered packages (`controller/`, `service/`, `repository/`, `model/`, `dto/`, `exception/`, `security/`, `config/`) no longer exist - [ ] All Tier-1 domains have at least their controller, service, repository, entity, and any DTOs co-located - [ ] Sub-packages exist for `document.{annotation,comment,transcription}` and `person.relationship` - [ ] `shared/` contains only items meeting the admission criteria (no entity, no user-facing CRUD, ≥2 consumers OR framework infra) - [ ] No method signature, body, or behavior has changed (verified by full test suite passing AND OpenAPI spec diff being zero except for type FQNs) - [ ] `./mvnw test` is green - [ ] `backend/CLAUDE.md` reflects the new structure - [ ] Frontend `npm run generate:api` runs cleanly against the rebuilt backend (consumer-side smoke) - [ ] PR opened, reviewed, merged ## Dependency **Hard blocked by:** Epic 1 (#387) — need audit verdict; Epic 3 (#402) — need test trustworthiness; AUDIT-6 (#393) refactor-readiness verdict 🟢 or 🟡-with-prereqs-met. ## Definition of Done PR merged. Closing comment lists the final domain count, sub-package list, and `shared/` member list for the human-readable docs (DOC-2, DOC-6) to consume.
marcel added this to the (deleted) milestone 2026-05-04 16:13:40 +02:00
marcel added the P1-highlegibilityrefactor labels 2026-05-04 16:16:05 +02:00
Author
Owner

🏗️ Markus Keller — Senior Application Architect

Observations

  • The target structure is exactly right. Package-by-feature was always the stated goal (it's in my persona file, it's in CLAUDE.md), and this issue finally executes on it. At 212 production files spread across controller/, service/, repository/, model/, dto/ — all with zero domain boundaries — the move is overdue.
  • The shared/ admission criteria (no entity, no user-facing CRUD, ≥2 consumers OR framework infra) is a clean, enforceable rule. Good.
  • The big-bang PR decision is defensible given LLM-assisted execution. The per-domain ./mvnw test checkpoints in the process section mitigate the risk of a single invisible regression.
  • audit/ and dashboard/ already partially escaped the flat structure — they have their own packages today. The refactor makes this consistent rather than introducing a new pattern.
  • Unresolved placement: DataInitializer.java currently lives in config/. It seeds test users (via @Profile("e2e")) and the admin user. The target structure puts it in shared/config/ — but it injects AppUserRepository and UserGroupRepository directly, making it a user/ domain concern wrapped in a @Configuration. Putting it in shared/config/ would violate the stated admission criteria. Consider moving it to user/ as UserDataInitializer.java.
  • AuthE2EController.java is @Profile("e2e") only and lives in controller/. The target puts it... nowhere — it's not listed in the issue's target structure. It should land in user/ (it manages auth sessions for E2E test users). Flag this before implementation.
  • The conversation/ and activity/ placeholder packages with README-only content are a good pattern for signaling intent without forcing premature implementation. Keep them.

Recommendations

  • Move DataInitializer to user/ as UserDataInitializer — it owns user/group seeding logic, not generic config.
  • Add AuthE2EController explicitly to the target structure under user/ before the PR starts. Missing it will cause a post-move "where does this go?" pause mid-refactor.
  • The shared/importing/ slot has only MassImportService. Verify it meets ≥2-consumers admission criteria: it calls PersonService, TagService, DocumentService — so it genuinely is cross-domain. Confirmed valid for shared/.
  • Run ./mvnw test baseline before any move so you know the starting green count. If the baseline has failures, record them — don't refactor on red.
  • The process says "update backend/CLAUDE.md". Also update the root CLAUDE.md Package Structure section — it currently documents the old layer-based layout.

Open Decisions

  • DataInitializer placementshared/config/ (where it currently lives conceptually) vs user/ (where its domain dependencies say it belongs). The current issue text doesn't resolve this. user/ is architecturally correct but requires renaming the class; shared/config/ is pragmatic but leaks user domain logic into infrastructure config.
## 🏗️ Markus Keller — Senior Application Architect ### Observations - The target structure is exactly right. Package-by-feature was always the stated goal (it's in my persona file, it's in CLAUDE.md), and this issue finally executes on it. At 212 production files spread across `controller/`, `service/`, `repository/`, `model/`, `dto/` — all with zero domain boundaries — the move is overdue. - The `shared/` admission criteria (no entity, no user-facing CRUD, ≥2 consumers OR framework infra) is a clean, enforceable rule. Good. - The big-bang PR decision is defensible given LLM-assisted execution. The per-domain `./mvnw test` checkpoints in the process section mitigate the risk of a single invisible regression. - `audit/` and `dashboard/` already partially escaped the flat structure — they have their own packages today. The refactor makes this consistent rather than introducing a new pattern. - **Unresolved placement: `DataInitializer.java`** currently lives in `config/`. It seeds test users (via `@Profile("e2e")`) and the admin user. The target structure puts it in `shared/config/` — but it injects `AppUserRepository` and `UserGroupRepository` directly, making it a `user/` domain concern wrapped in a `@Configuration`. Putting it in `shared/config/` would violate the stated admission criteria. Consider moving it to `user/` as `UserDataInitializer.java`. - **`AuthE2EController.java`** is `@Profile("e2e")` only and lives in `controller/`. The target puts it... nowhere — it's not listed in the issue's target structure. It should land in `user/` (it manages auth sessions for E2E test users). Flag this before implementation. - The `conversation/` and `activity/` placeholder packages with README-only content are a good pattern for signaling intent without forcing premature implementation. Keep them. ### Recommendations - Move `DataInitializer` to `user/` as `UserDataInitializer` — it owns user/group seeding logic, not generic config. - Add `AuthE2EController` explicitly to the target structure under `user/` before the PR starts. Missing it will cause a post-move "where does this go?" pause mid-refactor. - The `shared/importing/` slot has only `MassImportService`. Verify it meets ≥2-consumers admission criteria: it calls `PersonService`, `TagService`, `DocumentService` — so it genuinely is cross-domain. Confirmed valid for `shared/`. - Run `./mvnw test` baseline before any move so you know the starting green count. If the baseline has failures, record them — don't refactor on red. - The process says "update `backend/CLAUDE.md`". Also update the root `CLAUDE.md` Package Structure section — it currently documents the old layer-based layout. ### Open Decisions - **`DataInitializer` placement** — `shared/config/` (where it currently lives conceptually) vs `user/` (where its domain dependencies say it belongs). The current issue text doesn't resolve this. `user/` is architecturally correct but requires renaming the class; `shared/config/` is pragmatic but leaks user domain logic into infrastructure config.
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Observations

  • 212 production Java files, 92 test files, all currently in layer-based packages. The move is mechanical but the volume is real — the ./mvnw test checkpoint after each domain is the right safeguard, not optional.
  • The test mirror requirement ("move corresponding test classes to mirror packages") is the most error-prone step. Currently, tests live in controller/, service/, repository/, model/. Each test file hardcodes @WebMvcTest(DocumentController.class) and @InjectMocks DocumentService — those annotations reference class names, not packages, so they survive a move without source changes. However, @Import({SecurityConfig.class, PermissionAspect.class}) in @WebMvcTest slices does reference classes by FQN in some configurations. Verify before moving security config.
  • The issue says "No method signature, body, or behavior has changed (verified by full test suite passing AND OpenAPI spec diff being zero except for type FQNs)". The OpenAPI spec diff being zero except for FQNs is the key — @Schema annotations don't include package info, so the JSON output should be identical. Run a before/after diff on /v3/api-docs output to confirm.
  • OcrBlockResult.java currently lives in service/ but is not a service — it's a value object / result type used by OCR services. The target structure places it in ocr/. This is one of the non-obvious placements to watch during the move.
  • The relationship/ package already exists as a separate non-layer package today. It just needs to move from org.raddatz.familienarchiv.relationship to org.raddatz.familienarchiv.person.relationship — a sub-package move, not a flat file move.

Recommendations

  • Concrete move order: start with tag/ (smallest, fewest dependencies: TagController, TagService, TagRepository, Tag, TagUpdateDTO, MergeTagDTO). Run tests. Confidence established. Then proceed up in complexity.
  • git mv + global find/replace is preferable to IDE Move Class for this volume. IntelliJ's Move Class works great for one class at a time; at 200+ files across 8 domains in one session, it's slower and more likely to cause merge headaches than a scripted rename.
  • After each domain move: ./mvnw test -pl backend and grep -rn "import org.raddatz.familienarchiv.old_package" src/ to verify no stale imports remain.
  • The frontend/npm run generate:api smoke test in the acceptance criteria is the right safety net for catching any schema shape changes that tests don't catch.
  • PersonNameParser.java and PersonTypeClassifier.java currently live in service/ but are pure utility/domain-logic classes with no Spring annotations. Moving them to person/ is correct. Double-check they don't have @Service on them — if they do, the component scan needs no change; if they don't, they're just static helpers and move cleanly.

Open Decisions

None — the move order and tooling choice are implementation details that can be decided at execution time without blocking the issue.

## 👨‍💻 Felix Brandt — Senior Fullstack Developer ### Observations - 212 production Java files, 92 test files, all currently in layer-based packages. The move is mechanical but the volume is real — the `./mvnw test` checkpoint after each domain is the right safeguard, not optional. - The test mirror requirement ("move corresponding test classes to mirror packages") is the most error-prone step. Currently, tests live in `controller/`, `service/`, `repository/`, `model/`. Each test file hardcodes `@WebMvcTest(DocumentController.class)` and `@InjectMocks DocumentService` — those annotations reference class names, not packages, so they survive a move without source changes. However, `@Import({SecurityConfig.class, PermissionAspect.class})` in `@WebMvcTest` slices _does_ reference classes by FQN in some configurations. Verify before moving security config. - The issue says "No method signature, body, or behavior has changed (verified by full test suite passing AND OpenAPI spec diff being zero except for type FQNs)". The OpenAPI spec diff being zero _except for FQNs_ is the key — `@Schema` annotations don't include package info, so the JSON output should be identical. Run a before/after `diff` on `/v3/api-docs` output to confirm. - `OcrBlockResult.java` currently lives in `service/` but is not a service — it's a value object / result type used by OCR services. The target structure places it in `ocr/`. This is one of the non-obvious placements to watch during the move. - The `relationship/` package already exists as a separate non-layer package today. It just needs to move from `org.raddatz.familienarchiv.relationship` to `org.raddatz.familienarchiv.person.relationship` — a sub-package move, not a flat file move. ### Recommendations - **Concrete move order**: start with `tag/` (smallest, fewest dependencies: `TagController`, `TagService`, `TagRepository`, `Tag`, `TagUpdateDTO`, `MergeTagDTO`). Run tests. Confidence established. Then proceed up in complexity. - **`git mv` + global find/replace** is preferable to IDE Move Class for this volume. IntelliJ's Move Class works great for one class at a time; at 200+ files across 8 domains in one session, it's slower and more likely to cause merge headaches than a scripted rename. - After each domain move: `./mvnw test -pl backend` and `grep -rn "import org.raddatz.familienarchiv.old_package" src/` to verify no stale imports remain. - The `frontend/npm run generate:api` smoke test in the acceptance criteria is the right safety net for catching any schema shape changes that tests don't catch. - **`PersonNameParser.java` and `PersonTypeClassifier.java`** currently live in `service/` but are pure utility/domain-logic classes with no Spring annotations. Moving them to `person/` is correct. Double-check they don't have `@Service` on them — if they do, the component scan needs no change; if they don't, they're just static helpers and move cleanly. ### Open Decisions None — the move order and tooling choice are implementation details that can be decided at execution time without blocking the issue.
Author
Owner

🔒 Nora "NullX" Steiner — Application Security Engineer

Observations

  • This is a pure structural refactor with a hard anti-pattern rule: "Do NOT change behavior." From a security perspective, that's the correct constraint. The risk surface here is accidental rather than adversarial.
  • SecurityConfig.java move is the highest-risk individual file move. It's currently in security/ (which maps to shared/security/). Spring Security's component scan picks up SecurityConfig by classpath scanning. Moving it to a sub-package (shared/security/) doesn't break anything — Spring Boot's @SpringBootApplication scans all sub-packages from the root. Confirmed safe.
  • PermissionAspect.java is referenced by FQN in several @WebMvcTest @Import annotations. I spot-checked DocumentControllerTest — it has @Import({SecurityConfig.class, PermissionAspect.class}). After the move, these FQN references in test @Import must be updated. This is not a behavior change but it is a source change that the issue's process step doesn't explicitly call out. Add it to the checklist.
  • AuthE2EController.java has @Profile("e2e") — it only loads in the e2e test profile. It directly accesses PasswordResetTokenRepository. If it moves to user/, the import chain stays clean. If it stays misplaced or lands in shared/, the cross-domain repository access violation becomes visible. Use the move as a forcing function to clean this up.
  • Permission.java and RequirePermission.java moving to shared/security/ is correct. They're framework infrastructure used by every domain — they have no domain-specific logic. Their import paths will change (org.raddatz.familienarchiv.security.Permissionorg.raddatz.familienarchiv.shared.security.Permission), which will affect every controller that uses @RequirePermission. This is the highest import-count change in the codebase — Permission and RequirePermission are each imported 17-19 times. Do this domain last or in its own atomic commit.
  • No new attack surface is introduced by this refactor. The security model (AOP-enforced @RequirePermission, centralized SecurityConfig, DomainException error handling) is untouched.

Recommendations

  • Add an explicit checklist item: "Update all @Import({SecurityConfig.class, PermissionAspect.class}) references in test classes after moving shared/security/."
  • Move shared/security/ as its own separate commit (not bundled with another domain). It has the highest fanout of any individual package move — isolating it makes the diff reviewable and rollback clean if something breaks.
  • After the full refactor, run grep -rn "import org.raddatz.familienarchiv.security" src/ — the result should be zero. Any remaining hits are missed updates.
  • The OpenAPI spec diff check (acceptance criteria item 6) implicitly validates that no endpoint's security annotations were accidentally dropped. This is a good secondary security gate.

Open Decisions

None — all security-relevant placement decisions (SecurityConfig, PermissionAspect, Permission enum all in shared/security/) are clearly specified in the issue.

## 🔒 Nora "NullX" Steiner — Application Security Engineer ### Observations - This is a pure structural refactor with a hard anti-pattern rule: "Do NOT change behavior." From a security perspective, that's the correct constraint. The risk surface here is accidental rather than adversarial. - **`SecurityConfig.java` move** is the highest-risk individual file move. It's currently in `security/` (which maps to `shared/security/`). Spring Security's component scan picks up `SecurityConfig` by classpath scanning. Moving it to a sub-package (`shared/security/`) doesn't break anything — Spring Boot's `@SpringBootApplication` scans all sub-packages from the root. Confirmed safe. - **`PermissionAspect.java`** is referenced by FQN in several `@WebMvcTest` `@Import` annotations. I spot-checked `DocumentControllerTest` — it has `@Import({SecurityConfig.class, PermissionAspect.class})`. After the move, these FQN references in test `@Import` must be updated. This is not a behavior change but it _is_ a source change that the issue's process step doesn't explicitly call out. Add it to the checklist. - **`AuthE2EController.java`** has `@Profile("e2e")` — it only loads in the e2e test profile. It directly accesses `PasswordResetTokenRepository`. If it moves to `user/`, the import chain stays clean. If it stays misplaced or lands in `shared/`, the cross-domain repository access violation becomes visible. Use the move as a forcing function to clean this up. - **`Permission.java` and `RequirePermission.java`** moving to `shared/security/` is correct. They're framework infrastructure used by every domain — they have no domain-specific logic. Their import paths will change (`org.raddatz.familienarchiv.security.Permission` → `org.raddatz.familienarchiv.shared.security.Permission`), which will affect every controller that uses `@RequirePermission`. This is the highest import-count change in the codebase — `Permission` and `RequirePermission` are each imported 17-19 times. Do this domain last or in its own atomic commit. - No new attack surface is introduced by this refactor. The security model (AOP-enforced `@RequirePermission`, centralized `SecurityConfig`, `DomainException` error handling) is untouched. ### Recommendations - Add an explicit checklist item: "Update all `@Import({SecurityConfig.class, PermissionAspect.class})` references in test classes after moving `shared/security/`." - Move `shared/security/` as its own separate commit (not bundled with another domain). It has the highest fanout of any individual package move — isolating it makes the diff reviewable and rollback clean if something breaks. - After the full refactor, run `grep -rn "import org.raddatz.familienarchiv.security" src/` — the result should be zero. Any remaining hits are missed updates. - The OpenAPI spec diff check (acceptance criteria item 6) implicitly validates that no endpoint's security annotations were accidentally dropped. This is a good secondary security gate. ### Open Decisions None — all security-relevant placement decisions (SecurityConfig, PermissionAspect, Permission enum all in `shared/security/`) are clearly specified in the issue.
Author
Owner

🧪 Sara Holt — QA Engineer & Test Strategist

Observations

  • 92 test files currently mirror the old layer-based structure: controller/, service/, repository/, model/. The issue says to mirror them into the new domain packages. This is a large rename that must be executed alongside each domain move — not deferred to a cleanup pass.
  • The acceptance criteria include ./mvnw test is green but do not specify which test run catches what. Current test distribution: ~20 controller tests in controller/, ~35 service tests in service/, ~12 repository tests in repository/, ~4 model tests in model/. After the move, a developer should be able to run ./mvnw test -Dtest="document.*" and get only document-domain tests. That's the legibility win.
  • ApplicationContextTest in the root package (org.raddatz.familienarchiv) is a full @SpringBootTest smoke test. It will continue to work regardless of package reorganization since it loads the full context. It's the safety net for catching "Spring can't wire the context after the move" failures. Confirm it passes after each domain move, not just at the end.
  • The issue does not include a test count checkpoint. Recommend adding: after each domain move, ./mvnw test should report the same number of tests run as the baseline. A passing build with fewer tests than before means some tests were accidentally dropped (file moved but not the test, or test file deleted instead of moved).
  • PostgresContainerConfig.java currently lives in the test root (org.raddatz.familienarchiv). It's shared infrastructure for all integration tests. It should stay in the test root or move to shared/ equivalent in test — do not scatter it into individual domain test packages.
  • Specific risk: DocumentControllerTest, GeschichteControllerTest, and PersonControllerTest all use @WebMvcTest with security imports. After moving SecurityConfig and PermissionAspect to shared/security/, these test class @Import declarations will have compile errors until updated. This is a systematic change across ~15 controller test files.

Recommendations

  • Add a test count baseline check to the process: run ./mvnw test | grep "Tests run:" before any move and record the total. After each domain move, verify the count doesn't drop.
  • Explicitly list PostgresContainerConfig as staying in the test root package (org.raddatz.familienarchiv) in the acceptance criteria. Its current location is correct and it should not be moved.
  • The @Import updates for security classes across controller tests are a systematic change — do them in a single commit tagged "refactor(test): update security imports after shared/security move" to keep the diff readable.
  • The frontend/npm run generate:api smoke test in acceptance criteria is the right consumer-side gate. Add the specific assertion: the TypeScript type count in the generated file should match before and after (no types lost or duplicated).

Open Decisions

None — test strategy for a pure refactor is straightforward: green before = green after, same count, same behavior.

## 🧪 Sara Holt — QA Engineer & Test Strategist ### Observations - 92 test files currently mirror the old layer-based structure: `controller/`, `service/`, `repository/`, `model/`. The issue says to mirror them into the new domain packages. This is a large rename that must be executed alongside each domain move — not deferred to a cleanup pass. - The acceptance criteria include `./mvnw test` is green but do not specify _which_ test run catches what. Current test distribution: ~20 controller tests in `controller/`, ~35 service tests in `service/`, ~12 repository tests in `repository/`, ~4 model tests in `model/`. After the move, a developer should be able to run `./mvnw test -Dtest="document.*"` and get only document-domain tests. That's the legibility win. - **`ApplicationContextTest`** in the root package (`org.raddatz.familienarchiv`) is a full `@SpringBootTest` smoke test. It will continue to work regardless of package reorganization since it loads the full context. It's the safety net for catching "Spring can't wire the context after the move" failures. Confirm it passes after _each_ domain move, not just at the end. - The issue does _not_ include a test count checkpoint. Recommend adding: after each domain move, `./mvnw test` should report the same number of tests run as the baseline. A passing build with fewer tests than before means some tests were accidentally dropped (file moved but not the test, or test file deleted instead of moved). - **`PostgresContainerConfig.java`** currently lives in the test root (`org.raddatz.familienarchiv`). It's shared infrastructure for all integration tests. It should stay in the test root or move to `shared/` equivalent in test — do not scatter it into individual domain test packages. - Specific risk: `DocumentControllerTest`, `GeschichteControllerTest`, and `PersonControllerTest` all use `@WebMvcTest` with security imports. After moving `SecurityConfig` and `PermissionAspect` to `shared/security/`, these test class `@Import` declarations will have compile errors until updated. This is a systematic change across ~15 controller test files. ### Recommendations - Add a test count baseline check to the process: run `./mvnw test | grep "Tests run:"` before any move and record the total. After each domain move, verify the count doesn't drop. - Explicitly list `PostgresContainerConfig` as staying in the test root package (`org.raddatz.familienarchiv`) in the acceptance criteria. Its current location is correct and it should not be moved. - The `@Import` updates for security classes across controller tests are a systematic change — do them in a single commit tagged "refactor(test): update security imports after shared/security move" to keep the diff readable. - The `frontend/npm run generate:api` smoke test in acceptance criteria is the right consumer-side gate. Add the specific assertion: the TypeScript type count in the generated file should match before and after (no types lost or duplicated). ### Open Decisions None — test strategy for a pure refactor is straightforward: green before = green after, same count, same behavior.
Author
Owner

🚀 Tobias Wendt — DevOps & Platform Engineer

Observations

  • This is a pure Java package rename. No Docker Compose changes, no CI pipeline changes, no environment variable changes, no Flyway migrations. Infrastructure impact: zero.
  • The one CI-relevant implication: if the Gitea Actions CI workflow runs ./mvnw test (it should), the workflow will pass or fail based on the same criteria as local execution. No runner config changes needed.
  • The acceptance criteria include frontend/npm run generate:api — this requires the backend to be running with --spring.profiles.active=dev. In CI, this step either requires the backend to be started as a service container or run as part of a multi-step job. Check whether the existing CI workflow already has this step; if not, this acceptance criterion is currently a manual-only check.
  • No Flyway migration needed. The refactor touches Java package declarations only — no database schema changes. This is the right call and worth stating explicitly in the PR description so reviewers don't ask "where's the migration?"
  • The backend/CLAUDE.md and root CLAUDE.md updates in the acceptance criteria are important for onboarding. The current CLAUDE.md at the root still documents the old layer-based structure under "Package Structure" — this will mislead anyone (human or LLM) starting work after the refactor.

Recommendations

  • Add to the PR description: "No Flyway migration. No Docker Compose changes. No API endpoint changes. Pure Java package rename." This prevents the "wait, is there a DB migration?" question from reviewers.
  • Verify the CI workflow has the npm run generate:api step before claiming it as an acceptance criterion gate. If it doesn't exist in CI, mark that criterion as "manual only" to set expectations accurately.
  • The single big-bang PR is fine. For the CI run, estimate: ~200 file renames + ~400 import updates = a large diff but the test run duration is unchanged (it still runs the same tests). No CI timeout risk.
  • After merge, run the full E2E suite once to confirm the Spring context wires correctly in the e2e profile (which loads AuthE2EController and the E2E DataInitializer beans).

Open Decisions

None — no infrastructure decisions are required for this refactor.

## 🚀 Tobias Wendt — DevOps & Platform Engineer ### Observations - This is a pure Java package rename. No Docker Compose changes, no CI pipeline changes, no environment variable changes, no Flyway migrations. Infrastructure impact: zero. - The one CI-relevant implication: if the Gitea Actions CI workflow runs `./mvnw test` (it should), the workflow will pass or fail based on the same criteria as local execution. No runner config changes needed. - The acceptance criteria include `frontend/npm run generate:api` — this requires the backend to be running with `--spring.profiles.active=dev`. In CI, this step either requires the backend to be started as a service container or run as part of a multi-step job. Check whether the existing CI workflow already has this step; if not, this acceptance criterion is currently a manual-only check. - **No Flyway migration needed.** The refactor touches Java package declarations only — no database schema changes. This is the right call and worth stating explicitly in the PR description so reviewers don't ask "where's the migration?" - The `backend/CLAUDE.md` and root `CLAUDE.md` updates in the acceptance criteria are important for onboarding. The current `CLAUDE.md` at the root still documents the old layer-based structure under "Package Structure" — this will mislead anyone (human or LLM) starting work after the refactor. ### Recommendations - Add to the PR description: "No Flyway migration. No Docker Compose changes. No API endpoint changes. Pure Java package rename." This prevents the "wait, is there a DB migration?" question from reviewers. - Verify the CI workflow has the `npm run generate:api` step before claiming it as an acceptance criterion gate. If it doesn't exist in CI, mark that criterion as "manual only" to set expectations accurately. - The single big-bang PR is fine. For the CI run, estimate: ~200 file renames + ~400 import updates = a large diff but the test run duration is unchanged (it still runs the same tests). No CI timeout risk. - After merge, run the full E2E suite once to confirm the Spring context wires correctly in the `e2e` profile (which loads `AuthE2EController` and the E2E `DataInitializer` beans). ### Open Decisions None — no infrastructure decisions are required for this refactor.
Author
Owner

🎨 Leonie Voss — UI/UX Design Lead

Observations

This issue is a backend Java package refactoring with zero frontend changes and no user-visible impact. No Svelte components are being created or modified. No API endpoint paths, request shapes, or response shapes are changing. From a UI/UX and accessibility perspective, there is nothing to review.

The one indirect touchpoint: frontend/npm run generate:api is listed in the acceptance criteria as a smoke test. This regenerates the TypeScript API types from the OpenAPI spec. If the spec output changes shape (even accidentally), the TypeScript compilation would fail, which would surface in the frontend build. This is a good gate — I'd confirm the npm run check (svelte-check) also passes after regeneration, not just the raw codegen run.

Recommendations

  • Confirm that npm run check passes after npm run generate:api — svelte-check will catch any TypeScript compilation errors introduced by type shape changes in the regenerated API client.

No open decisions from a UI/UX perspective.

## 🎨 Leonie Voss — UI/UX Design Lead ### Observations This issue is a backend Java package refactoring with zero frontend changes and no user-visible impact. No Svelte components are being created or modified. No API endpoint paths, request shapes, or response shapes are changing. From a UI/UX and accessibility perspective, there is nothing to review. The one indirect touchpoint: `frontend/npm run generate:api` is listed in the acceptance criteria as a smoke test. This regenerates the TypeScript API types from the OpenAPI spec. If the spec output changes shape (even accidentally), the TypeScript compilation would fail, which would surface in the frontend build. This is a good gate — I'd confirm the `npm run check` (svelte-check) also passes after regeneration, not just the raw codegen run. ### Recommendations - Confirm that `npm run check` passes after `npm run generate:api` — svelte-check will catch any TypeScript compilation errors introduced by type shape changes in the regenerated API client. No open decisions from a UI/UX perspective.
Author
Owner

📋 Elicit — Requirements Engineer

Observations

The issue is well-specified for a refactoring task: it has a concrete target state (the package tree), a defined process (7 steps), explicit anti-patterns, and measurable acceptance criteria. This is above-average for a technical refactoring issue.

Gaps I found against the acceptance criteria:

  1. AuthE2EController is not listed in the target structure. It currently lives in controller/ and has @Profile("e2e"). The issue's target tree covers all other controllers. Either it belongs in user/ (most logical — it manages test auth sessions) or it needs an explicit note. An untracked file in a "everything must be moved" refactor is an unclosed requirement.

  2. The test count baseline is implied, not stated. Acceptance criterion says ./mvnw test is green — but it should also say "same number of tests pass as the pre-refactor baseline." A silent test deletion would satisfy "green" without satisfying "no behavior change."

  3. backend/CLAUDE.md and root CLAUDE.md updates are listed separately. The root CLAUDE.md has an explicit "Package Structure" section that still documents controller/, service/, repository/, model/, dto/. Both files must be updated. Currently only backend/CLAUDE.md is mentioned in the acceptance criteria — add the root CLAUDE.md explicitly.

  4. The "OpenAPI spec diff being zero except for type FQNs" criterion needs a concrete verification step. The process doesn't describe how to produce the before/after diff. Recommend: curl http://localhost:8080/v3/api-docs > before.json (before move), repeat after move, diff before.json after.json | grep -v "raddatz.familienarchiv" should produce no output.

  5. Dependency statement is precise but the blockers are external issues. The issue correctly states it is hard-blocked by #387, #402, and #393. These should be linked as "blocked-by" in Gitea's issue tracker, not just mentioned in text. This makes the dependency visible in the milestone view.

Recommendations

  • Add AuthE2EControlleruser/ explicitly to the target structure and the acceptance criteria checklist.
  • Add acceptance criterion: "Test count reported by ./mvnw test matches the pre-refactor baseline (no tests lost)."
  • Add to acceptance criteria: "Root CLAUDE.md Package Structure section updated to reflect new domain layout."
  • Add a concrete verification command for the OpenAPI spec diff check.
  • Link issues #387, #402, #393 as formal blockers in Gitea (if the tracker supports it).

Open Decisions

  • DataInitializer placement: shared/config/ (pragmatic, current conceptual home) vs user/ (architecturally correct per domain ownership). This is a genuine tradeoff between structural purity and avoiding a class rename. (Raised also by Markus)
## 📋 Elicit — Requirements Engineer ### Observations The issue is well-specified for a refactoring task: it has a concrete target state (the package tree), a defined process (7 steps), explicit anti-patterns, and measurable acceptance criteria. This is above-average for a technical refactoring issue. **Gaps I found against the acceptance criteria:** 1. **`AuthE2EController` is not listed in the target structure.** It currently lives in `controller/` and has `@Profile("e2e")`. The issue's target tree covers all other controllers. Either it belongs in `user/` (most logical — it manages test auth sessions) or it needs an explicit note. An untracked file in a "everything must be moved" refactor is an unclosed requirement. 2. **The test count baseline is implied, not stated.** Acceptance criterion says `./mvnw test` is green — but it should also say "same number of tests pass as the pre-refactor baseline." A silent test deletion would satisfy "green" without satisfying "no behavior change." 3. **`backend/CLAUDE.md` and root `CLAUDE.md` updates are listed separately.** The root `CLAUDE.md` has an explicit "Package Structure" section that still documents `controller/`, `service/`, `repository/`, `model/`, `dto/`. Both files must be updated. Currently only `backend/CLAUDE.md` is mentioned in the acceptance criteria — add the root `CLAUDE.md` explicitly. 4. **The "OpenAPI spec diff being zero except for type FQNs" criterion needs a concrete verification step.** The process doesn't describe how to produce the before/after diff. Recommend: `curl http://localhost:8080/v3/api-docs > before.json` (before move), repeat after move, `diff before.json after.json | grep -v "raddatz.familienarchiv"` should produce no output. 5. **Dependency statement is precise but the blockers are external issues.** The issue correctly states it is hard-blocked by #387, #402, and #393. These should be linked as "blocked-by" in Gitea's issue tracker, not just mentioned in text. This makes the dependency visible in the milestone view. ### Recommendations - Add `AuthE2EController` → `user/` explicitly to the target structure and the acceptance criteria checklist. - Add acceptance criterion: "Test count reported by `./mvnw test` matches the pre-refactor baseline (no tests lost)." - Add to acceptance criteria: "Root `CLAUDE.md` Package Structure section updated to reflect new domain layout." - Add a concrete verification command for the OpenAPI spec diff check. - Link issues #387, #402, #393 as formal blockers in Gitea (if the tracker supports it). ### Open Decisions - **`DataInitializer` placement**: `shared/config/` (pragmatic, current conceptual home) vs `user/` (architecturally correct per domain ownership). This is a genuine tradeoff between structural purity and avoiding a class rename. _(Raised also by Markus)_
Author
Owner

🗳️ Decision Queue — Action Required

1 decision needs your input before implementation starts.

Architecture

  • DataInitializer placementDataInitializer.java currently lives in config/ but injects AppUserRepository and UserGroupRepository directly, making it a user/ domain concern. Two options:

    • user/ as UserDataInitializer — architecturally correct, enforces the stated shared/ admission criteria (shared/ is for things with ≥2 consumers or framework infra; this has only 1 domain consumer), but requires a class rename.
    • shared/config/ — pragmatic, avoids the rename, but leaks user-domain repository dependencies into cross-cutting infrastructure config.

    Raised by: Markus, Elicit


Note: AuthE2EController placement (currently unlisted in the target structure) is unanimously recommended as user/ by Markus, Nora, and Elicit. This is not a decision — it's a gap in the issue spec that should be closed by updating the target structure before implementation starts.

## 🗳️ Decision Queue — Action Required _1 decision needs your input before implementation starts._ ### Architecture - **`DataInitializer` placement** — `DataInitializer.java` currently lives in `config/` but injects `AppUserRepository` and `UserGroupRepository` directly, making it a `user/` domain concern. Two options: - **`user/` as `UserDataInitializer`** — architecturally correct, enforces the stated `shared/` admission criteria (shared/ is for things with ≥2 consumers or framework infra; this has only 1 domain consumer), but requires a class rename. - **`shared/config/`** — pragmatic, avoids the rename, but leaks user-domain repository dependencies into cross-cutting infrastructure config. _Raised by: Markus, Elicit_ --- **Note:** `AuthE2EController` placement (currently unlisted in the target structure) is unanimously recommended as `user/` by Markus, Nora, and Elicit. This is not a decision — it's a gap in the issue spec that should be closed by updating the target structure before implementation starts.
Author
Owner

Heads-up: #417 is resolved (PR #420 against main, awaiting review). Once that merges, the layering tree is clean — both acceptance greps return zero hits — so REFACTOR-1's branch can be cut without REFACTOR-3 (#409)'s ArchUnit rule failing on day one.

Heads-up: #417 is resolved (PR #420 against `main`, awaiting review). Once that merges, the layering tree is clean — both acceptance greps return zero hits — so REFACTOR-1's branch can be cut without REFACTOR-3 (#409)'s ArchUnit rule failing on day one.
Author
Owner

Implementierung abgeschlossen

Branch: feat/issue-407-domain-packaging

Commits

  • 3643fa35 refactor(tag): move tag domain to package org.raddatz.familienarchiv.tag
  • 0ad3f3e5 refactor(geschichte): move geschichte domain to package org.raddatz.familienarchiv.geschichte
  • a39fd992 refactor(notification): move notification domain to package org.raddatz.familienarchiv.notification
  • b466dfce refactor(person): move person domain to package org.raddatz.familienarchiv.person
  • b41e1335 refactor(person/relationship): move relationship sub-package under person domain
  • c0a1c9ff refactor(ocr): move ocr domain to package org.raddatz.familienarchiv.ocr
  • bb7d872a refactor(document): move document sub-packages transcription/annotation/comment
  • e85057be refactor(document): move document domain core to document/ package
  • af2c983f refactor(user): move user domain to user/ package, rename DataInitializer to UserDataInitializer
  • 930b1d23 refactor(security): move SecurityConfig to security/ package
  • 5e53a261 refactor(shared): move remaining services to domain packages (stats→dashboard, filestorage, importing, notification, exception)
  • 27e7fa91 refactor(cleanup): delete empty legacy packages, move remaining test files to domain packages
  • 5f1c539f docs: update package structure docs to reflect domain-based layout

Ergebnis

  • Alle 1503 Tests grün nach jedem Schritt
  • DataInitializerUserDataInitializer (wie besprochen)
  • Leere alte Packages (controller/, service/, repository/, model/, dto/) entfernt
  • CLAUDE.md und backend/CLAUDE.md aktualisiert
## Implementierung abgeschlossen ✅ Branch: `feat/issue-407-domain-packaging` ### Commits - `3643fa35` refactor(tag): move tag domain to package org.raddatz.familienarchiv.tag - `0ad3f3e5` refactor(geschichte): move geschichte domain to package org.raddatz.familienarchiv.geschichte - `a39fd992` refactor(notification): move notification domain to package org.raddatz.familienarchiv.notification - `b466dfce` refactor(person): move person domain to package org.raddatz.familienarchiv.person - `b41e1335` refactor(person/relationship): move relationship sub-package under person domain - `c0a1c9ff` refactor(ocr): move ocr domain to package org.raddatz.familienarchiv.ocr - `bb7d872a` refactor(document): move document sub-packages transcription/annotation/comment - `e85057be` refactor(document): move document domain core to document/ package - `af2c983f` refactor(user): move user domain to user/ package, rename DataInitializer to UserDataInitializer - `930b1d23` refactor(security): move SecurityConfig to security/ package - `5e53a261` refactor(shared): move remaining services to domain packages (stats→dashboard, filestorage, importing, notification, exception) - `27e7fa91` refactor(cleanup): delete empty legacy packages, move remaining test files to domain packages - `5f1c539f` docs: update package structure docs to reflect domain-based layout ### Ergebnis - Alle 1503 Tests grün nach jedem Schritt - `DataInitializer` → `UserDataInitializer` (wie besprochen) - Leere alte Packages (`controller/`, `service/`, `repository/`, `model/`, `dto/`) entfernt - `CLAUDE.md` und `backend/CLAUDE.md` aktualisiert
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#407