2.5 KiB
Architect — F-012 Search: interface plus PostgreSQL FTS
Decision
Split product search from product persistence behind a dedicated catalog port. The HTTP contract stays GET /products/search, while the implementation is swappable from PostgreSQL FTS to a future external engine without route changes.
Domain/application
Add a ProductSearchRepository (or equivalent) port in catalog domain:
- Input:
q,limit,offset, optionalbrandSlug,activeOnly. - Output:
Product[]for this slice to preserve current API payload shape.
SearchProducts must depend on the search port, not on ProductRepository. Product CRUD remains in ProductRepository.
PostgreSQL implementation
Add PgProductSearchRepository under catalog infrastructure.
Search scope:
- Product:
catalog_products.name,description,seo_title,seo_description. - Brand:
brands_brands.name,slug,seo_title,seo_description. - Category:
categories_categories.name,slug,seo_title,seo_descriptionthroughcatalog_product_categories.
Ranking/order:
- When
qis present, use PostgreSQL full text search withwebsearch_to_tsquery('spanish', q)andts_rank_cd. - Stable ordering: rank descending, then
p.created_at DESC, thenp.name ASC, thenp.id ASC. - When
qis absent, keep stable listing order byp.created_at DESC,p.name ASC,p.id ASC. - Preserve pagination with bounded
limitandoffset. - Preserve active-only public search and
brandSlugfiltering.
Migration:
- Add GIN expression indexes for product, brand, and category searchable text. Do not add extensions or new runtime dependencies.
- Keep migration reversible.
Telemetry
Measure search duration in the route or use case using performance.now() and log a structured event through the existing logger:
event: 'catalog_search'queryPresent,brandSlug,limit,offset,durationMs,resultCount- If
qis present, include a bounded/sanitizedqueryvalue for popular-search analysis (trimmed max 200; never secrets).
Tests/evidence
- Unit:
SearchProductscalls the search port with defaults and active-only true. - Unit or repository-level SQL construction: stable ordering and FTS query path should be represented in code and typechecked.
- Existing integration tests may remain skipped without
TEST_DATABASE_URL. - Required commands:
npm run lint,npm run typecheck,npm test,./scripts/verify.sh.
Out of scope
No Elasticsearch, Meilisearch, Algolia, typo tolerance beyond PostgreSQL FTS, search suggestions UI, cache, or Redis persistence in this ticket.