# ADR-015 — Services agnósticos, headless e sempre disponíveis

| Campo | Valor |
|---|---|
| **Status** | Aceito (2026-10-16) |
| **Data** | 2026-10-14 |
| **Plano de origem** | `wiki/plan/08-servicos-headless-persistencia.md` |
| **Relacionadas** | ADR-013, ADR-014, ADR-009, ADR-001, ADR-033, ADR-034 |

## Contexto

**Módulos** são casos de uso autocontidos (domínio + telas). **Services** são capacidades
transversais (SEO, mídia, busca, tema, auditoria). O `sucesu-cms` tem um service de SEO exemplar
(`V2/Services/SEO/`): um `SEOService` headless + provider (singleton + view composer) +
migrations + um **componente de configuração** (`Views/components/seo-input.blade.php` e
`layouts/cms/components/seo-button.blade.php`) que qualquer módulo inclui no seu formulário.
Precisamos deixar esse conceito explícito e consistente.

## Decisão

- **Agnóstico:** o service **não conhece** módulos; não importa `Modules\*`.
- **Headless (público):** sem rotas/telas **públicas**; a lógica vive em `src/services/{Nome}` e o
  contrato em `src/services/{Nome}/Contracts`. Capacidades transversais **com UI compartilhada** (ex.: Gallery) podem expor
  **superfície administrativa** — rotas **sob `/cms`** + componente de uso —, **nunca** rotas públicas.
  A superfície administrativa **declara** as ações no `config()` do service (`'permissions' =>
  Permission::ACTIONS`) e o Core registra as abilities `{service}.{ver|editar|aprovar|arquivar}`
  (ADR-033); a autorização é do controller (`Permission::authorize`).
- **Sempre disponível:** registrado uma vez (singleton/provider) e usável por **qualquer módulo**,
  de forma **independente** — não é "sob demanda".
- **Implementação padrão:** uma forma canônica de fazer (evita variação por módulo) + **guia** de uso.
- **Componente de configuração/uso** (quando cabível): um componente Blade reutilizável e acessível
  que o módulo inclui (ex.: SEO). **Não** é rota/tela do service.
- **Fundamento:** o **Core** é o **fundamento** (auditoria, observabilidade, descoberta, portas e
  contratos universais); os **services flutuam sobre a implementação** (capacidades transversais);
  os **módulos** são os casos de uso que consomem services, sustentados pelo Core.
- **Dependências:** o service depende de `core`; o módulo depende do **contrato (interface) do
  service** (`src/services/{Nome}/Contracts`), nunca da implementação concreta. No Core ficam apenas
  os contratos que **ele** implementa/provisiona (`Clock`, `IdGen`, `AuditWriter`, `WidgetRegistry`,
  `ModuleProvider`, `Telemetry`) e as **portas cross-módulo** (`Core\Contracts\Ports\*`).
- **Declaração por convenção (simétrica ao `ModuleProvider`):** cada service tem
  `src/services/{Nome}/ServiceProvider.php` **na raiz**, estendendo o provider do Laravel e
  implementando `Core\Contracts\Service` com `contracts(): array` (mapa **contrato → implementação**)
  e `config(): array` (título/rota admin/uso). O **contrato (interface) vive no próprio service**
  (`src/services/{Nome}/Contracts`). O Core descobre `src/services/*/ServiceProvider.php` por
  convenção (`ServiceDiscoveryService`), registra o provider, **absorve** `contracts()` (faz o
  binding) e `config()` e indexa um **catálogo acionável** (`catalog()`). Nada de registro manual no
  `bootstrap/providers.php`.

## Justificativa (alternativas descartadas)

- **Service como módulo** — descartada: módulo é caso de uso; service é capacidade transversal.
- **Service "sob demanda" apenas** — descartada: precisa estar sempre disponível a qualquer módulo.
- **Service com rotas/telas públicas próprias** — descartada: isso é um módulo. UI compartilhada
  (ex.: Gallery) entra como **componente de uso** + rotas **administrativas** sob `/cms`.
- **Sem componente de configuração** — descartada: força cada módulo a reimplementar a UI (o
  `seo-input` do `sucesu-cms` mostra o ganho de centralizar).

## Consequências

**Positivas**
- Reuso: o módulo ganha uma capacidade padrão **e** a UI de configuração, sem reimplementar.
- Fronteira preservada: módulos dependem de contratos de **service**, nunca de outros módulos.
- Acionável: `ServiceDiscoveryService::catalog()` serve CMS/health/`make:service` sem registro manual.

**Custos / negativas**
- O componente de configuração precisa ser acessível/tokenizado e desacoplado de módulo.
- Um contrato pequeno por capacidade, **co-localizado com o service**.

## Exemplo mínimo (comparativo)

```php
// ❌ Módulo reimplementando SEO (e preso a colunas próprias)
$noticia->update(['seo_title' => $t, 'seo_description' => $d]);

// ✅ Service agnóstico + componente de configuração/uso
// Formulário do módulo:
//   <x-service-seo::input :model="$noticia" />   (grava metadata['seo'])
// Render do portal:
//   app(Services\SEO\Contracts\SeoBinder::class)->bind($noticia);  // contrato do serviço
```

```
src/services/Seo/
├── ServiceProvider.php                  # raiz: Core\Contracts\Service (contracts()+config())
├── Contracts/SeoBinder.php              # contrato (interface) consumido pelos módulos
├── SEOService.php                       # implementa SeoBinder (estado, bind(), addSchema())
├── Migrations/                          # tabelas + chaves de configuração
└── View/seo-input.blade.php             # componente de configuração/uso (a11y)
```

## Verificação

- Qualquer módulo usa o service **sem** importar a implementação (`Services\{Nome}\{Contracts,DTOs,Enums}\*`;
  `make check`).
- `make:service Nome` gera `ServiceProvider.php` conformante; `ServiceDiscoveryService::catalog()`
  lista o service; `bootstrap/providers.php` não o registra à mão.
- O componente de configuração é acessível e reaproveitado por ≥2 módulos.
- Teste de contrato do service (fake) e teste do componente (axe).

## Referências

- `sucesu-cms/V2/Services/SEO/` (SEOService, provider, `seo-input.blade.php`);
  `wiki/plan/08-servicos-headless-persistencia.md`.
