6.6 KiB
Service Simplification Plan
Status: active Last updated: 2026-04-13
Purpose
This document tracks the next pass of service simplification work. The goal is to reduce orchestration density, make the booking flow easier to follow for a human reader, and keep responsibilities aligned with the actual boundaries in the code.
The emphasis is not on deleting services for its own sake. The emphasis is on:
- keeping one clear owner for each meaningful boundary
- removing thin wrappers and pass-through helpers
- avoiding services that mostly shuffle data between layers
- keeping presentation concerns out of calculation and orchestration code
Current Read
The codebase is already in a better place than it was at the start of the refactor, but a few services still carry more than one responsibility:
BookingServicenow mostly covers booking bootstrap, service preselection, and booking status rules.BookingParticipantCountServicewas removed — its logic lives as a private method in the one controller that needed it (Step2Controller).BookingRoomSelectionServicewas removed — its logic lives as a private method inBookingCreateContextFactory.BookingSummaryParticipantCountServicewas removed — its logic lives as a private method inBookingSummaryDataService.ParticipantPrepopulationServicenow owns applicant prefill plus the create-mode dummy-data shortcut.BookingEditParticipantContextFactorynow prepares the edit participant page context directly, replacing the older pass-through participant form service.BookingPriceCalculatorServiceis focused on pricing, but it still sits close to display-oriented behavior in adjacent code paths.TravelDataServiceremains broad and is likely the next larger boundary after booking orchestration is reduced.
One registry stands out as a real orchestration boundary and should be left alone for now:
ParticipantFieldHandlerRegistry
It is not just a lookup table. It owns execution order, edit-mode mutability gating, and synchronization of submitted form data back into the DTO state.
Next Pass
1. Keep pricing calculation focused
Primary goal: keep pricing code about pricing, not rendering.
Concrete next steps:
- keep
BookingPriceCalculatorServiceas the pricing boundary - continue removing display formatting from pricing code paths
- keep any remaining view-specific formatting in the presentation layer or a dedicated UI helper
- avoid introducing another service that only formats values already known to the view
Decision rule:
- if a value is only needed for display, prefer exposing the raw numeric/domain value and formatting it as close to the UI as possible
2. Leave the field-handler registry in place
Primary goal: avoid unnecessary churn in a class that is already a meaningful orchestration layer.
Concrete next steps:
- do not refactor
ParticipantFieldHandlerRegistryin this pass - revisit only if a later change can split ordering, mutability, and synchronization into clear collaborators without making the flow harder to trace
Decision rule:
- if a registry owns actual workflow behavior, treat it as a boundary rather than a smell
Follow-Up Queue
After the booking service pass, the next likely candidates are:
TravelDataService
This is the largest broad service still in the codebase. It likely needs a later pass if the application should become easier to follow end to end.
Likely directions:
- separate runtime travel loading from cache/snapshot maintenance if the public API still feels too wide
- keep the read path explicit and avoid hiding maintenance work behind one large method surface
BookingPriceCalculatorService
This service should stay focused on pricing logic, but it may still have room for further internal cleanup if more display or transport aggregation concerns surface.
Likely directions:
- keep calculation responsibilities together
- avoid dragging presentation behavior back into the calculator
- split only if a sub-boundary becomes obvious and reusable
ParticipantFieldHandlerRegistry
This stays on the list only as a future optional refactor, not as an immediate target.
Likely directions, only if justified later:
- isolate ordering/sorting logic if it becomes independently meaningful
- split synchronization code if a clearer DTO/form boundary emerges
- otherwise leave it as the central orchestration point for participant field processing
Current Notes
templates/booking/_summary_travel_info.html.twigalready renders the summary participant count directly, so the remaining work here is naming and contract clarity rather than Twig branching.- The summary count still needs a clear name if the code should distinguish the display-oriented count from the canonical participant total in the DTO/service layer.
ParticipantFormSupportServicewas reviewed and kept: it is shared between two controllers (Step2ParticipantControllerandEdit/ParticipantController) with real shared logic, not just delegation.
Progress Tracker
| Item | Status | Notes |
|---|---|---|
| Participant card DTO cleanup | Done | Card data now uses typed DTOs instead of nested array payloads |
| Room label formatting cleanup | Done | Pricing labels now have a dedicated presentation helper |
| Booking service split | Done | Session lifecycle, hydration, baseline snapshot, return URL handling, room grouping, and participant count shaping moved out of BookingService |
| Dummy prefill extraction | Done | Moved into ParticipantPrepopulationService |
| Micro-service consolidation | Done | BookingRoomSelectionService, BookingParticipantCountService, BookingSummaryParticipantCountService inlined into their single callers |
| Pricing service review | Pending | Keep focused on calculation, not rendering |
| Travel data service review | Pending | Broad boundary, likely later pass |
| Participant field registry review | Deferred | Real orchestration boundary, intentionally left alone for now |
Acceptance Criteria
The next booking-service pass is only worth keeping if it:
- reduces the number of unrelated responsibilities in
BookingService - makes the booking flow easier to trace from controller to session/DTO state
- preserves existing booking behavior and test coverage
- does not replace one large service with several generic "manager" classes
Working Agreement
- Update this document as decisions are made.
- Record rejected simplification ideas here with a short reason.
- Keep the plan aligned with actual code, not with an abstract architecture ideal.
- If a future simplification does not clearly reduce cognitive load, do not add it.