main → IDesignRefactor
The IDesignRefactor branch adopts Juval Lowy’s IDesign architecture to enforce a clear separation of concerns:
- Manager — orchestrates application use-cases (no I/O).
- Engine — pure computation/business logic.
- DataAccess — external resources (network, DB, filesystem).
- API — service boundary/transport only.
- Utils — cross-cutting helpers.
- DataAccess now fetches HTML with Playwright.
- Engine now parses HTML only with AngleSharp.
- Manager coordinates the workflow and persistence.
- Coordinates fetching, parsing, and persistence across layers.
- Aggregates, de-duplicates (by Link, fallback Title), and orders results.
- No direct external I/O; relies on contracts.
- Parses HTML into structured results (no I/O).
- Site-specific parsers behind
IEcommerceParserEngine. - Uses AngleSharp to traverse/extract DOM.
- Fetches HTML via Playwright behind
IHtmlFetcher, with ready CSS selectors and timeouts per site. - Persists results through
IProductDataAccess(EF Core; migrations present). - Centralizes resource concerns (timeouts, waits, navigation).
- Exposes endpoints; composes Managers into HTTP.
- No business logic or persistence logic inside endpoints.
IHtmlFetcher(DataAccess):GetContentAsync(Uri url, string readySelector, int timeoutMs, CancellationToken ct)IEcommerceParserEngine(Engine):ParseSearchHtml(string html, Uri sourceUrl, string siteName)IProductDataAccess(DataAccess): persistence (GetAsync,AddRangeAsync, …)
- Fetch & Parse per site
- Manager calls
IHtmlFetcher.GetContentAsync(...)with site URL, ready selector, timeout:- PuntoFarma →
div.card-producto_cardProducto__Jl8Pw(≈ 15s) - FarmaTotal →
div.product(≈ 20s) - Biggie →
div.card-container(≈ 15s)
- PuntoFarma →
- Passes HTML to the keyed
IEcommerceParserEngine("puntoFarma","farmaTotal","biggie").
- Manager calls
- Aggregate & De-dupe
- Flattens results; de-dupes by Link (fallback: Title) via
ConcurrentDictionary.
- Flattens results; de-dupes by Link (fallback: Title) via
- Persist
- Maps to
CreateProductAccessRequestand callsIProductDataAccess.AddRangeAsync(...).
- Maps to
- Normalizes terms (trim, distinct, ignore-empty), repeats
SearchAndSaveAsyncper term, honorsCancellationToken.
- Reads persisted data via
IProductDataAccess.GetAsync(...). - Returns totals and per-site counts.
A Scheduler API is introduced to trigger a daily batch search that populates the datastore by calling the batch endpoint.
GET /search
Returns aggregated stored results (optionally takessearchTextfor compatibility).POST /search/run
Triggers fetch → parse → persist for a single search term (body: string).POST /search/run/batch
Triggers fetch → parse → persist for multiple search terms (body:IEnumerable<string>).
- Once per day, the scheduler composes a list of search terms and calls:
POST /search/run/batch
- The API methods are cancellable (accept
CancellationToken) and return204 No Contenton success. - Input validation is performed:
run: requires non-emptysearchTextrun/batch: requires a non-null collection
# Single term
curl -X POST http://<host>/search/run \
-H "Content-Type: application/json" \
-d '"ibuprofeno"'
# Batch
curl -X POST http://<host>/search/run/batch \
-H "Content-Type: application/json" \
-d '["ibuprofeno","paracetamol","vitamina c"]'