🗄️ Estratégia de Limites de Banco de Dados - Plataforma MeAjudaAi¶
Seguindo a abordagem de Milan Jovanović para manter os limites de dados em Monólitos Modulares.
🎯 Princípios Fundamentais¶
Limites Forçados no Nível do Banco de Dados¶
- ✅ Um schema por módulo com função de banco de dados dedicada
- ✅ Permissões baseadas em funções restringem o acesso apenas ao schema do próprio módulo
- ✅ Um DbContext por módulo com configuração de schema padrão
- ✅ Strings de conexão separadas usando credenciais específicas do módulo
- ✅ Acesso entre módulos apenas através de views explícitas ou APIs
📁 Estrutura de Arquivos¶
infrastructure/database/
├── 📂 shared/ # Scripts base da plataforma
│ ├── 00-create-base-roles.sql # Funções compartilhadas
│ └── 01-create-base-schemas.sql # Schemas compartilhados
│
├── 📂 modules/ # Scripts específicos de módulos
│ ├── 📂 users/ # Módulo de Usuários (IMPLEMENTADO)
│ │ ├── 00-create-roles.sql # Funções do módulo
│ │ ├── 01-create-schemas.sql # Schemas do módulo
│ │ └── 02-grant-permissions.sql # Permissões do módulo
│ │
│ ├── 📂 providers/ # Módulo de Provedores (FUTURO)
│ │ ├── 00-create-roles.sql
│ │ ├── 01-create-schemas.sql
│ │ └── 02-grant-permissions.sql
│ │
│ └── 📂 services/ # Módulo de Serviços (FUTURO)
│ ├── 00-create-roles.sql
│ ├── 01-create-schemas.sql
│ └── 02-grant-permissions.sql
│
├── 📂 views/ # Consultas transversais
│ └── cross-module-views.sql # Acesso controlado entre módulos
│
├── 📂 orchestrator/ # Coordenação e controle
│ └── module-registry.sql # Registro de módulos instalados
│
└── README.md # Documentação
🏗️ Organização de Schemas¶
Estrutura de Schemas do Banco de Dados¶
-- Database: meajudaai
├── users (schema) - Dados de gerenciamento de usuários
├── providers (schema) - Dados de provedores de serviço
├── services (schema) - Dados de catálogo de serviços
├── bookings (schema) - Agendamentos e reservas
├── notifications (schema) - Sistema de mensagens
└── public (schema) - Views transversais e dados compartilhados
🔐 Funções do Banco de Dados¶
| Função | Schema | Propósito |
|---|---|---|
users_role |
users |
Perfis de usuário, dados de autenticação |
providers_role |
providers |
Informações de provedores de serviço |
services_role |
services |
Catálogo de serviços e precificação |
bookings_role |
bookings |
Agendamentos e reservas |
notifications_role |
notifications |
Sistema de mensagens e alertas |
meajudaai_app_role |
public |
Acesso entre módulos via views |
🔧 Implementação Atual¶
Módulo de Usuários (Ativo)¶
- Schema:
users - Função:
users_role - Search Path:
users, public - Permissões: CRUD completo no schema users, acesso limitado ao public para migrations do EF
Configuração de String de Conexão¶
{
"ConnectionStrings": {
"Users": "Host=localhost;Database=meajudaai;Username=users_role;Password=${USERS_ROLE_PASSWORD}",
"Providers": "Host=localhost;Database=meajudaai;Username=providers_role;Password=${PROVIDERS_ROLE_PASSWORD}",
"DefaultConnection": "Host=localhost;Database=meajudaai;Username=meajudaai_app_role;Password=${APP_ROLE_PASSWORD}"
}
}
Configuração do DbContext¶
public class UsersDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Define schema padrão para todas as entidades
modelBuilder.HasDefaultSchema("users");
base.OnModelCreating(modelBuilder);
}
}
// Registro com migrations específicas do schema
builder.Services.AddDbContext<UsersDbContext>(options =>
options.UseNpgsql(connectionString,
o => o.MigrationsHistoryTable("__EFMigrationsHistory", "users")));
🚀 Benefícios desta Estratégia¶
Limites Forçados¶
- Cada módulo opera em seu próprio contexto de segurança
- Acesso a dados entre módulos deve ser explícito (views ou APIs)
- Dependências tornam-se visíveis e mantíveis
- Fácil identificar violações de limites
Extração Futura de Microsserviços¶
- Limites limpos facilitam a extração de módulos
- Banco de dados pode ser dividido ao longo das linhas de schema existentes
- Refatoração mínima necessária para separação de serviços
Principais Vantagens¶
- 🔒 Isolamento em Nível de Banco de Dados: Previne acesso acidental entre módulos
- 🎯 Propriedade Clara: Cada módulo possui seu schema e dados
- 📈 Escalabilidade Independente: Módulos podem ser extraídos para bancos de dados separados posteriormente
- 🛡️ Segurança: Controle de acesso baseado em funções no nível do banco de dados
- 🔄 Segurança de Migration: Histórico de migration separado por módulo
🚀 Adicionando Novos Módulos¶
Passo 1: Copiar Template de Módulo¶
# Copiar template para novo módulo
cp -r infrastructure/database/modules/users infrastructure/database/modules/providers
Passo 2: Atualizar Scripts SQL¶
Substituir users pelo nome do novo módulo em:
- 00-create-roles.sql
- 01-create-schemas.sql
- 02-grant-permissions.sql
Passo 3: Criar DbContext¶
public class ProvidersDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("providers");
base.OnModelCreating(modelBuilder);
}
}
Step 4: Register in DI¶
builder.Services.AddDbContext<ProvidersDbContext>(options =>
options.UseNpgsql(
builder.Configuration.GetConnectionString("Providers"),
o => o.MigrationsHistoryTable("__EFMigrationsHistory", "providers")));
🔄 Migration Commands¶
Generate Migrations¶
# Generate migration for Users module
dotnet ef migrations add AddUserProfile --context UsersDbContext --output-dir Infrastructure/Persistence/Migrations
# Generate migration for Providers module (future)
dotnet ef migrations add InitialProviders --context ProvidersDbContext --output-dir Infrastructure/Persistence/Migrations
Apply Migrations¶
# Apply all migrations for Users module
dotnet ef database update --context UsersDbContext
# Apply specific migration
dotnet ef database update AddUserProfile --context UsersDbContext
Remove Migrations¶
Controle de Migrations em Produção¶
Por padrão, cada módulo aplica suas migrations automaticamente no startup. Para ambientes de produção com múltiplas instâncias ou pipelines de deployment controlados, você pode desabilitar migrations automáticas usando a variável de ambiente APPLY_MIGRATIONS:
# Desabilitar migrations automáticas (recomendado para produção)
APPLY_MIGRATIONS=false
# Habilitar migrations automáticas (padrão em desenvolvimento)
APPLY_MIGRATIONS=true
# ou simplesmente não definir a variável
Quando usar APPLY_MIGRATIONS=false:
- ✅ Ambientes de produção com múltiplas instâncias (evita race conditions)
- ✅ Deployments controlados via pipeline de CI/CD
- ✅ Blue-green deployments onde migrations devem rodar antes do switch
- ✅ Ambientes que exigem aprovação manual de mudanças no schema
Implementação por Módulo:
Cada módulo implementa o controle em seu arquivo API/Extensions.cs:
private static void EnsureDatabaseMigrations(WebApplication app)
{
// Pular em ambientes de teste
if (app.Environment.IsEnvironment("Test") || app.Environment.IsEnvironment("Testing"))
{
return;
}
// Controle via variável de ambiente
var applyMigrations = Environment.GetEnvironmentVariable("APPLY_MIGRATIONS");
if (!string.IsNullOrEmpty(applyMigrations) &&
bool.TryParse(applyMigrations, out var shouldApply) && !shouldApply)
{
logger?.LogInformation("Migrações automáticas desabilitadas via APPLY_MIGRATIONS=false");
return;
}
// Aplicar migrations...
context.Database.Migrate();
}
Aplicar Migrations via Pipeline:
# No seu pipeline de CI/CD, antes do deployment
dotnet ef database update --context DocumentsDbContext --connection "$CONNECTION_STRING"
dotnet ef database update --context UsersDbContext --connection "$CONNECTION_STRING"
dotnet ef database update --context ProvidersDbContext --connection "$CONNECTION_STRING"
# ... outros módulos
# Depois fazer o deployment com APPLY_MIGRATIONS=false
Módulos que implementam este controle: - ✅ Documents - ⏳ Users (pendente) - ⏳ Providers (pendente) - ⏳ ServiceCatalogs (pendente) - ⏳ Locations (pendente)
🌐 Cross-Module Access Strategies¶
Option 1: Database Views (Current)¶
CREATE VIEW public.user_bookings_summary AS
SELECT u.id, u.email, b.booking_date, s.service_name
FROM users.users u
JOIN bookings.bookings b ON b.user_id = u.id
JOIN services.services s ON s.id = b.service_id;
GRANT SELECT ON public.user_bookings_summary TO meajudaai_app_role;
Opção 2: APIs de Módulo (Recomendada)¶
// Cada módulo expõe uma API limpa
public interface IUsersModuleApi
{
Task<UserSummaryDto?> GetUserSummaryAsync(Guid userId);
Task<bool> UserExistsAsync(Guid userId);
}
// Implementação usa DbContext interno
public class UsersModuleApi : IUsersModuleApi
{
private readonly UsersDbContext _context;
public async Task<UserSummaryDto?> GetUserSummaryAsync(Guid userId)
{
return await _context.Users
.Where(u => u.Id == userId)
.Select(u => new UserSummaryDto(u.Id, u.Email, u.FullName))
.FirstOrDefaultAsync();
}
}
// Uso em outros módulos
public class BookingService
{
private readonly IUsersModuleApi _usersApi;
public async Task<BookingDto> CreateBookingAsync(CreateBookingRequest request)
{
// Validar se usuário existe via API
var userExists = await _usersApi.UserExistsAsync(request.UserId);
if (!userExists)
throw new UserNotFoundException();
// Criar agendamento...
}
}
Opção 3: Read Models Orientados a Eventos (Futuro)¶
// Módulo Users publica eventos
public class UserRegisteredEvent
{
public Guid UserId { get; set; }
public string Email { get; set; }
public DateTime RegisteredAt { get; set; }
}
// Outros módulos se inscrevem e constroem read models
public class NotificationEventHandler : INotificationHandler<UserRegisteredEvent>
{
public async Task Handle(UserRegisteredEvent notification, CancellationToken cancellationToken)
{
// Construir read model específico de notificações
await _notificationContext.UserNotificationPreferences.AddAsync(
new UserNotificationPreference
{
UserId = notification.UserId,
EmailEnabled = true
});
}
}
⚡ Configuração de Desenvolvimento¶
Desenvolvimento Local¶
- Aspire: Cria automaticamente o banco de dados e executa scripts de inicialização
- Docker: Container PostgreSQL com montagem de volumes para scripts de schema
- Migrations: Cada módulo mantém histórico de migration separado
Considerações de Produção¶
- Usar Azure PostgreSQL com schemas separados
- Considerar réplicas de leitura para views entre módulos
- Monitorar consultas entre schemas para desempenho
- Planejar eventual divisão de banco de dados se os módulos precisarem escalar independentemente
✅ Checklist de Conformidade¶
- Cada módulo tem seu próprio schema
- Cada módulo tem sua própria função de banco de dados
- Permissões de funções restritas apenas ao schema do módulo
- DbContext configurado com schema padrão
- Tabela de histórico de migrations no schema do módulo
- Strings de conexão usam credenciais específicas do módulo
- Search path configurado para o schema do módulo
- Acesso entre módulos controlado via views/APIs
- Módulos adicionais seguem o mesmo padrão
- Views transversais criadas conforme necessário
🎓 Referências¶
Baseado nos excelentes artigos de Milan Jovanović: - How to Keep Your Data Boundaries Intact in a Modular Monolith - Modular Monolith Data Isolation - Internal vs Public APIs in Modular Monoliths
🔒 Isolamento de Schema para Módulo de Usuários¶
O SchemaPermissionsManager implementa isolamento de segurança para o módulo Users usando os scripts SQL existentes em infrastructure/database/schemas/.
🎯 Objetivos¶
- Isolamento de Dados: O módulo Users só acessa o schema
users. - Segurança: O
users_rolenão pode acessar outros dados. - Reusabilidade: Usa scripts de infraestrutura existentes.
- Flexibilidade: Pode ser habilitado/desabilitado por configuração.
🚀 Como Usar¶
1. Desenvolvimento (Padrão Atual)¶
2. Produção (Com Isolamento)¶
// Program.cs - modo seguro
if (app.Environment.IsProduction())
{
await services.AddUsersModuleWithSchemaIsolationAsync(configuration);
}
else
{
services.AddUsersModule(configuration);
}
3. Configuração (appsettings.Production.json)¶
{
"Database": {
"EnableSchemaIsolation": true
},
"ConnectionStrings": {
"meajudaai-db-admin": "Host=prod-db;Database=meajudaai;Username=admin;Password=admin_password;"
},
"Postgres": {
"UsersRolePassword": "users_secure_password_123",
"AppRolePassword": "app_secure_password_456"
}
}
🔧 Scripts Existentes Utilizados¶
- 00-create-roles-users-only.sql: Cria
users_roleemeajudaai_app_role. - 02-grant-permissions-users-only.sql: Concede permissões específicas para o módulo Users.
📝 Nota sobre Schemas: O schema
usersé criado automaticamente pelo Entity Framework Core através da configuraçãoHasDefaultSchema("users"). Não há necessidade de scripts específicos de criação de schema.
⚡ Benefícios¶
- ✅ Reutiliza infraestrutura existente: Usa scripts já testados.
- ✅ Zero configuração manual: Configuração automática quando necessário.
- ✅ Flexível: Pode ser habilitado apenas em produção.
- ✅ Seguro: Isolamento real para o módulo Users.
- ✅ Consistente: Alinhado com a estrutura atual do projeto.
- ✅ Simplificado: EF Core gerencia a criação de schema automaticamente.
📊 Cenários de Uso¶
| Ambiente | Configuração | Comportamento |
|---|---|---|
| Desenvolvimento | EnableSchemaIsolation: false |
Usa usuário admin padrão |
| Teste | EnableSchemaIsolation: false |
TestContainers com um único usuário |
| Produção | EnableSchemaIsolation: true | Máxima segurança para Users |
🛡️ Estrutura de Segurança¶
- users_role: Acesso exclusivo ao schema
users. - meajudaai_app_role: Acesso transversal para operações gerais.
- Isolamento: O schema
usersestá isolado de outros dados. - Search path:
users,public- prioriza dados do módulo.
Esta solução aproveita completamente sua infraestrutura existente! 🚀
Organização de Scripts de Banco de Dados¶
🔒 Aviso de Segurança¶
Importante: Nunca codifique senhas diretamente em scripts SQL ou documentação. Todas as senhas de banco de dados devem ser: - Recuperadas de variáveis de ambiente - Armazenadas em provedores de configuração seguros (Azure Key Vault, AWS Secrets Manager, etc.) - Geradas usando geradores aleatórios criptograficamente seguros - Rotacionadas regularmente de acordo com políticas de segurança
�📁 Structure Overview¶
infrastructure/database/
├── modules/
│ ├── users/ ✅ IMPLEMENTED
│ │ ├── 00-roles.sql
│ │ └── 01-permissions.sql
│ ├── providers/ 🔄 FUTURE MODULE
│ │ ├── 00-roles.sql
│ │ └── 01-permissions.sql
│ └── services/ 🔄 FUTURE MODULE
│ ├── 00-roles.sql
│ └── 01-permissions.sql
├── views/
│ └── cross-module-views.sql
├── create-module.ps1 # Script para criar novos módulos
└── README.md # Esta documentação
🛠️ Adding New Modules¶
Step 1: Create Module Folder Structure¶
Step 2: Create Scripts Using Templates¶
00-roles.sql Template:¶
-- [MODULE_NAME] Module - Database Roles
-- Create dedicated role for [module_name] module
-- Note: Replace $PASSWORD with secure password from environment variables or secrets store
CREATE ROLE [module_name]_role LOGIN PASSWORD '$PASSWORD';
-- Grant [module_name] role to app role for cross-module access
GRANT [module_name]_role TO meajudaai_app_role;
01-permissions.sql Template:¶
-- [MODULE_NAME] Module - Permissions
-- Grant permissions for [module_name] module
GRANT USAGE ON SCHEMA [module_name] TO [module_name]_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA [module_name] TO [module_name]_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA [module_name] TO [module_name]_role;
-- Set default privileges for future tables and sequences
ALTER DEFAULT PRIVILEGES IN SCHEMA [module_name] GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO [module_name]_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA [module_name] GRANT USAGE, SELECT ON SEQUENCES TO [module_name]_role;
-- Set default search path
ALTER ROLE [module_name]_role SET search_path = [module_name], public;
-- Grant cross-schema permissions to app role
GRANT USAGE ON SCHEMA [module_name] TO meajudaai_app_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA [module_name] TO meajudaai_app_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA [module_name] TO meajudaai_app_role;
-- Set default privileges for app role
ALTER DEFAULT PRIVILEGES IN SCHEMA [module_name] GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO meajudaai_app_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA [module_name] GRANT USAGE, SELECT ON SEQUENCES TO meajudaai_app_role;
-- Grant permissions on public schema
GRANT USAGE ON SCHEMA public TO [module_name]_role;
Step 3: Update SchemaPermissionsManager¶
Adicionar novos métodos para cada módulo:
public async Task EnsureProvidersModulePermissionsAsync(string adminConnectionString,
string providersRolePassword, string appRolePassword)
{
// Implementação similar a EnsureUsersModulePermissionsAsync
}
```csharp
> ⚠️ **AVISO DE SEGURANÇA**: Nunca codifique senhas diretamente em assinaturas de métodos ou código-fonte!
**Padrão de Recuperação Segura de Senhas:**
```csharp
// ✅ SEGURO: Recuperar senhas de configuração/segredos
public async Task ConfigureProvidersModule(IConfiguration configuration)
{
var adminConnectionString = configuration.GetConnectionString("AdminPostgres");
// Opção 1: Variáveis de ambiente
var providersPassword = Environment.GetEnvironmentVariable("PROVIDERS_ROLE_PASSWORD");
var appPassword = Environment.GetEnvironmentVariable("APP_ROLE_PASSWORD");
// Opção 2: Configuração com provedores de segredos (Azure Key Vault, etc.)
var providersPassword = configuration["Database:Roles:ProvidersPassword"];
var appPassword = configuration["Database:Roles:AppPassword"];
// Opção 3: Serviço de segredos dedicado
var secretsService = serviceProvider.GetRequiredService<ISecretsService>();
var providersPassword = await secretsService.GetSecretAsync("db-providers-password");
var appPassword = await secretsService.GetSecretAsync("db-app-password");
if (string.IsNullOrEmpty(providersPassword) || string.IsNullOrEmpty(appPassword))
{
throw new InvalidOperationException("Senhas de funções do banco de dados devem ser configuradas via provedor de segredos");
}
await schemaManager.EnsureProvidersModulePermissionsAsync(
adminConnectionString, providersPassword, appPassword);
}
```text
### Passo 4: Atualizar Registro do Módulo
No `Extensions.cs` de cada módulo:
```csharp
// Opção 1: Usando IServiceScopeFactory (recomendado para métodos de extensão)
public static IServiceCollection AddProvidersModuleWithSchemaIsolation(
this IServiceCollection services, IConfiguration configuration)
{
var enableSchemaIsolation = configuration.GetValue<bool>("Database:EnableSchemaIsolation", false);
if (enableSchemaIsolation)
{
// Registrar um método factory que será executado quando necessário
services.AddSingleton<Func<Task>>(provider =>
{
return async () =>
{
using var scope = provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
var schemaManager = scope.ServiceProvider.GetRequiredService<SchemaPermissionsManager>();
var adminConnectionString = configuration.GetConnectionString("AdminPostgres");
await schemaManager.EnsureProvidersModulePermissionsAsync(adminConnectionString!);
};
});
}
return services;
}
// Opção 2: Usando IHostedService (recomendado para inicialização na startup)
public class DatabaseSchemaInitializationService : IHostedService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IConfiguration _configuration;
public DatabaseSchemaInitializationService(IServiceScopeFactory scopeFactory, IConfiguration configuration)
{
_scopeFactory = scopeFactory;
_configuration = configuration;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var enableSchemaIsolation = _configuration.GetValue<bool>("Database:EnableSchemaIsolation", false);
if (enableSchemaIsolation)
{
using var scope = _scopeFactory.CreateScope();
var schemaManager = scope.ServiceProvider.GetRequiredService<SchemaPermissionsManager>();
var adminConnectionString = _configuration.GetConnectionString("AdminPostgres");
await schemaManager.EnsureProvidersModulePermissionsAsync(adminConnectionString!);
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
// Registrar o hosted service no Program.cs ou Startup.cs:
// services.AddHostedService<DatabaseSchemaInitializationService>();
```csharp
## 🔧 Convenções de Nomenclatura
### Objetos de Banco de Dados:
- **Schema**: `[module_name]` (ex: `users`, `providers`, `services`)
- **Função**: `[module_name]_role` (ex: `users_role`, `providers_role`)
- **Senha**: Recuperada de configuração segura (variáveis de ambiente, Key Vault ou gerenciador de segredos)
### Nomes de Arquivos:
- **Funções**: `00-roles.sql`
- **Permissões**: `01-permissions.sql`
### Configuração do DbContext:
```csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("[module_name]");
// EF Core criará o schema automaticamente
}
⚡ Script Rápido de Criação de Módulo¶
Criar este script PowerShell para configuração rápida de módulos:
# create-module.ps1
param(
[Parameter(Mandatory=$true)]
[string]$ModuleName
)
$ModulePath = "infrastructure/database/modules/$ModuleName"
New-Item -ItemType Directory -Path $ModulePath -Force
# Criar 00-roles.sql
$RolesContent = @"
-- $ModuleName Module - Database Roles
-- Criar função dedicada para o módulo $ModuleName
-- Nota: Substitua `$env:DB_ROLE_PASSWORD pela variável de ambiente real ou recuperação segura de senha
CREATE ROLE ${ModuleName}_role LOGIN PASSWORD '`$env:DB_ROLE_PASSWORD';
-- Conceder função $ModuleName à função app para acesso entre módulos
GRANT ${ModuleName}_role TO meajudaai_app_role;
"@
$RolesContent | Out-File -FilePath "$ModulePath/00-roles.sql" -Encoding UTF8
# Criar 01-permissions.sql
$PermissionsContent = @"
-- $ModuleName Module - Permissions
-- Conceder permissões para o módulo $ModuleName
GRANT USAGE ON SCHEMA $ModuleName TO ${ModuleName}_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA $ModuleName TO ${ModuleName}_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA $ModuleName TO ${ModuleName}_role;
-- Definir privilégios padrão para futuras tabelas e sequences
ALTER DEFAULT PRIVILEGES IN SCHEMA $ModuleName GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${ModuleName}_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA $ModuleName GRANT USAGE, SELECT ON SEQUENCES TO ${ModuleName}_role;
-- Definir search path padrão
ALTER ROLE ${ModuleName}_role SET search_path = $ModuleName, public;
-- Conceder permissões entre schemas à função app
GRANT USAGE ON SCHEMA $ModuleName TO meajudaai_app_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA $ModuleName TO meajudaai_app_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA $ModuleName TO meajudaai_app_role;
-- Definir privilégios padrão para função app
ALTER DEFAULT PRIVILEGES IN SCHEMA $ModuleName GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO meajudaai_app_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA $ModuleName GRANT USAGE, SELECT ON SEQUENCES TO meajudaai_app_role;
-- Conceder permissões no schema public
GRANT USAGE ON SCHEMA public TO ${ModuleName}_role;
"@
$PermissionsContent | Out-File -FilePath "$ModulePath/01-permissions.sql" -Encoding UTF8
Write-Host "✅ Scripts de banco de dados do módulo '$ModuleName' criados com sucesso!" -ForegroundColor Green
Write-Host "📁 Localização: $ModulePath" -ForegroundColor Cyan
📝 Exemplo de Uso¶
# Criar novo módulo providers
./create-module.ps1 -ModuleName "providers"
# Criar novo módulo services
./create-module.ps1 -ModuleName "services"
🔒 Melhores Práticas de Segurança¶
- Isolamento de Schema: Cada módulo tem seu próprio schema e função
- Princípio do Menor Privilégio: Funções têm apenas as permissões necessárias
- Acesso Entre Módulos: Controlado através de
meajudaai_app_role - Gerenciamento de Senhas: Usar senhas seguras em produção
- Search Path: Sempre incluir schema do módulo primeiro, depois public
🔄 Integração com SchemaPermissionsManager¶
O SchemaPermissionsManager automaticamente gerencia:
- ✅ Criação de funções e gerenciamento de senhas
- ✅ Configuração de permissões de schema
- ✅ Configuração de acesso entre módulos
- ✅ Privilégios padrão para objetos futuros
- ✅ Otimização de search path
DbContext Factory Pattern - Documentação¶
Visão Geral¶
A classe BaseDesignTimeDbContextFactory<TContext> fornece uma implementação base para factories de DbContext em tempo de design (design-time), utilizizada principalmente para operações de migração do Entity Framework Core.
Objetivo¶
- Padronização: Centraliza a configuração comum para factories de DbContext
- Reutilização: Permite que módulos implementem facilmente suas próprias factories
- Consistência: Garante configuração uniforme de migrações across módulos
- Manutenibilidade: Facilita mudanças futuras na configuração base
Como Usar¶
// Namespace: MeAjudaAi.Modules.Orders.Infrastructure.Persistence ### 1. Implementação Básica
// Module Name detectado: "Orders"
``````csharp
public class UsersDbContextFactory : BaseDesignTimeDbContextFactory
2. Configuração Automática{¶
Com base no nome do módulo detectado, a factory configura automaticamente: protected override string GetDesignTimeConnectionString()
{
-
Migrations Assembly:
MeAjudaAi.Modules.{ModuleName}.Infrastructurereturn "Host=localhost;Database=meajudaai_dev;Username=postgres;Password=postgres"; -
Schema:
{modulename}(lowercase) } -
Connection String: Baseada no módulo com fallback para configuração padrão
protected override string GetMigrationsAssembly()
3. Configuração Flexível {¶
Suporta configuração via appsettings.json: return "MeAjudaAi.Modules.Users.Infrastructure";
}
```json
{ protected override string GetMigrationsHistorySchema()
"ConnectionStrings": { {
"UsersDatabase": "Host=prod-server;Database=meajudaai_prod;Username=app;Password=secret;SearchPath=users,public", return "users";
"OrdersDatabase": "Host=prod-server;Database=meajudaai_prod;Username=app;Password=secret;SearchPath=orders,public" }
}
} protected override UsersDbContext CreateDbContextInstance(DbContextOptions
``` {
return new UsersDbContext(options);
Como Usar }¶
}
1. Implementação Simples```csharp¶
```csharp
public class UsersDbContextFactory : BaseDesignTimeDbContextFactory
{
protected override UsersDbContext CreateDbContextInstance(DbContextOptions<UsersDbContext> options)```csharp
{public class AdvancedDbContextFactory : BaseDesignTimeDbContextFactory<AdvancedDbContext>
return new UsersDbContext(options);{
} // ... implementações obrigatórias ...
}
``` protected override void ConfigureAdditionalOptions(DbContextOptionsBuilder
{
2. Execução de Migrations // Configurações específicas do módulo¶
```bash optionsBuilder.EnableSensitiveDataLogging();
Funciona automaticamente - detecta o módulo do namespace optionsBuilder.EnableDetailedErrors();¶
dotnet ef migrations add NewMigration --project src/Modules/Users/Infrastructure --startup-project src/Bootstrapper/MeAjudaAi.ApiService }
}
Lista migrations existentes```csharp¶
dotnet ef migrations list --project src/Modules/Users/Infrastructure --startup-project src/Bootstrapper/MeAjudaAi.ApiService
```## Métodos Abstratos
Estrutura de Arquivos| Método | Descrição | Exemplo |¶
|--------|-----------|---------|
``|GetDesignTimeConnectionString()| Connection string para design-time |"Host=localhost;Database=..."` |
src/| GetMigrationsAssembly() | Assembly onde as migrações ficam | "MeAjudaAi.Modules.Users.Infrastructure" |
├── Modules/| GetMigrationsHistorySchema() | Schema para tabela de histórico | "users" |
│ ├── Users/| CreateDbContextInstance() | Cria instância do DbContext | new UsersDbContext(options) |
│ │ └── Infrastructure/
│ │ └── Persistence/## Métodos Virtuais
│ │ ├── UsersDbContext.cs
│ │ └── UsersDbContextFactory.cs ← namespace detecta "Users"| Método | Descrição | Uso |
│ └── Orders/|--------|-----------|-----|
│ └── Infrastructure/| ConfigureAdditionalOptions() | Configurações extras | Override para configurações específicas |
│ └── Persistence/
│ ├── OrdersDbContext.cs## Características
│ └── OrdersDbContextFactory.cs ← namespace detecta "Orders"
└── Shared/- ✅ PostgreSQL: Configurado para usar Npgsql
└── MeAjudaAi.Shared/- ✅ **Migrations Assembly**: Configuração automática
└── Database/- ✅ **Schema Separation**: Cada módulo tem seu schema
└── BaseDesignTimeDbContextFactory.cs ← classe base- ✅ **Design-Time Only**: Connection string não usada em produção
```- ✅ Extensível: Permite configurações adicionais
Vantagens## Convenções¶
-
Zero Hardcoding: Não há valores hardcoded no código### Connection String
-
Convenção sobre Configuração: Funciona automaticamente seguindo a estrutura de namespaces- Formato:
Host=localhost;Database={database};Username=postgres;Password=postgres -
Reutilizável: Mesma implementação para todos os módulos- Uso: Apenas para operações de design-time (migrations)
-
Configurável: Permite override via configuração quando necessário- Produção: Connection string real vem de configuração
-
Type-Safe: Usa reflection de forma segura com validação de namespace
Schema¶
Resolução de Problemas- Padrão: Cada módulo usa seu próprio schema¶
- Exemplos:
users,orders,notifications
Namespace Inválido- Histórico: __EFMigrationsHistory sempre no schema do módulo¶
Se o namespace não seguir o padrão MeAjudaAi.Modules.{ModuleName}.Infrastructure.Persistence, será lançada uma exceção explicativa.
Assembly¶
Connection String- Localização: Sempre no projeto Infrastructure do módulo¶
A factory tenta encontrar uma connection string específica do módulo primeiro, depois usa a padrão:- Formato: MeAjudaAi.Modules.{ModuleName}.Infrastructure
-
{ModuleName}Database(ex: "UsersDatabase") -
DefaultConnection## Exemplo Completo - Novo Módulo -
Fallback para desenvolvimento local
```csharp
Exemplo Completo// Em MeAjudaAi.Modules.Orders.Infrastructure/Persistence/OrdersDbContextFactory.cs¶
using Microsoft.EntityFrameworkCore;
Para adicionar um novo módulo "Products":using MeAjudaAi.Shared.Database;
-
Criar namespace:
MeAjudaAi.Modules.Products.Infrastructure.Persistencenamespace MeAjudaAi.Modules.Orders.Infrastructure.Persistence; -
Implementar factory:
```csharppublic class OrdersDbContextFactory : BaseDesignTimeDbContextFactory
public class ProductsDbContextFactory : BaseDesignTimeDbContextFactory
{ protected override string GetDesignTimeConnectionString()
protected override ProductsDbContext CreateDbContextInstance(DbContextOptions<ProductsDbContext> options) {
{ return "Host=localhost;Database=meajudaai_dev;Username=postgres;Password=postgres";
return new ProductsDbContext(options); }
}
} protected override string GetMigrationsAssembly()
``` {
-
Pronto! A detecção automática cuidará do resto. return "MeAjudaAi.Modules.Orders.Infrastructure";
}
Testado e Validado ✅¶
protected override string GetMigrationsHistorySchema()
Sistema confirmado funcionando através de: {
-
Compilação bem-sucedida return "orders";
-
Comando
dotnet ef migrations listdetectando automaticamente módulo "Users" } -
Localização correta da migration
20250914145433_InitialCreateprotected override OrdersDbContext CreateDbContextInstance(DbContextOptionsoptions) { return new OrdersDbContext(options); } } ```bash
Comandos de Migração¶
```bash
Adicionar migração¶
dotnet ef migrations add InitialCreate --project src/Modules/Users/Infrastructure/MeAjudaAi.Modules.Users.Infrastructure
Aplicar migração¶
dotnet ef database update --project src/Modules/Users/Infrastructure/MeAjudaAi.Modules.Users.Infrastructure ```text
Benefícios¶
- Consistência: Todas as factories seguem o mesmo padrão
- Manutenção: Mudanças na configuração base afetam todos os módulos
- Simplicidade: Implementação reduzida por módulo
- Testabilidade: Configuração centralizada facilita testes
- Documentação: Padrão claro para novos desenvolvedores