svc-tenders Extraction Order
This document proposes a practical extraction sequence based on current code reality.
It does not assume immediate service splitting. The default strategy is:
- code boundary first where possible
- package/API boundary next
- service or database split only when contracts are stable
Extraction decision rules
- Extract first when:
- the capability already has a distinct table set
- the gateway already supports a service URL override
- Kafka/outbox coupling is low
- UI and API ownership are already product-distinct
- Delay extraction when:
- the public truth model is still mixed or UI-first
- the data model is still legacy/new dual-path
- the capability is deeply tied into KES/tender declarations
- the current service is still the only stable canonical owner
Recommended extraction sequence
1. First extraction candidate: ICPI
- Candidate: ICPI price ingestion and read service
- Current location:
- Why first:
VERIFIED: one clean table family:icpi_price_pointsVERIFIED: low coupling to tender lifecycle, KES, and auctionsVERIFIED: gateway already supportsICPI_SERVICE_URL ?? TENDERS_SERVICE_URLVERIFIED: dedicated user-facing ICPI surface already exists in web
- Current blockers:
- ICPI persistence still lives in giant
repository.ts - no standalone package for ICPI domain contracts yet
- ICPI persistence still lives in giant
- Dependency risks:
- low
- Required preconditions:
- extract ICPI repository methods from
repository.ts - define ICPI-specific service module boundary inside monorepo
- document API contract and env/runtime expectations
- extract ICPI repository methods from
- Recommended prep work:
- code boundary first
- then package boundary first
- then optional API boundary first if a separate runtime is desired
- Recommended extraction mode:
code boundary first- then
API boundary first - database split can wait
2. Second extraction candidate: Butkhuzi
- Candidate: Butkhuzi norms/search platform
- Current location:
services/svc-tenders/src/server.ts/butkhuzi/*services/svc-tenders/src/repository.tsButkhuzi methodsservices/api/src/routes/butkhuzi.ts
- Why second:
VERIFIED: own table set:butkhuzi_norms,butkhuzi_chunksVERIFIED: gateway already supportsBUTKHUZI_SERVICE_URL ?? TENDERS_SERVICE_URLVERIFIED: business lifecycle is distinct from tenders
- Why not first:
VERIFIED: current product use is still KES/admin-adjacent, so business ownership is less fully separated than ICPI
- Current blockers:
- KES UI and admin flows consume Butkhuzi through shared portal API
- no standalone Butkhuzi frontend/runtime yet
- Dependency risks:
- medium, mostly product and auth wiring rather than schema
- Required preconditions:
- extract Butkhuzi persistence helpers from
repository.ts - document search/rebuild/upsert contracts
- clarify whether chunk rebuild remains synchronous admin action or moves to background work
- extract Butkhuzi persistence helpers from
- Recommended prep work:
- code boundary first
- package boundary first
- API boundary first
- Recommended extraction mode:
API boundary first- database split can remain later
3. Third extraction candidate: vacancy + accommodation into a workstay platform
- Candidate: vacancy/worker matching + accommodation/stay + nearby work/stay composition
- Current location:
- vacancy modules under
services/svc-tenders/src/vacancy - accommodation modules under
services/svc-tenders/src/accommodation - public and owner routes in
services/svc-tenders/src/server.ts - gateway proxies in
services/api/src/routes/vacancies.ts,vacancy-postings.ts,vacancy-applications.ts,accommodations.ts,accommodation-listings.ts,accommodation-bookings.ts
- vacancy modules under
- Why third:
VERIFIED: real write models and projection flows already existVERIFIED: product ownership is distinct from tender/procurementVERIFIED: gateway already has route-level separation and env indirection for vacancies/accommodations
- Why not earlier:
VERIFIED: public vacancy reads still union new projections with legacyvacanciesVERIFIED: public accommodation discovery still reads legacyaccommodationsVERIFIED: some accommodation booking-read endpoints still return empty arrays despite existing read-model codeVERIFIED: nearby jobs/stays coupling is still assembled at web level over mixed catalogs
- Current blockers:
- dual public truth for vacancy
- dual public truth for accommodation
outbox_eventsis local/in-process, not a clean shared event boundary- giant repository still owns public catalog reads
- Dependency risks:
- medium to high
- Required preconditions:
- finish public read convergence on new write-model projections or consciously retire them in favor of legacy
- wire the missing accommodation booking-read endpoints to
AccommodationReadRepository - separate workstay repository modules from tender/auction persistence
- document domain contracts for vacancy and accommodation independently
- Recommended prep work:
code boundary first- then
package boundary first
4. Fourth extraction candidate: auctions
- Candidate: auctions as their own runtime/domain service
- Current location:
- durable declaration/admin logic in
services/svc-tenders/src/server.tsandservices/svc-tenders/src/repository.ts - gateway and compatibility surfaces in
services/api/src/auctions/* - public web action surfaces in
apps/web/src/ui/auctions/*
- durable declaration/admin logic in
- Why later:
VERIFIED: auctions are product-distinct from tenders long-termVERIFIED: there is already a dedicated public auction surface and internal declaration flow
- Why not soon:
VERIFIED: public bidder mutation truth is still UI-first/session-backed in the web appVERIFIED: gateway and web still mix canonical reads with local-engine/fallback compatibilityVERIFIED: settlement/output allocation/entitlement boundaries are not yet cleanly separated
- Current blockers:
- no durable public bid mutation API
- mixed read truth
- output allocation ownership ambiguity
- auction declaration still tied to current tender/KES operating model
- Dependency risks:
- high
- Required preconditions:
- establish durable bidder mutation path
- remove or isolate compatibility/local-engine reads further
- decide output allocation ownership
- extract auction persistence from giant repository
- Recommended prep work:
API boundary firstis not safe yet- first do
code boundary first - then
event boundary firstif auction events become first-class
- Recommended extraction mode:
code boundary first
5. Shared backbone realignment: KES and event runtime
- Candidate: KES orchestration + outbox/Kafka projection backbone
- Current location:
- KES routes in
services/svc-tenders/src/server.ts - KES persistence in
services/svc-tenders/src/repository.ts - event runtime in
services/svc-tenders/src/kafka
- KES routes in
- Why not treat as a normal product extraction:
VERIFIED: this looks more like shared Kvary execution/runtime backbone than a tender-only or standalone customer-facing platform
- Current blockers:
- KES runtime is colocated with tender data and tender outbox triggers
- shared event contracts still use
svc-tendersas the host service - operational tooling and documentation are still service-local
- Dependency risks:
- very high
- Required preconditions:
- first document and stabilize the shared execution boundary
- decide whether KES becomes a shared platform runtime or remains a service-local orchestration engine
- separate KES persistence/repository modules from tender and auction persistence
- Recommended prep work:
code boundary first- then
package boundary first - service split only after backbone ownership is formally decided
- Recommended extraction mode:
- not a normal extraction; this is a
shared backbone realignment
- not a normal extraction; this is a
What should remain longest inside svc-tenders
- Tender lifecycle and procurement declaration logic
- Tender review/admin workflow
- Tender-specific evidence handling
- Tender event persistence and current tender outbox trigger path
These are the parts with the strongest tender-specific invariants and the least ambiguity about current domain ownership.
What may never become a standalone product service
- Kafka outbox/idempotency/DLQ mechanics
- shared declaration and instrument contracts in
packages/core - service-auth/principal enforcement touchpoints
These are better treated as shared Kvary backbone or shared contract capabilities, not independent product platforms.
Safest extraction order summary
- ICPI
- Butkhuzi
- Vacancy + accommodation workstay platform
- Auctions
- KES/backbone realignment
Required prep work before any real extraction
- Break
services/svc-tenders/src/repository.tsinto domain-owned persistence modules. - Break
services/svc-tenders/src/server.tsinto domain route modules that mirror future platform boundaries. - Remove or explicitly retire legacy public catalog fallbacks for vacancy/accommodation.
- Finish accommodation booking read wiring.
- Decide output allocation ownership.
- Keep
services/apienv-based route indirection; it is already the best extraction seam currently present.