Skip to content

refactor(spike): ArchUnit / Spring Modulith / CQRS with module packages (not layers)#110

Draft
David Conneely (dconneely) wants to merge 11 commits into
mainfrom
refactor/architecture-poc
Draft

refactor(spike): ArchUnit / Spring Modulith / CQRS with module packages (not layers)#110
David Conneely (dconneely) wants to merge 11 commits into
mainfrom
refactor/architecture-poc

Conversation

@dconneely

@dconneely David Conneely (dconneely) commented May 11, 2026

Copy link
Copy Markdown
Collaborator

docs: Update tech-docs (with an architecture proposal/suggestion)

  • Rename architecture.html.md -> architecture-patterns.html.md,
    and add Options 4 and 5 for less exotic architecture changes
  • Reanme domain.html.md -> domain-model.html.md
  • Rename events.html.md -> event-patterns.html.md, and reference
    the new Options 4 and 5 from the architecture patterns document
  • Add architecture-proposal.html.md with suggestions on way forward
    with reasoning behind these suggestions

feat: Enforce command/query separation with ArchUnit (proof of concept)

  • Add ArchUnit rules to catch services that mix read and write transactions
    • Add ArchUnit 1.4.2 as a test dependency
    • Write layering, mapper-placement, and command/query naming rules
  • Split ProviderService as the first example of the pattern the rules
    enforce, to validate the approach before wider adoption in the PoC/spike
    • Move patchProvider() into ProviderCommandService to fix the
      write-in-readonly-class ArchUnit violation
    • Rename ProviderService -> ProviderQueryService and
      ProviderCreationService -> ProviderCommandService
    • Update all controllers, services, and tests to use renamed types

refactor: Split OfficeService into command and query services

  • Apply the command/query pattern from the PoC to OfficeService
    • Create OfficeCommandService for write operations (create, patch)
    • Create OfficeQueryService (@Transactional readOnly) for reads
    • Delete OfficeService, update all callers and tests

refactor: Split BankDetailsService into command and query services

  • Apply the same command/query separation to bank account
    operations, consistent with the ArchUnit rules and the previous
    Provider and Office service splits
    • Create BankAccountCommandService for create and link operations
    • Create BankAccountQueryService separate from OfficeQueryService
      to avoid pulling in four unrelated repository dependencies
    • Delete BankDetailsService, update all callers and tests

refactor: Split OfficeLiaisonManagerService into command service

  • Apply the same command/query separation to liaison manager operations,
    consistent with the ArchUnit rules and previous service splits
    • Create OfficeLiaisonManagerCommandService for write operations
    • Move getOfficeLiaisonManagers into OfficeQueryService, which
      already has the required repository dependencies
    • Delete OfficeLiaisonManagerService, update all callers and tests

refactor: Rename contract manager services for command/query separation

  • Apply the same naming convention to the contract manager services,
    consistent with the ArchUnit rules and previous splits
    • Rename OfficeContractManagerAssignmentService ->
      OfficeContractManagerCommandService
    • Rename ContractManagerService -> OfficeContractManagerQueryService
      and ProviderContractManagersService -> ContractManagerQueryService
      (left as two query services because the office-scoped variant uses
      ContractManagerMapper and different repositories than global search)
    • Add @Transactional(readOnly = true) at class level to both query
      services, update all callers and tests

feat: Add Spring Modulith module structure verification (proof of concept)

  • Prove Spring Boot 4 / Spring Modulith 2.0.6 compatibility before committing
    to a modular package reorganisation
    • Add org.springframework.modulith:spring-modulith-core:2.0.6 as a
      test dependency (version not managed by the Spring Boot BOM)
    • Add ApplicationModulesTest calling ApplicationModules.of(...).verify()
    • Move ProviderSpecification from repository.spec -> repository
      to fix the module boundary violation Spring Modulith detected

docs: Update target module structure in architecture docs

  • Update module layout in architecture-proposal.html.md to six domain
    modules and a shared/ utility module
  • Update Option 5 in architecture-patterns.html.md to same layout
  • Rename "Current structure" heading to "Pre-refactoring structure"

refactor: Restructure provider-data-service into module-per-aggregate-root layout

  • Replace flat layered packages (entity/, service/, controller/, mapper/ etc.)
    with per-aggregate Spring Modulith modules: bankaccount/, contractmanager/,
    liaisonmanager/, office/, provider/, shared/, usecase/
  • Add ApplicationModulesTest to enforce module boundaries with Spring Modulith
  • Add and update ArchUnit rules: no field injection, @RestController classes
    in ..web.. packages, repositories in ..repository.. packages, web layer
    does not depend on repositories
  • Update tests to use renamed types

feat: Add Spring Modulith events, implement provider-events API

  • Publish a full-state snapshot event after every write
  • Store published events permanently in provider_event table
  • Implement GET /provider-events and /provider-events/{eventGUID}
  • Add spring-modulith-starter-insight Micrometer tracing of modules
  • Add unit, integration, and e2e test coverage for event publication and query

docs: Revise architecture recommendation to flat technical layers

  • Revise recommendation from per-entity domain modules to flat technical
    layers, retaining Spring Modulith for event publication, observability,
    and ApplicationModulesTest applied to the layer packages themselves
  • Explain why per-entity module granularity fights a workflow-oriented API
    spec: endpoints spanning multiple aggregates force an orchestration module
    that depends on everything, defeating the purpose of the boundaries
  • Remove specific class names from both documents and reduce illustrative
    CQRS examples to representative pairs

Checklist

  • Tests should be passing: ./gradlew test
  • GitHub should not be reporting conflicts; you should have recently run git rebase main.
  • Avoid mixing whitespace changes with code changes in the same commit. These make diffs harder to read and conflicts more likely.
  • You should have looked at the diff against main and ensured that nothing unexpected is included in your changes.
  • You should have checked that the commit messages say why the change was made.

@dconneely David Conneely (dconneely) requested a review from a team as a code owner May 11, 2026 10:21
@dconneely David Conneely (dconneely) marked this pull request as draft May 11, 2026 10:22
@dconneely David Conneely (dconneely) changed the title feat: ArchUnit / Spring Modulith CQRS proof-of-concept feat(SPIKE): ArchUnit / Spring Modulith CQRS proof-of-concept May 11, 2026
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

- Rename `architecture.html.md` -> `architecture-patterns.html.md`,
  and add Options 4 and 5 for less exotic architecture changes
- Reanme `domain.html.md` -> `domain-model.html.md`
- Rename `events.html.md` -> `event-patterns.html.md`, and reference
  the new Options 4 and 5 from the architecture patterns document
- Add `architecture-proposal.html.md` with suggestions on way forward
  with reasoning behind these suggestions
- Add ArchUnit rules to catch services that mix read and write transactions
  - Add ArchUnit 1.4.2 as a test dependency
  - Write layering, mapper-placement, and command/query naming rules
- Split `ProviderService` as the first example of the pattern the rules
  enforce, to validate the approach before wider adoption in the PoC/spike
  - Move `patchProvider()` into `ProviderCommandService` to fix the
    write-in-readonly-class ArchUnit violation
  - Rename `ProviderService` -> `ProviderQueryService` and
    `ProviderCreationService` -> `ProviderCommandService`
  - Update all controllers, services, and tests to use renamed types
- Apply the command/query pattern from the PoC to `OfficeService`
  - Create `OfficeCommandService` for write operations (create, patch)
  - Create `OfficeQueryService` (`@Transactional` readOnly) for reads
  - Delete `OfficeService`, update all callers and tests
- Apply the same command/query separation to bank account
  operations, consistent with the ArchUnit rules and the previous
  Provider and Office service splits
  - Create `BankAccountCommandService` for create and link operations
  - Create `BankAccountQueryService` separate from `OfficeQueryService`
    to avoid pulling in four unrelated repository dependencies
  - Delete `BankDetailsService`, update all callers and tests
@dconneely David Conneely (dconneely) changed the title feat(SPIKE): ArchUnit / Spring Modulith CQRS proof-of-concept refactor(SPIKE): ArchUnit / Spring Modulith CQRS proof-of-concept May 11, 2026
- Apply the same command/query separation to liaison manager operations,
  consistent with the ArchUnit rules and previous service splits
  - Create `OfficeLiaisonManagerCommandService` for write operations
  - Move `getOfficeLiaisonManagers` into `OfficeQueryService`, which
    already has the required repository dependencies
  - Delete `OfficeLiaisonManagerService`, update all callers and tests
- Apply the same naming convention to the contract manager services,
  consistent with the ArchUnit rules and previous splits
  - Rename `OfficeContractManagerAssignmentService` ->
    `OfficeContractManagerCommandService`
  - Rename `ContractManagerService` -> `OfficeContractManagerQueryService`
    and `ProviderContractManagersService` -> `ContractManagerQueryService`
    (left as two query services because the office-scoped variant uses
    `ContractManagerMapper` and different repositories than global search)
  - Add `@Transactional(readOnly = true)` at class level to both query
    services, update all callers and tests
…cept)

- Prove Spring Boot 4 / Spring Modulith 2.0.6 compatibility before committing
  to a modular package reorganisation
  - Add `org.springframework.modulith:spring-modulith-core:2.0.6` as a
    test dependency (version not managed by the Spring Boot BOM)
  - Add `ApplicationModulesTest` calling `ApplicationModules.of(...).verify()`
  - Move `ProviderSpecification` from `repository.spec` -> `repository`
    to fix the module boundary violation Spring Modulith detected
- Update module layout in `architecture-proposal.html.md` to six domain
  modules and a `shared/` utility module
- Update Option 5 in `architecture-patterns.html.md` to same layout
- Rename "Current structure" heading to "Pre-refactoring structure"
…-root layout

- Replace flat layered packages (entity/, service/, controller/, mapper/ etc.)
  with per-aggregate Spring Modulith modules: bankaccount/, contractmanager/,
  liaisonmanager/, office/, provider/, shared/, usecase/
- Add `ApplicationModulesTest` to enforce module boundaries with Spring Modulith
- Add and update ArchUnit rules: no field injection, `@RestController` classes
  in `..web..` packages, repositories in `..repository..` packages, web layer
  does not depend on repositories
- Update tests to use renamed types
- Publish a full-state snapshot event after every write
- Store published events permanently in `provider_event` table
- Implement `GET /provider-events` and `/provider-events/{eventGUID}`
- Add `spring-modulith-starter-insight` Micrometer tracing of modules
- Add unit, integration, and e2e test coverage for event publication and query
@dconneely David Conneely (dconneely) changed the title refactor(SPIKE): ArchUnit / Spring Modulith CQRS proof-of-concept refactor(spike): ArchUnit / Spring Modulith CQRS proof-of-concept May 12, 2026
- Revise recommendation from per-entity domain modules to flat technical
  layers, retaining Spring Modulith for event publication, observability,
  and `ApplicationModulesTest` applied to the layer packages themselves
- Explain why per-entity module granularity fights a workflow-oriented API
  spec: endpoints spanning multiple aggregates force an orchestration module
  that depends on everything, defeating the purpose of the boundaries
- Remove specific class names from both documents and reduce illustrative
  CQRS examples to representative pairs
@dconneely David Conneely (dconneely) changed the title refactor(spike): ArchUnit / Spring Modulith CQRS proof-of-concept refactor(spike): ArchUnit / Spring Modulith / CQRS with modules proof-of-concept May 14, 2026
@dconneely David Conneely (dconneely) changed the title refactor(spike): ArchUnit / Spring Modulith / CQRS with modules proof-of-concept refactor(spike): ArchUnit / Spring Modulith / CQRS with module packages (not layers) May 14, 2026
@dconneely David Conneely (dconneely) added the no-preview Pull requests that should NOT be deployed to a preview environment label May 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-preview Pull requests that should NOT be deployed to a preview environment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants