Skip to content

Service Layer Progress

Ed Mozley edited this page Jul 5, 2026 · 18 revisions

Service Layer β€” progress tracker

Rollout status of the service-layer refactor: collapsing each module's duplicated UI + REST API write logic into one shared service, module by module, behind a before/after parity harness.

Strategy: prove the pattern on the smallest, lowest-risk module first; do tickets last (biggest, most integrations). Each module is its own revertable commit; nothing ships unless the harness proves the API is byte-identical.

Per-module status

Module Status Adapter lines removed Parity Notes
Service Status βœ… Done (#718) 474 removed / 75 kept (9 files) βœ… byte-identical Pilot. Established the primitives + pattern. Display-UTC fix #719.
Morning Checks βœ… Done (#720) 424 removed / 94 kept (10 files) βœ… byte-identical Unified documented UI/API drift. Added ActorContext.actorName + shared apiFailFromService().
Software βœ… Done (#722) 175 removed / 40 kept (3 files) βœ… byte-identical Only licences overlap UI+API; inventory is read-only and widgets/apikeys are UI-only (no twin), so left untouched.
Calendar βœ… Done (#723) 302 removed / 70 kept (5 files) βœ… byte-identical Events overlap UI+API; UI-only categories folded in. NaΓ―ve-local datetime contract preserved (not converted to UTC).
Forms ⬜ Planned β€” β€”
Contracts ⬜ Planned β€” β€”
Knowledge ⬜ Planned β€” β€”
Tasks ⬜ Planned β€” β€”
CMDB ⬜ Planned β€” β€”
Network Mapper ⬜ Planned β€” β€”
Assets ⬜ Planned β€” β€”
Changes ⬜ Planned β€” β€”
Problems ⬜ Planned β€” β€”
Service Status settings βœ… folded into pilot β€” n/a Incident-statuses + impact-levels are UI-only; moved into the service.
Tickets ⬜ Last β€” β€” Biggest payoff, most integrations β€” deliberately last.

Line savings β€” the honest picture

The first module is roughly line-neutral: it pays a one-time cost (the shared ActorContext + ServiceError primitives, ~105 lines) and its service includes UI-only logic that was never duplicated. The thousands of lines of net removal arrive as the larger, heavily-mirrored modules migrate β€” tickets above all.

Lines
One-time primitives (includes/service_context.php + apiFailFromService) +122 (paid once, for all modules)
Service Status: adapter logic removed βˆ’474
Service Status: adapters kept (thin) +75
Service Status: service (incl. UI-only ops) +390
Morning Checks: adapter logic removed βˆ’424
Morning Checks: adapters kept (thin) +94
Morning Checks: service (incl. UI-only ops) +310
Software: adapter logic removed βˆ’175
Software: adapters kept (thin) +40
Software: service (licences only) +168
Calendar: adapter logic removed βˆ’302
Calendar: adapters kept (thin) +70
Calendar: service (events + UI-only categories) +251

Convergence log

Where a module's UI and API already behaved differently, we converge to the API's behaviour (the API is the external contract; it stays identical, the UI shifts to match). Record each convergence here so nothing is a silent surprise.

Service Status (#718):

  • Create timestamps: server-local β†’ UTC (SLA-correct; same bug class as Forms #701).
  • Empty description: "" β†’ null.
  • Affected-service validation: silently-skip unknowns β†’ strict 422.
  • Incident status: name-only β†’ name or id.
  • UI deletes: idempotent β†’ 404 on missing (matches the API).

Morning Checks (#720):

  • Result date: malformed silently-swapped-for-today β†’ strict 422.
  • Unknown check id on a result: raw FK 500 β†’ 422 with a clear message.
  • Result CreatedBy: left NULL by the UI β†’ the recording analyst's name (attribution; the API already did this).
  • Check validation messages align with the API wording; updating/deleting a missing check β†’ 404 (was a silent no-op).

Software (#722):

  • Licence app id: no check (raw FK error on a bad id) β†’ the app must exist, 422 with a clear message.
  • Quantity / cost / dates: silently coerced β†’ validated (422) β€” a negative quantity, a non-numeric cost or a malformed date is rejected.
  • Empty optional fields (licence_key, portal_url, vendor_contact, notes): stored as "" β†’ null; missing currency/status fall back to GBP / Active.
  • Saving or deleting a missing licence: silent success β†’ "Licence not found." (matches the API's 404).

Calendar (#723):

  • Event category id: no check (raw FK error on a bad id) β†’ the category must exist, 422.
  • Event times: no check β†’ end_at before start_at is rejected (422); naive-datetime format is validated (Z/offset rejected) β€” the naive-local contract is kept, not converted to UTC.
  • Generated events (source = 'asset_warranty' etc.): the UI let you edit/delete one, only for it to reappear on the next sync β†’ now refused (409) on both edit and delete, matching the API.
  • Saving or deleting a missing event: silent success β†’ "Event not found." (matches the API's 404).
  • Categories are a UI-only operation (no API twin) β€” moved into the service with behaviour preserved verbatim (name required, #RRGGBB colour check, in-use delete guard).

Deferred / follow-up work

  • Display-layer UTC parsing β€” storage is now consistently UTC, but module JS parses "Y-m-d H:i:s" as local (new Date(...)), so UTC data can render an hour off. Fix is a small per-module parseUtc() helper (mark the naive string UTC before toLocale*). Done for service-status (#719); still to do on calendar, morning-checks and other timestamp-rendering modules. See issue #2.
  • Webhook events from the write path β€” once a module is on its service, emitting lifecycle events (for outbound webhooks) is one line from the single write path, firing identically for UI, API and automation. This is the webhooks "wider event catalogue" phase.

How each module is done

  1. Read the complete surface β€” every UI endpoint and the API resource for the module.
  2. Enumerate the UI-vs-API divergences; agree the convergence.
  3. Build/adapt the parity harness; capture golden-before on untouched code.
  4. Extract <Module>Service; repoint the API write handlers + the UI endpoints at it.
  5. Run the harness after; require the API byte-identical, UI converged as designed.
  6. Ship as one commit. Update this page.

See Service Layer β€” architecture for the conventions and the full list of knotty issues.

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally