Una búsqueda que sigue siendo coherente con la fuente
Antes de construir un endpoint de búsqueda, pregunta dónde viven los datos. Esa respuesta decide el diseño.
Si los datos están en tu base de datos, la búsqueda es una consulta. Para una tabla de dominios en un servicio hermano:
$term = addcslashes($request->validated('search'), '\\%_');
$domains = Domain::query()
->whereBelongsTo($site)
->whereLike('hostname', "%{$term}%")
->orderBy('hostname')
->limit(50)
->get();
Fíjate en whereBelongsTo($site) antes del whereLike. Quita esa línea y el endpoint sigue funcionando en todos los tests que escribas contra tus propios datos, y filtra los nombres de host de todos los demás clientes el día que se registre un segundo cliente. Y antes de que la consulta llegue a ejecutarse, una Policy tiene que haber respondido si quien llama puede ver ese sitio. El ámbito estrecha la consulta. La Policy decide quién tiene permiso para preguntar. Necesitas las dos cosas, como mostró el Capítulo 5.
No hay una segunda copia de los datos, así que nada puede quedar obsoleto. Ese es el argumento para mantener la búsqueda así de aburrida todo el tiempo que puedas: una consulta contra la fuente de verdad es coherente por construcción. Cuando LIKE deje de bastar, Laravel Scout te da búsqueda de texto completo detrás del mismo modelo, empezando por un motor de base de datos que no necesita infraestructura adicional. (Documentación de Laravel: Scout.)
Si los datos no están en tu base de datos, ten cuidado. La License API no tiene tabla licenses. Sus licencias viven en el proveedor, y el proveedor no tiene endpoint de búsqueda.
Aquí es donde los equipos recurren a un índice de búsqueda, y donde aparece el costo real. Ya no consultas la fuente de verdad. Consultas una copia, y una copia es correcta solo en el momento en que se sincronizó.
La respuesta de la License API es más pequeña que un índice. El Capítulo 9 ya cachea la lista de licencias de cada cuenta, y esa lista tiene unos pocos cientos de elementos como mucho. La búsqueda es un filtro sobre ella:
// app/Http/Controllers/LicenseController.php
public function index(
IndexLicensesRequest $request,
): AnonymousResourceCollection {
$term = (string) $request->validated('search');
$licenses = Licenses::all()->filter(
fn (License $license): bool => $term === ''
|| str_contains($license->name, $term),
);
return LicenseResource::collection($licenses->values());
}
IndexLicensesRequest tiene una sola regla, 'search' => ['string', 'max:100'], el límite que el Capítulo 5 puso en su propia búsqueda. Ni índice, ni sincronización, ni un segundo sistema. Tiene la frescura de la caché y nada peor.
Si alguna vez necesitas una copia de verdad, constrúyela a partir de los eventos que ya disparas, trátala como desechable, y escribe el comando que la reconstruye desde la fuente antes de escribir cualquier otra cosa. Cuando el proveedor y la copia no coincidan, gana el proveedor.