# Micro-SaaS - Architectural Decision Records

## ADR-001: Blazor Server vs Blazor WebAssembly

**Status**: Accepted

**Context**: Escolher entre Blazor Server e Blazor WebAssembly para o frontend.

**Decision**: Blazor Server

**Rationale**:
- Melhor para dashboards com dados em tempo real
- Menor payload inicial
- Acesso direto ao backend (sem API intermediária)
- Melhor SEO com pre-rendering
- Scale-to-zero funciona bem com Container Apps

**Consequences**:
- Requer conexão SignalR persistente
- Latência perceptível em conexões lentas
- Não funciona offline

---

## ADR-002: Asaas vs Stripe para Billing

**Status**: Accepted

**Context**: Escolher gateway de pagamento para mercado brasileiro.

**Decision**: Asaas

**Rationale**:
- Suporte nativo a PIX e Boleto
- Taxas competitivas para Brasil
- API bem documentada
- Webhooks confiáveis
- Gestão de assinaturas built-in

**Consequences**:
- Limitado ao mercado brasileiro
- Menos features que Stripe
- Se expandir internacionalmente, precisará adicionar outro gateway

---

## ADR-003: Clerk vs Azure AD B2C para Auth

**Status**: Accepted

**Context**: Escolher provider de autenticação.

**Decision**: Clerk (com Azure AD como alternativa)

**Rationale**:
- Setup mais simples
- UI pré-construída
- Boa integração com .NET
- Free tier generoso (10k MAU)
- Organization support para multi-tenancy

**Alternatives Considered**:
- Azure AD B2C: Mais complexo, mas melhor para enterprise
- Auth0: Boa opção, mas mais caro

**Consequences**:
- Dependência de serviço terceiro
- Custo em escala (após 10k MAU)

---

## ADR-004: Multi-Tenancy Strategy

**Status**: Accepted

**Context**: Definir estratégia de isolamento de dados.

**Decision**: Database-per-tenant com query filter

**Rationale**:
- Single database com `TenantId` em cada tabela
- EF Core Global Query Filters para isolamento
- Mais econômico que database separado
- Escala bem para milhares de tenants

**Implementation**:
```csharp
modelBuilder.Entity<Order>()
    .HasQueryFilter(o => o.TenantId == _tenantContext.TenantId);
```

**Consequences**:
- Cuidado extra com queries que bypassam filtros
- Backup/restore é do banco inteiro
- Migração de tenant individual é mais complexa

---

## ADR-005: Tenant Resolution Strategy

**Status**: Accepted

**Context**: Como identificar o tenant em cada request.

**Decision**: Multiple strategies (subdomain > header > route > claim)

**Rationale**:
1. **Subdomain** (`acme.app.com`): Mais limpo para usuários
2. **Header** (`X-Tenant-ID`): Para APIs
3. **Route** (`/tenant/{slug}`): Fallback
4. **Claim**: Para JWT tokens

**Consequences**:
- DNS wildcard necessário
- Configuração de CORS mais complexa
- Custom domains requerem SSL wildcard ou certificados individuais

---

## ADR-006: Azure Container Apps vs App Service

**Status**: Accepted

**Context**: Escolher serviço de hosting.

**Decision**: Azure Container Apps

**Rationale**:
- Scale-to-zero (custo zero quando inativo)
- Auto-scaling baseado em HTTP requests
- Built-in ingress controller
- Managed identity integration
- Mais barato para cargas variáveis

**Consequences**:
- Cold start de ~2-5 segundos
- Menos features que App Service
- Debugging mais complexo

---

## ADR-007: SQL Server Free Tier

**Status**: Accepted

**Context**: Escolher tier de database.

**Decision**: SQL Server Free Tier (32GB)

**Rationale**:
- Custo zero para desenvolvimento e MVPs
- 32GB suficiente para milhares de tenants pequenos
- Upgrade fácil quando necessário
- Compatível com EF Core

**Consequences**:
- Limites de DTU/vCore
- Sem geo-replication
- Upgrade necessário para produção de alta escala

---

## ADR-008: Subscription Lifecycle

**Status**: Accepted

**Context**: Definir estados e transições de subscription.

**Decision**: Estado máquina com 5 estados

```
Trial → Active → PastDue → Canceled
                    ↓
                 Expired
```

**States**:
- **Trial**: 14 dias sem cobrança
- **Active**: Pagamento em dia
- **PastDue**: Pagamento atrasado (grace period 7 dias)
- **Canceled**: Cancelado pelo usuário
- **Expired**: Trial ou grace period expirado

**Consequences**:
- Lógica de transição precisa ser robusta
- Webhooks Asaas controlam transições de pagamento
- Jobs agendados para expiração de trial

---

## ADR-009: Caching Strategy

**Status**: Accepted

**Context**: Definir estratégia de cache.

**Decision**: In-memory cache com invalidation

**Rationale**:
- Tenant data raramente muda
- Plans são estáticos
- In-memory suficiente para Container Apps com poucas instâncias

**Implementation**:
- `IMemoryCache` para dados de leitura frequente
- Cache invalidation em updates

**Consequences**:
- Cache não compartilhado entre instâncias
- Inconsistência temporária possível (aceitável)

---

## ADR-010: Error Handling Strategy

**Status**: Accepted

**Context**: Padronizar tratamento de erros.

**Decision**: Problem Details (RFC 7807) + Global Exception Handler

**Implementation**:
```csharp
app.UseExceptionHandler(options =>
{
    options.Run(async context =>
    {
        var problemDetails = new ProblemDetails
        {
            Status = StatusCodes.Status500InternalServerError,
            Title = "An error occurred",
            Instance = context.Request.Path
        };
        await context.Response.WriteAsJsonAsync(problemDetails);
    });
});
```

**Consequences**:
- Respostas de erro consistentes
- Fácil integração com frontends
- Logging estruturado

---

*MORPH-SPEC by Polymorphism Tech*
