# Service Simplification Plan Status: draft Last updated: 2026-04-05 ## 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: - `BookingService` no longer owns session lifecycle, baseline snapshot handling, return URL management, or room grouping. That work now lives in `BookingSessionService` and `BookingRoomSelectionService`, which keeps the booking orchestration boundary narrower. - `BookingService` still covers hydration, booking bootstrap, service preselection, and booking status rules. - `BookingParticipantCountService` now handles only participant count shaping. - `ParticipantPrepopulationService` now owns applicant prefill plus the create-mode dummy-data shortcut. - `BookingPriceCalculatorService` is focused on pricing, but it still sits close to display-oriented behavior in adjacent code paths. - `TravelDataService` remains 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. Reduce `BookingService` Primary goal: make the booking create/edit flow easier to read by splitting unrelated concerns. Concrete next steps: - keep booking session lifecycle in one place - extract baseline room snapshot handling into a narrower helper or dedicated service - separate return URL handling if it stays conceptually unrelated - keep `startFreshBooking()` focused on booking bootstrap rather than general session utilities - keep hydration behavior obvious and local to the booking session path Decision rule: - if a method only forwards to DTO/session behavior, prefer removing the wrapper - if a method is a genuine workflow owner, keep it and narrow the surrounding API instead of splitting it into generic helpers ### 2. Keep pricing calculation focused Primary goal: keep pricing code about pricing, not rendering. Concrete next steps: - keep `BookingPriceCalculatorService` as 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 ### 3. 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 `ParticipantFieldHandlerRegistry` in 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 ## 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 | In progress | Session lifecycle, baseline snapshot, return URL handling, room grouping, and participant count shaping moved out of `BookingService`; dummy prefill moved into `ParticipantPrepopulationService` | | 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.