Este documento detalha a implementação completa do módulo SearchProviders, responsável pela busca geoespacial de prestadores de serviços na plataforma MeAjudaAi.
O módulo SearchProviders implementa um read model otimizado para buscas geoespaciais de prestadores, utilizando PostGIS para queries eficientes baseadas em localização. Segue os princípios de Domain-Driven Design (DDD) e Clean Architecture.
- ✅ Busca por proximidade (raio de distância em quilômetros)
- ✅ Filtros avançados (serviços, avaliação mínima, tier de assinatura)
- ✅ Ranking inteligente (tier > rating > distância)
- ✅ Paginação eficiente com contagem total
- ✅ Cache otimizado para queries frequentes
- ✅ Read model denormalizado para desempenho
src/Modules/SearchProviders/
├── API/ # Camada de apresentação (endpoints)
│ └── Endpoints/ # Minimal APIs
│ └── SearchProvidersEndpoint.cs
├── Application/ # Camada de aplicação (CQRS)
│ ├── Queries/ # SearchProvidersQuery
│ ├── Handlers/ # SearchProvidersQueryHandler
│ ├── Validators/ # FluentValidation
│ └── DTOs/ # Data Transfer Objects
├── Domain/ # Camada de domínio
│ ├── Entities/ # SearchableProvider (aggregate)
│ ├── ValueObjects/ # SearchProvidersProvidersResult, SearchableProviderId
│ ├── Enums/ # ESubscriptionTier
│ └── Repositories/ # ISearchableProviderRepository
├── Infrastructure/ # Camada de infraestrutura
│ ├── Persistence/ # Entity Framework + PostGIS
│ │ ├── Configurations/ # EF Core entity configurations
│ │ ├── Migrations/ # Database migrations
│ │ └── Repositories/ # SearchableProviderRepository
│ └── Extensions.cs # DI registration
└── Tests/ # Testes do módulo
├── Unit/ # Testes unitários
│ ├── Domain/ # Entidades e value objects
│ ├── Application/ # Handlers e validators
└── Integration/ # Testes com Testcontainers + PostGIS
└── SearchableProviderRepositoryIntegrationTests.cs
O módulo implementa apenas o lado de Query do CQRS, pois é um read model:
- Query:
SearchProvidersQuerycom validação de parâmetros - Handler:
SearchProvidersQueryHandlerexecuta busca geoespacial - Repository:
SearchableProviderRepositorycom PostGIS
Read model denormalizado com dados necessários para busca:
- Dados de identificação: ProviderId (referência), Name
- Geolocalização: Location (GeoPoint com latitude/longitude)
- Métricas: AverageRating, TotalReviews
- Classificação: SubscriptionTier
- Serviços: ServiceIds (array para filtros)
- Status: IsActive (visibilidade na busca)
- Endereço: City, State, Description
O módulo SearchProviders requer PostgreSQL 12+ com a extensão PostGIS para queries geoespaciais.
Instalação do PostGIS:
# Via Aspire (Recomendado - automático)
cd src/Aspire/MeAjudaAi.AppHost
dotnet run
# Via Docker Compose
docker compose -f infrastructure/compose/environments/development.yml up -d
# Instalação manual no PostgreSQL
psql -U postgres -d MeAjudaAi -c "CREATE EXTENSION IF NOT EXISTS postgis;"Verificar instalação:
SELECT PostGIS_Version();
-- Deve retornar algo como: 3.4 USE_GEOS=1 USE_PROJ=1...O módulo usa schema isolado SearchProviders com suporte PostGIS.
Aplicar migrações:
# Aplicar migrações via Aspire (automático)
cd src\Aspire\MeAjudaAi.AppHost
dotnet run
# Ou aplicar manualmente
cd src\Modules\SearchProviders\Infrastructure
dotnet ef database update --startup-project ..\..\..\..\Bootstrapper\MeAjudaAi.ApiService
# Criar nova migração (se necessário)
dotnet ef migrations add <MigrationName> --startup-project ..\..\..\..\Bootstrapper\MeAjudaAi.ApiServiceEstrutura criada:
- Schema:
SearchProviders - Tabela:
searchable_providers(snake_case) - Extensão:
postgis(geolocalização) - Índices: GIST spatial index na coluna
location
appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=MeAjudaAi;Username=postgres;Password=postgres"
}
}Environment Variables (produção):
export DB_CONNECTION_STRING="Host=prod-server;Database=MeAjudaAi;..."O módulo é registrado automaticamente no Program.cs:
// Registra o módulo SearchProviders completo (Domain, Application, Infrastructure, API)
// Internamente registra DbContext, Repositories, Handlers, Validators
builder.Services.AddSearchProvidersModule(builder.Configuration);
// Mapeia todos os endpoints do módulo SearchProviders
// Substitui a necessidade de chamar métodos individuais de registro
app.UseSearchProvidersModule();Nota: Os métodos AddSearchProvidersModule() e UseSearchProvidersModule() substituem as chamadas individuais anteriores (AddSearchProvidersInfrastructure, AddSearchProvidersApplication, MapSearchProvidersEndpoints), consolidando o registro em dois métodos simples.
Busca prestadores de serviço por proximidade e filtros.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
latitude |
double |
✅ | Latitude do ponto de busca (-90 a 90) |
longitude |
double |
✅ | Longitude do ponto de busca (-180 a 180) |
radiusInKm |
double |
✅ | Raio de busca em quilômetros (> 0, máx. 500) |
serviceIds |
Guid[] |
❌ | IDs dos serviços desejados |
minRating |
decimal |
❌ | Avaliação mínima (0-5) |
subscriptionTiers |
ESubscriptionTier[] |
❌ | Tiers de assinatura (Free, Standard, Gold, Platinum) |
pageNumber |
int |
❌ | Número da página (padrão: 1) |
pageSize |
int |
❌ | Itens por página (padrão: 20, máx.: 100) |
- Filtro espacial: Providers dentro do raio especificado
- Filtros opcionais: Serviços, rating mínimo, tiers
- Ordenação (ranking):
- 1º: Subscription tier (Platinum > Gold > Standard > Free)
- 2º: Average rating (maior para menor)
- 3º: Distância (mais próximo primeiro)
- Paginação: Skip e Take aplicados após ordenação
curl -X GET "https://localhost:7032/api/v1/search/providers?\
latitude=-23.5505&\
longitude=-46.6333&\
radiusInKm=10&\
serviceIds=123e4567-e89b-12d3-a456-426614174000&\
minRating=4.0&\
subscriptionTiers=Gold&subscriptionTiers=Platinum&\
pageNumber=1&\
pageSize=20"{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"providerId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"name": "João Silva Eletricista",
"location": {
"latitude": -23.5505,
"longitude": -46.6333
},
"distanceInKm": 2.5,
"averageRating": 4.8,
"totalReviews": 127,
"subscriptionTier": "Gold",
"serviceIds": [
"123e4567-e89b-12d3-a456-426614174000"
],
"description": "Eletricista com 15 anos de experiência",
"city": "São Paulo",
"state": "SP"
}
],
"pageNumber": 1,
"pageSize": 20,
"totalCount": 45,
"totalPages": 3,
"hasPreviousPage": false,
"hasNextPage": true
}{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "Invalid Parameter",
"status": 400,
"detail": "latitude must be between -90 and 90"
}Casos comuns de validação:
latitudefora do intervalo [-90, 90]longitudefora do intervalo [-180, 180]radiusInKm≤ 0 ou > 500pageNumber< 1pageSize≤ 0 ou > 100minRatingfora do intervalo [0, 5]
{
"type": "https://tools.ietf.org/html/rfc4918#section-11.2",
"title": "Validation Failed",
"status": 422,
"detail": "One or more validation errors occurred.",
"errors": {
"MinRating": ["'Min Rating' must be between 0 and 5."]
}
}{
"type": "https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title": "Search Failed",
"status": 500,
"detail": "An error occurred while processing your search request."
}Códigos de Status:
200 OK: Busca executada com sucesso400 Bad Request: Parâmetros inválidos (coordenadas, raio, paginação)422 Unprocessable Entity: Falhas de validação do FluentValidation500 Internal Server Error: Erro interno do servidor (banco de dados, exceções não tratadas)
1. Buscar prestadores próximos:
GET /api/v1/search/providers?latitude=-23.5505&longitude=-46.6333&radiusInKm=52. Buscar eletricistas bem avaliados:
GET /api/v1/search/providers?latitude=-23.5505&longitude=-46.6333&radiusInKm=10
&serviceIds=<electrician-service-id>&minRating=4.53. Buscar apenas prestadores Premium:
GET /api/v1/search/providers?latitude=-23.5505&longitude=-46.6333&radiusInKm=20
&subscriptionTiers=Gold&subscriptionTiers=PlatinumO módulo implementa cache automático com chaves baseadas em parâmetros:
// Cache key format
search:providers:lat:-23.5505:lng:-46.6333:radius:10:services:all:rating:4.5:tiers:Gold-Platinum:page:1:size:20
// TTL: 5 minutos (dados atualizados frequentemente)Invalidação de cache:
- Automática após 5 minutos
- Manual via tags:
["search", "providers", "search-results"]
| Campo | Mínimo | Máximo | Padrão |
|---|---|---|---|
| Latitude | -90 | 90 | - |
| Longitude | -180 | 180 | - |
| RadiusInKm | 0.1 | 500 | - |
| MinRating | 0 | 5 | - |
| PageNumber | 1 | ∞ | 1 |
| PageSize | 1 | 100 | 20 |
O módulo usa índice GIST para queries espaciais eficientes:
-- Índice criado automaticamente pela migração
CREATE INDEX ix_searchable_providers_location
ON search.searchable_providers
USING GIST (location);Desempenho esperado:
- < 100ms para raio de 10km com 10k providers
- < 500ms para raio de 50km com 100k providers
- Cache hit rate > 70% em produção
⚠️ IMPORTANTE: A integração automática com outros módulos ainda não está implementada. O módulo SearchProviders atualmente opera de forma independente sem sincronização automática. Os dados são estáticos até que a integração via eventos de domínio seja implementada.
O módulo SearchProviders é um read model sincronizado com o módulo Providers:
- Eventos de domínio disparam atualização do SearchableProvider
- Sincronização via domain events ou mensageria (futura implementação)
Fluxo de sincronização (planejado):
Providers Module Search Module
│ │
├─ Provider.Activate() │
├─ ProviderActivatedEvent ─────>│
│ UpdateSearchableProvider
│ MarkAsActive()
Validação de serviceIds será integrada quando o módulo Services estiver implementado.
Atualização de AverageRating e TotalReviews via eventos de review.
Tests/
├── Unit/
│ ├── Domain/
│ │ └── Entities/
│ │ └── SearchableProviderTests.cs # Domain entity tests
│ ├── Application/
│ │ ├── Handlers/
│ │ │ └── SearchProvidersQueryHandlerTests.cs # Handler logic tests
│ │ ├── Queries/
│ │ │ └── SearchProvidersQueryTests.cs # Query behavior tests
│ │ └── Validators/
│ │ └── SearchProvidersQueryValidatorTests.cs # Validation tests
└── Integration/
├── SearchIntegrationTestBase.cs
└── SearchableProviderRepositoryIntegrationTests.cs # 11 testes
```powershell
# Todos os testes do módulo SearchProviders
dotnet test src\Modules\SearchProviders\Tests\
dotnet test src\Modules\SearchProviders\Tests\
# Apenas testes unitários
dotnet test src\Modules\SearchProviders\Tests\ --filter "Category=Unit"
# Apenas testes de integração (requer Docker)
dotnet test src\Modules\SearchProviders\Tests\ --filter "Category=Integration"Os testes de integração usam Testcontainers com PostgreSQL 16 + PostGIS 3.4:
// Container iniciado automaticamente
var container = new PostgreSqlBuilder()
.WithImage("postgis/postgis:16-3.4")
.WithDatabase("search_test")
.Build();
await container.StartAsync();Cobertura de testes:
- ✅ Busca por raio
- ✅ Filtros combinados (serviços, rating, tier)
- ✅ Ordenação (tier > rating > distância)
- ✅ Paginação
- ✅ Providers inativos não aparecem
- ✅ CRUD básico
Causa: PostgreSQL sem extensão PostGIS instalada.
Solução:
-- Conectar ao banco e instalar PostGIS
psql -U postgres -d MeAjudaAi
CREATE EXTENSION IF NOT EXISTS postgis;
SELECT PostGIS_Version(); -- VerificarCausa: Índice GIST ausente ou não utilizado.
Solução:
-- Verificar índices
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'searchable_providers';
-- Recriar índice se necessário
REINDEX INDEX search.ix_searchable_providers_location;
-- Analisar query plan
EXPLAIN ANALYZE
SELECT * FROM search.searchable_providers
WHERE ST_DWithin(location, ST_MakePoint(-46.6333, -23.5505)::geography, 10000);Causa: Pacote NuGet Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite ausente.
Solução:
cd src\Modules\SearchProviders\Infrastructure
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite
dotnet ef database update --startup-project ..\..\..\..\Bootstrapper\MeAjudaAi.ApiServiceCausa: Redis não configurado ou indisponível.
Verificar:
# Verificar se Redis está rodando
docker ps | grep redis
# Testar conexão
redis-cli ping # Deve retornar PONG- Sincronização automática via domain events
- Elasticsearch para full-text search em descriptions
- Filtros adicionais: disponibilidade, preço, especialidades
- Busca por rotas (multiple waypoints)
- Clustering de resultados para visualização de mapa
- A/B testing de algoritmos de ranking
- ML-based ranking (personalização por histórico do usuário)
- Materialização incremental do read model
- Particionamento da tabela por região geográfica
- Cache distribuído com Redis Cluster
- Read replicas para queries geoespaciais
Para contribuir com o módulo SearchProviders:
- Leia o Guia de Desenvolvimento
- Implemente testes (cobertura mínima: 80%)
- Verifique o desempenho com queries geoespaciais
- Documente mudanças em
CHANGELOG.md - Abra Pull Request com descrição detalhada
Contato do Maintainer: Equipe MeAjudaAi