{"id":29617060,"url":"https://github.com/maxsonferovante/search-service","last_synced_at":"2026-04-30T16:32:10.694Z","repository":{"id":295092160,"uuid":"987635777","full_name":"maxsonferovante/search-service","owner":"maxsonferovante","description":"O Search Service é um microsserviço responsável por coletar preços de voos em intervalos regulares, detectar variações significativas e publicar eventos quando os preços caem abaixo dos limites definidos pelos usuários.","archived":false,"fork":false,"pushed_at":"2025-06-02T03:08:50.000Z","size":109,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-02T12:52:21.405Z","etag":null,"topics":["hibernate","openfeign","postgresql","rabbitmq","spring-amqp","spring-boot","spring-data-jpa"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxsonferovante.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-21T11:12:41.000Z","updated_at":"2025-06-02T03:08:54.000Z","dependencies_parsed_at":"2025-05-23T16:18:01.673Z","dependency_job_id":"b3f6940a-d0ae-4521-abf1-71161d7f080f","html_url":"https://github.com/maxsonferovante/search-service","commit_stats":null,"previous_names":["maxsonferovante/search-service"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/maxsonferovante/search-service","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxsonferovante%2Fsearch-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxsonferovante%2Fsearch-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxsonferovante%2Fsearch-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxsonferovante%2Fsearch-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxsonferovante","download_url":"https://codeload.github.com/maxsonferovante/search-service/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxsonferovante%2Fsearch-service/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266226341,"owners_count":23895690,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hibernate","openfeign","postgresql","rabbitmq","spring-amqp","spring-boot","spring-data-jpa"],"created_at":"2025-07-21T01:32:40.057Z","updated_at":"2026-04-30T16:32:05.674Z","avatar_url":"https://github.com/maxsonferovante.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Search Service - Flight Price Monitor\n\nO **Search Service** é um microsserviço responsável por coletar preços de voos em intervalos regulares, detectar variações significativas e publicar eventos quando os preços caem abaixo dos limites definidos pelos usuários.\n\n## Visão Geral\n\nEste serviço implementa uma arquitetura hexagonal (Ports \u0026 Adapters) com Clean Architecture, mantendo o domínio isolado de frameworks e dependências externas. O serviço:\n\n- 🔄 Coleta preços automaticamente via job agendado\n- 💾 Persiste histórico de preços no PostgreSQL\n- 📊 Detecta variações significativas usando políticas de negócio\n- 📢 Publica eventos via RabbitMQ quando há quedas relevantes\n- 🎯 Monitora rotas específicas baseadas em alertas criados pelos usuários\n- 🧪 Inclui API mock para desenvolvimento e testes\n\n---\n\n## Arquitetura Hexagonal\n\n```\n                         ┌─────────────────┐\n                         │   Domain Core   │\n                         │                 │\n                         │  • WatchRoute   │\n                         │  • FlightPrice  │\n                         │  • PriceUpdated │\n                         │  • Policies     │\n                         └─────────────────┘\n                                  ↑\n                 ┌────────────────┼────────────────┐\n                 │                │                │\n        ┌────────▼───────┐ ┌──────▼──────┐ ┌──────▼──────┐\n        │  Application   │ │Presentation │ │Infrastructure│\n        │                │ │             │ │             │\n        │ • PricePolling │ │ • REST API  │ │ • JPA       │\n        │ • ChangeDetect │ │ • Events    │ │ • RabbitMQ  │\n        │ • Commands     │ │ • Mappers   │ │ • External  │\n        └────────────────┘ └─────────────┘ └─────────────┘\n```\n\n---\n\n## 1. Camada **Domain** (`com.maal.searchservice.domain`)\n\n| Classe / Interface                     | Membros                                                                                                                                                                                                                          | Papel                                                    |\n| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |\n| **WatchRoute**                         | • `alertId: Long`\u003cbr\u003e• `origin: String`\u003cbr\u003e• `destination: String`\u003cbr\u003e• `outboundDate: LocalDate`\u003cbr\u003e• `returnDate: LocalDate`\u003cbr\u003e• `targetPrice: BigDecimal`\u003cbr\u003e• `toleranceUp: BigDecimal`\u003cbr\u003e• `currency: Currency`\u003cbr\u003e• `active: Boolean` | Representa a rota e condições que estão sendo vigiadas. |\n| **FlightPrice**                        | • `origin: String`\u003cbr\u003e• `destination: String`\u003cbr\u003e• `travelDate: LocalDate`\u003cbr\u003e• `price: BigDecimal`\u003cbr\u003e• `currency: Currency`\u003cbr\u003e• `checkedAt: Instant`                                                                         | Snapshot do preço coletado em um momento específico.    |\n| **PriceUpdated** *(evento de domínio)* | • `messageId: UUID`\u003cbr\u003e• `alertId: Long`\u003cbr\u003e• `origin: String`\u003cbr\u003e• `destination: String`\u003cbr\u003e• `outboundDate: LocalDate`\u003cbr\u003e• `returnDate: LocalDate`\u003cbr\u003e• `oldPrice: BigDecimal`\u003cbr\u003e• `newPrice: BigDecimal`\u003cbr\u003e• `currency: Currency`\u003cbr\u003e• `targetPrice: BigDecimal`\u003cbr\u003e• `toleranceUp: BigDecimal`\u003cbr\u003e• `checkedAt: Instant` | Evento publicado quando há variação significativa.      |\n| **AlertEventPayload** *(evento)*      | • `messageId: UUID`\u003cbr\u003e• `origin: String`\u003cbr\u003e• `destination: String`\u003cbr\u003e• `outboundDate: LocalDate`\u003cbr\u003e• `returnDate: LocalDate`\u003cbr\u003e• `oldPrice: BigDecimal`\u003cbr\u003e• `newPrice: BigDecimal`\u003cbr\u003e• `currency: Currency`\u003cbr\u003e• `checkedAt: Instant` | Payload do evento de alerta para publicação.           |\n| **PriceVariationPolicy**               | `Boolean isSignificantDrop(oldPrice, newPrice, tolerance)`                                                                                                                                                                       | Regra de negócio que decide se a queda é relevante.     |\n| **MessagingException**                 | Extends `RuntimeException`                                                                                                                                                                                                        | Exceção específica para problemas de messaging.         |\n| **WatchRouteRepository** *(porta)*     | `List\u003cWatchRoute\u003e findAllActive()`\u003cbr\u003e`void upsert(WatchRoute)`\u003cbr\u003e`Optional\u003cWatchRoute\u003e findByAlertId(Long)`\u003cbr\u003e`Optional\u003cWatchRoute\u003e findById(Long)`\u003cbr\u003e`void deleteById(Long)`\u003cbr\u003e`void deleteByAlertId(Long)`            | Interface para CRUD das rotas vigiadas.                 |\n| **FlightRepository** *(porta)*         | `void save(FlightPrice)`\u003cbr\u003e`Optional\u003cFlightPrice\u003e findLatest(origin, dest, date)`\u003cbr\u003eOperações de busca e persistência                                                                                                          | Interface para persistência do histórico de preços.    |\n| **PriceHistoryRepository** *(porta)*   | `void save(PriceUpdated)`                                                                                                                                                                                                         | Interface para persistência do histórico de eventos.   |\n| **PriceAlertPublisher** *(porta)*      | `void publishPriceAlert(AlertEventPayload)`                                                                                                                                                                                      | Interface para publicação de alertas de preços.        |\n\n---\n\n## 2. Camada **Application** (`com.maal.searchservice.application`)\n\n| Classe                               | Dependências (→)                                                                             | Responsabilidade                                                                                          |\n| ------------------------------------ | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |\n| **PricePollingJob** *(@Scheduled)*   | → `WatchRouteRepository`\u003cbr\u003e→ `ExternalFlightApiClient`\u003cbr\u003e→ `PriceDropOrchestrator`         | Executa periodicamente: coleta rotas ativas, chama API externa e delega orquestração.                   |\n| **PriceDropOrchestrator**            | → `PriceChangeDetector`\u003cbr\u003e→ `PriceHistoryRepository`\u003cbr\u003e→ `PriceAlertPublisher`\u003cbr\u003e→ `PriceUpdatedMapper` | Orquestra o fluxo de detecção de quedas e publicação de alertas.                                         |\n| **PriceChangeDetector**              | → `FlightRepository`\u003cbr\u003e→ `PriceVariationPolicy`                                            | Detecta variações significativas comparando preços atuais com histórico.                                 |\n| **PriceUpdatedMapper**               | —                                                                                            | Converte eventos de domínio para payloads de alerta.                                                     |\n| **TriggerManualFetchCommand**        | —                                                                                            | Comando para trigger manual de coleta (DTO/Record).                                                      |\n\n---\n\n## 3. Camada **Infrastructure** (`com.maal.searchservice.infra`)\n\n### 3.1 Persistência (`infra.persistence`)\n\n#### Entidades JPA\n| Entidade                       | Tabela                  | Responsabilidade                                                  |\n| ------------------------------ | ----------------------- | ----------------------------------------------------------------- |\n| **WatchRouteEntity**           | `watch_routes`          | Persistência de rotas sendo monitoradas.                         |\n| **FlightPriceEntity**          | `flight_prices`         | Histórico completo de preços coletados.                          |\n| **PriceUpdatedEntity**         | `price_history`         | Eventos de alteração de preços persistidos.                      |\n\n#### Repositórios Spring Data JPA\n| Repositório                    | Entidade                | Operações Principais                                              |\n| ------------------------------ | ----------------------- | ----------------------------------------------------------------- |\n| **JpaWatchRouteRepository**    | `WatchRouteEntity`      | CRUD, busca por alertId, busca de rotas ativas.                  |\n| **JpaFlightPriceRepository**   | `FlightPriceEntity`     | Salvamento e busca de preços por rota e data.                    |\n| **JpaPriceHistoryRepository**  | `PriceUpdatedEntity`    | Persistência do histórico de eventos de preços.                  |\n\n#### Adaptadores de Persistência\n| Adapter                        | Implementa              | Responsabilidade                                                  |\n| ------------------------------ | ----------------------- | ----------------------------------------------------------------- |\n| **JpaWatchRouteAdapter**       | `WatchRouteRepository`  | Converte entre domain models e entities para rotas.              |\n| **JpaFlightPriceAdapter**      | `FlightRepository`      | Converte entre domain models e entities para preços.             |\n| **JpaPriceUpdatedAdapter**     | `PriceHistoryRepository`| Converte entre domain models e entities para histórico.          |\n\n#### Mappers de Persistência\n| Mapper                         | Conversão               | Observação                                                        |\n| ------------------------------ | ----------------------- | ----------------------------------------------------------------- |\n| **WatchRouteMapper**           | `WatchRoute` ↔ `Entity` | Conversão bidirecional com validações.                           |\n| **FlightPriceMapper**          | `FlightPrice` ↔ `Entity`| Conversão bidirecional preservando timestamps.                   |\n| **PriceHistoryMapper**         | `PriceUpdated` ↔ `Entity`| Conversão de eventos para persistência.                          |\n\n### 3.2 APIs Externas (`infra.api`)\n| Classe / Adapter                | Implementa / Usa        | Observação                                                       |\n| ------------------------------- | ----------------------- | ---------------------------------------------------------------- |\n| **ExternalFlightApiClient**     | —                       | Cliente HTTP para APIs de voo (Mock API local para desenvolvimento). |\n| **ExternalFlightApiConfig**     | —                       | Configuração do cliente Feign para API externa.                  |\n\n### 3.3 Messaging (`infra.messaging`)\n| Classe / Adapter                | Implementa / Usa        | Observação                                                       |\n| ------------------------------- | ----------------------- | ---------------------------------------------------------------- |\n| **RabbitPriceAlertPublisher**   | —                       | Publica `PriceUpdated` na exchange `price.events`.               |\n\n---\n\n## 4. Camada **Presentation** (`com.maal.searchservice.presentation`)\n\n### 4.1 REST Controllers (`presentation.rest`)\n| Controller                  | Endpoints                                           | Chamadas ao Application              |\n| --------------------------- | --------------------------------------------------- | ------------------------------------ |\n| **HealthController**        | `GET /health`                                       | —                                    |\n| **ManualTriggerController** | `POST /trigger` (JSON com `origin`, `dest`, `date`) | → `ManualTriggerHandler.handle(...)` |\n\n### 4.2 Event Handlers (`presentation.event`)\n| Handler                     | Eventos                     | Responsabilidade                     |\n| --------------------------- | --------------------------- | ------------------------------------ |\n| **AlertCreatedHandler**     | `alert.created`             | Processa alertas criados pelo Alert Service |\n\n### 4.3 Mappers (`presentation.mapper`)\n| Mapper                      | Conversões                  | Responsabilidade                     |\n| --------------------------- | --------------------------- | ------------------------------------ |\n| **FlightPriceMapper**       | DTO ↔ Domain Model          | Converte entre camadas sem vazamentos |\n\n---\n\n## 5. Tecnologias e Dependências\n\n### Core Framework\n- **Spring Boot 3.4.5** (Java 21)\n- **Spring Data JPA** - Persistência\n- **Spring AMQP** - Messaging com RabbitMQ\n- **Spring Web** - REST APIs\n- **Virtual Threads** - Habilitado para melhor performance\n\n### Persistence \u0026 Messaging\n- **PostgreSQL** - Banco de dados principal\n- **RabbitMQ** - Message broker para eventos\n- **Hibernate** - ORM com dialeto PostgreSQL\n\n### External APIs\n- **Spring Cloud OpenFeign 4.2.1** - Cliente HTTP declarativo\n- **Mock API Python** - API local para desenvolvimento e testes\n\n### Development\n- **Lombok** - Redução de boilerplate\n- **Spring Boot Test** - Testes integrados\n\n---\n\n## 6. Configuração\n\n### Banco de Dados\n```properties\nspring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5432/search}\nspring.datasource.username=${DB_USERNAME:admin}\nspring.datasource.password=${DB_PASSWORD:admin}\nspring.jpa.hibernate.ddl-auto=create-drop\nspring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect\n```\n\n### API Externa (Mock Local)\n```properties\nexternal.flight.api.url=http://localhost:5002/search_flights\nexternal.flight.api.key=\"SUA_API_KEY\"\nexternal.flight.api.engine=google_flights\nexternal.flight.api.currency=USD\nexternal.flight.api.language=en\n```\n\n### RabbitMQ\n```properties\nspring.rabbitmq.host=localhost\nspring.rabbitmq.port=5672\nspring.rabbitmq.username=${RABBIT_USER:guest}\nspring.rabbitmq.password=${RABBIT_PASS:guest}\n```\n\n---\n\n## 7. Fluxo de Dados\n\n```\n┌──────────────┐    Schedule    ┌─────────────────┐    HTTP    ┌─────────────────┐\n│ PricePolling │ ──────────────► │ ExternalFlight  │ ─────────► │   Mock API      │\n│     Job      │                │   ApiClient     │            │  (Python)       │\n└──────────────┘                └─────────────────┘            └─────────────────┘\n        │                                │\n        ▼                                ▼\n┌──────────────┐    delegate    ┌─────────────────┐    compare  ┌─────────────────┐\n│ PriceDrop    │ ──────────────► │ PriceChange     │ ──────────► │ FlightPrice     │\n│ Orchestrator │                │   Detector      │             │   Repository    │\n└──────────────┘                └─────────────────┘             └─────────────────┘\n        │                                │\n        ▼ (save history)                 ▼ (if significant drop)\n┌──────────────┐                ┌─────────────────┐\n│ PriceHistory │                │ PriceVariation  │\n│  Repository  │                │     Policy      │\n└──────────────┘                └─────────────────┘\n        │\n        ▼ (publish alert)\n┌──────────────┐    Publish     ┌─────────────────┐    Consume  ┌─────────────────┐\n│ PriceAlert   │ ──────────────► │    RabbitMQ     │ ──────────► │   Alert Service │\n│  Publisher   │                │ (price.events)   │            │                 │\n└──────────────┘                └─────────────────┘            └─────────────────┘\n```\n\n---\n\n## 8. Estrutura de Diretórios Atual\n\n```\nsearch-service/\n├── build.gradle.kts\n├── compose.yml                         # PostgreSQL + RabbitMQ para desenvolvimento\n├── api.py                             # Mock API Python para desenvolvimento\n├── src/\n│   ├── main/\n│   │   ├── java/com/maal/searchservice/\n│   │   │   ├── SearchServiceApplication.java\n│   │   │   ├── domain/                    # Núcleo do negócio\n│   │   │   │   ├── modal/                # Modelos de domínio\n│   │   │   │   │   ├── WatchRoute.java\n│   │   │   │   │   ├── FlightPrice.java\n│   │   │   │   │   └── PriceUpdated.java\n│   │   │   │   ├── repository/           # Interfaces (ports)\n│   │   │   │   │   ├── WatchRouteRepository.java\n│   │   │   │   │   ├── FlightRepository.java\n│   │   │   │   │   └── PriceHistoryRepository.java\n│   │   │   │   ├── port/                 # Portas da arquitetura hexagonal\n│   │   │   │   │   └── PriceAlertPublisher.java\n│   │   │   │   ├── politics/             # Políticas de negócio\n│   │   │   │   │   └── PriceVariationPolicy.java\n│   │   │   │   ├── event/                # Eventos de domínio\n│   │   │   │   │   └── AlertEventPayload.java\n│   │   │   │   └── exception/            # Exceções de domínio\n│   │   │   │       └── MessagingException.java\n│   │   │   │\n│   │   │   ├── application/              # Casos de uso\n│   │   │   │   ├── scheduler/           # Jobs agendados\n│   │   │   │   │   └── PricePollingJob.java\n│   │   │   │   ├── service/             # Serviços de aplicação\n│   │   │   │   │   ├── PriceDropOrchestrator.java\n│   │   │   │   │   └── PriceChangeDetector.java\n│   │   │   │   ├── command/             # Comandos/Handlers\n│   │   │   │   │   └── TriggerManualFetchCommand.java\n│   │   │   │   └── mapper/              # Mapeadores de aplicação\n│   │   │   │       └── PriceUpdatedMapper.java\n│   │   │   │\n│   │   │   ├── infra/                   # Adaptadores externos\n│   │   │   │   ├── api/                 # Clientes de APIs externas\n│   │   │   │   │   ├── ExternalFlightApiClient.java\n│   │   │   │   │   ├── ExternalFlightApiConfig.java\n│   │   │   │   │   └── dto/             # DTOs para APIs externas\n│   │   │   │   ├── persistence/         # Repositórios JPA\n│   │   │   │   │   ├── entity/          # Entidades JPA\n│   │   │   │   │   │   ├── WatchRouteEntity.java\n│   │   │   │   │   │   ├── FlightPriceEntity.java\n│   │   │   │   │   │   └── PriceUpdatedEntity.java\n│   │   │   │   │   ├── repository/      # Repositórios Spring Data\n│   │   │   │   │   │   ├── JpaWatchRouteRepository.java\n│   │   │   │   │   │   ├── JpaFlightPriceRepository.java\n│   │   │   │   │   │   └── JpaPriceHistoryRepository.java\n│   │   │   │   │   ├── adapter/         # Adaptadores de persistência\n│   │   │   │   │   │   ├── JpaWatchRouteAdapter.java\n│   │   │   │   │   │   ├── JpaFlightPriceAdapter.java\n│   │   │   │   │   │   └── JpaPriceUpdatedAdapter.java\n│   │   │   │   │   └── mapper/          # Mapeadores de persistência\n│   │   │   │   │       ├── WatchRouteMapper.java\n│   │   │   │   │       ├── FlightPriceMapper.java\n│   │   │   │   │       └── PriceHistoryMapper.java\n│   │   │   │   └── messaging/           # RabbitMQ publishers/listeners\n│   │   │   │       └── RabbitPriceAlertPublisher.java\n│   │   │   │\n│   │   │   ├── presentation/            # Interface externa\n│   │   │   │   ├── rest/                # Controllers REST\n│   │   │   │   ├── event/               # Event handlers\n│   │   │   │   └── mapper/              # DTOs e conversores\n│   │   │   │\n│   │   │   └── config/                  # Configuração e beans\n│   │   │\n│   │   └── resources/\n│   │       ├── application.properties\n│   │       ├── init.sql                # Scripts de inicialização\n│   │       ├── static/\n│   │       └── templates/\n│   │\n│   └── test/                           # Testes organizados por camada\n│\n├── docker-compose.yml                  # Alias para compose.yml\n└── README.md\n```\n\n---\n\n## 9. Mock API para Desenvolvimento\n\nO projeto inclui uma API mock em Python (`api.py`) que simula o comportamento de APIs de voo reais:\n\n### Características da Mock API:\n- **Endpoint**: `GET /search_flights`\n- **Porta**: 5002\n- **Dados**: 35+ rotas pré-configuradas com preços aleatórios\n- **Parâmetros**: `departure_id`, `arrival_id`, `outbound_date`, `return_date`, `currency`\n- **Resposta**: JSON com detalhes completos de voos, incluindo escalas e preços\n\n### Executar Mock API:\n```bash\npython api.py\n# API disponível em http://localhost:5002\n```\n\n---\n\n## 10. Exemplo de Evento Publicado\n\n### AlertEventPayload (Evento Simplificado)\n```json\n{\n  \"messageId\": \"b1fc7199-3f0d-11ef-bd87-0242ac120005\",\n  \"origin\": \"GRU\",\n  \"destination\": \"CDG\",\n  \"outboundDate\": \"2025-07-10\",\n  \"returnDate\": \"2025-07-20\",\n  \"oldPrice\": 2500.00,\n  \"newPrice\": 1899.99,\n  \"currency\": \"BRL\",\n  \"checkedAt\": \"2025-05-15T12:30:05Z\"\n}\n```\n\n### PriceUpdated (Evento Completo de Domínio)\n```json\n{\n  \"messageId\": \"b1fc7199-3f0d-11ef-bd87-0242ac120005\",\n  \"alertId\": 101,\n  \"origin\": \"GRU\",\n  \"destination\": \"CDG\",\n  \"outboundDate\": \"2025-07-10\",\n  \"returnDate\": \"2025-07-20\",\n  \"oldPrice\": 2500.00,\n  \"newPrice\": 1899.99,\n  \"currency\": \"BRL\",\n  \"targetPrice\": 2000.00,\n  \"toleranceUp\": 100.00,\n  \"checkedAt\": \"2025-05-15T12:30:05Z\"\n}\n```\n\n---\n\n## 11. Principles Applied\n\n### Clean Architecture\n- **Dependency Inversion**: Domain não conhece frameworks\n- **Interface Segregation**: Repositories como ports bem definidas\n- **Single Responsibility**: Cada camada tem responsabilidade única\n\n### Domain-Driven Design\n- **Aggregate Roots**: `WatchRoute` e `FlightPrice`\n- **Domain Events**: `PriceUpdated` para comunicação assíncrona\n- **Policies**: `PriceVariationPolicy` encapsula regras de negócio\n\n### SOLID Principles\n- **Open/Closed**: Extensível via novos adapters\n- **Liskov Substitution**: Implementations intercambiáveis\n- **Dependency Inversion**: Abstrações estáveis, detalhes flexíveis\n\n---\n\n## Como Executar\n\n### Desenvolvimento Local\n\n#### 1. Subir dependências (PostgreSQL + RabbitMQ)\n```bash\ndocker compose up -d\n```\n\n#### 2. Executar Mock API (opcional, para desenvolvimento)\n```bash\npython api.py\n```\n\n#### 3. Executar aplicação\n```bash\n./gradlew bootRun\n```\n\n#### 4. Health check\n```bash\ncurl http://localhost:8080/health\n```\n\n### Build \u0026 Deploy\n```bash\n# Build da aplicação\n./gradlew build\n\n# Build da imagem Docker\n./gradlew bootBuildImage\n\n# Deploy (exemplo)\ndocker run -p 8080:8080 search-service:latest\n```\n\n### Configuração de Ambiente\n\n#### Variáveis de Ambiente Disponíveis:\n- `DB_URL`: URL do banco PostgreSQL\n- `DB_USERNAME`: Usuário do banco\n- `DB_PASSWORD`: Senha do banco\n- `RABBIT_USER`: Usuário do RabbitMQ\n- `RABBIT_PASS`: Senha do RabbitMQ\n\n#### Exemplo de configuração para produção:\n```bash\nexport DB_URL=jdbc:postgresql://prod-db:5432/search\nexport DB_USERNAME=search_user\nexport DB_PASSWORD=secure_password\nexport RABBIT_USER=search_service\nexport RABBIT_PASS=rabbit_password\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxsonferovante%2Fsearch-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxsonferovante%2Fsearch-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxsonferovante%2Fsearch-service/lists"}