Files
mercadodevida/work/artifacts/F-012/architect.md
2026-08-17 22:23:10 +02:00

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, optional brandSlug, 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_description through catalog_product_categories.

Ranking/order:

  • When q is present, use PostgreSQL full text search with websearch_to_tsquery('spanish', q) and ts_rank_cd.
  • Stable ordering: rank descending, then p.created_at DESC, then p.name ASC, then p.id ASC.
  • When q is absent, keep stable listing order by p.created_at DESC, p.name ASC, p.id ASC.
  • Preserve pagination with bounded limit and offset.
  • Preserve active-only public search and brandSlug filtering.

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 q is present, include a bounded/sanitized query value for popular-search analysis (trimmed max 200; never secrets).

Tests/evidence

  • Unit: SearchProducts calls 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.