Gerenciando Persistência de Dados
A persistência de dados no Lino é gerada em torno das fronteiras da Clean Architecture: o modelo de domínio permanece independente do Entity Framework Core, enquanto as preocupações de banco de dados ficam em Infrastructure.Persistence. Isso mantém entidades focadas em regras de negócio e coloca mapeamentos, configuração de provider, migrations, repositories, transações e inicialização na camada de infraestrutura.
Ao criar um serviço com lino service new, o Lino registra decisões importantes de persistência, como arquitetura do serviço e provider de dados. As opções atuais de provider são SqlServer e PostgreSql. Em serviços tradicionais, a persistência é gerada diretamente abaixo do serviço. Em serviços modulares, cada módulo recebe seu próprio projeto Infrastructure.Persistence e seu próprio ApplicationDbContext, enquanto o banco de dados permanece compartilhado pelo serviço.
Em serviços tradicionais ou modulares, o banco de dados é único por serviço. Em serviços modulares, cada módulo é mapeado em um schema distinto dentro do mesmo banco. Isso permite que cada bounded context mantenha um namespace lógico separado, com isolamento funcional, versionamento mais claro e migrations mais organizadas.
Esta página explica como o Lino organiza configurações do Entity Framework Core, registro de DbContext, repositories, IUnitOfWork, transações, fluxo de Transactional Outbox e o ciclo de migrations exposto pelo CLI.
Importante: o código de persistência gerado é scaffolding orientado a produção. Sempre revise mapeamentos, constraints, índices, comportamento de exclusão, transações e scripts de migration antes de aplicar mudanças em bancos compartilhados ou de produção.
Entity Type Configurations
O Lino segue o princípio Persistence-Ignorant: entidades de domínio não conhecem detalhes de infraestrutura. Todo o mapeamento ORM fica em classes que implementam IEntityTypeConfiguration<TEntity>, localizadas em Infrastructure.Persistence/Configurations. Assim, entidades expressam comportamento de negócio enquanto a infraestrutura define como elas serão armazenadas.
Os arquivos de configuração são gerados a partir das decisões feitas no CLI: primary keys, strongly typed IDs, campos obrigatórios, tamanhos de string, Value Objects, relacionamentos, enumerações, campos de auditoria, tenant fields e entidades filhas. O resultado gerado deve ser tratado como um ponto de partida forte, não como algo que nunca precisa ser revisado.
O que as configurações costumam definir
- Tabela e schema: nome da tabela e schema do serviço ou módulo atual.
- Primary keys: identificadores de entidade, conversões de strongly typed IDs e comportamento de geração de chave.
- Propriedades: campos obrigatórios, tamanho máximo, tipos de coluna, persistência de enum, owned structures de Value Objects e colunas de metadata de arquivos.
- Relacionamentos: one-to-one, one-to-many, many-to-many, child collections, foreign keys e comportamento de exclusão.
- Índices e constraints: unicidade, performance de lookup, unicidade tenant-aware e consistência no nível do banco.
Convenções globais
Convenções repetidas podem ser centralizadas em ModelConfiguration ou helpers equivalentes, como precisão decimal, collation, conversão de DateTime, naming convention, filtros globais, propriedades auditáveis e regras comuns de campos multi-tenant.
Como as configurações são aplicadas
O ApplicationDbContext gerado aplica as configurações em OnModelCreating. Por padrão, o Lino usa Source Generators para registrar configurações com melhor performance e sem uso de Reflection. Quando o scanning por assembly é usado, a configuração pode ficar assim:
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
Quando mensageria está habilitada, o Lino também aplica a configuração de OutboxMessage, permitindo que eventos de integração sejam persistidos na mesma transação do banco antes de um worker publicá-los de forma assíncrona.
Checklist de revisão
- Confirme se nomes de tabelas, schemas e convenções respeitam a fronteira do módulo.
- Revise tamanhos de string, precisão decimal, colunas nullable e persistência de enums antes de gerar migrations.
- Revise relacionamentos e delete behavior com cuidado, especialmente para aggregate roots e entidades filhas.
- Adicione ou ajuste índices para queries usadas por grids, filtros, integrações e jobs em background.
- Rode build antes de criar uma migration para garantir que o EF Core enxergue um modelo consistente.
DbContexts
O ApplicationDbContext gerado é o contexto do EF Core para a fronteira de persistência de um serviço ou módulo. Ele expõe propriedades DbSet<TEntity> para as entidades mapeadas e implementa IApplicationDbContext, permitindo que query handlers e serviços de aplicação dependam de uma abstração de aplicação em vez da classe concreta de infraestrutura.
Serviços tradicionais e serviços modulares
- Serviço tradicional: a persistência é gerada em
src/Services/<ServiceName>/Infrastructure.Persistence, e a API do serviço é usada como startup project para migrations. - Serviço modular: cada módulo possui seu próprio projeto
src/Services/<ServiceName>/Modules/<ModuleName>/Infrastructure.Persistencee seu próprioApplicationDbContext. O host do serviço é usado como startup project para migrations.
Em serviços modulares, essa separação deixa cada bounded context explícito no código. Os módulos podem evoluir seu modelo de persistência de forma independente, mesmo rodando no mesmo banco do serviço quando essa é a arquitetura escolhida.
Registro e configuração de provider
O Lino gera o registro de persistência por uma extensão de IHostApplicationBuilder. O código gerado registra Unit of Work, repositories, domain services, uma factory pooled de ApplicationDbContext, o ApplicationDbContext scoped e a abstração IApplicationDbContext.
A configuração específica do provider é gerada de acordo com o banco selecionado para o serviço:
UseSqlServer(...)para serviços SQL Server.UseNpgsql(...).UseSnakeCaseNamingConvention()para serviços PostgreSQL.MigrationsHistoryTable(Constants.Database.MigrationsHistoryTable, Constants.Database.Schema)para armazenar o histórico de migrations na tabela e schema esperados.- Constraint validators específicos do provider são registrados para converter violações de banco em erros de aplicação consistentes.
Contexts tenant-aware
Quando um módulo contém entidades tenant-aware, o contexto gerado pode incluir estado do tenant e filtros globais de query. Nesse cenário, a factory scoped cria um contexto configurado para o escopo atual, evitando que handlers repitam filtros de tenant manualmente em todos os pontos de leitura e escrita.
Repositories
O Lino gera interfaces de repository na camada de domínio e implementações concretas em Infrastructure.Persistence/Repositories. Isso preserva a direção de dependência: o domínio define o contrato de persistência que precisa, e a infraestrutura fornece a implementação com Entity Framework Core.
Repositories concretos também podem aparecer em <ModuleName>/Infrastructure.Persistence.Repositories e implementar interfaces de <ModuleName>/Domain.Repositories, conforme a estrutura do serviço. O ponto importante é que a aplicação e o domínio não dependem diretamente de detalhes concretos do EF Core.
Repositories são usados principalmente por command handlers e fluxos orientados ao domínio que precisam persistir aggregates. Query Handlers podem usar IApplicationDbContext diretamente quando precisam de projeções otimizadas de leitura, filtros, paginação e resultados em formato de DTO.
Responsabilidades de repository
- Carregar aggregate roots e dados filhos necessários para command handlers.
- Adicionar, atualizar e remover aggregates de acordo com o modelo de persistência.
- Encapsular consultas de persistência que fazem parte do comportamento do aggregate ou da orquestração de escrita.
- Manter operações específicas do EF Core fora do projeto de domínio.
- Encapsular consultas complexas quando elas pertencem ao fluxo de escrita, incluindo LINQ,
FromSqle projeções auxiliares.
Orientações práticas
- Use repositories para use cases de escrita que precisam preservar consistência de aggregate.
- Use projeções de query para leitura em vez de carregar aggregates completos apenas para montar DTO.
- Mantenha métodos de repository explícitos; evite métodos genéricos que exponham operações arbitrárias de persistência para a aplicação.
- Revise includes e tracking quando um handler modifica aggregate, child collection ou relacionamento many-to-many.
- Exponha apenas métodos necessários ao domínio e à aplicação, mantendo o repository aggregate-root-centric.
Unit of Work
IUnitOfWork é a fronteira transacional gerada pelo Lino. Ele coordena persistência com EF Core, controle de transação, publicação de eventos de domínio e persistência no Outbox para eventos de integração. Handlers usam essa abstração para confirmar alterações explicitamente, em vez de chamar DbContext.SaveChangesAsync diretamente em todos os pontos da aplicação.
Em cenários simples, o próprio DbContext costuma ser suficiente como unidade de trabalho. Ainda assim, o Lino gera uma implementação dedicada de UnitOfWork para oferecer controle consistente sobre transações, eventos e integração com Outbox.
Operações geradas
SaveChangesAsync(cancellationToken): salva alterações e publica eventos de domínio quando configurado.SaveChangesAsync(publishDomainEvents, cancellationToken): permite salvar com ou sem publicação de eventos de domínio.SaveChangesInTransactionAsync(cancellationToken): abre uma transação, salva alterações e confirma.BeginTransactionAsync,CommitAsynceRollbackAsync: permitem controle explícito de transação.CommitOrRollbackAsync: confirma ou desfaz a transação com base em umResult.
Eventos de domínio e Outbox
Quando existem eventos de domínio, o Lino exige uma transação aberta antes de publicá-los. Isso é intencional. Se um evento de domínio gerar eventos de integração, esses eventos são registrados no Outbox e persistidos na mesma transação. Depois, um worker pode processar os registros do Outbox e publicar mensagens pela infraestrutura de mensageria configurada.
Esse fluxo protege consistência: quando um command cria entidades que levantam eventos e o projeto usa Outbox, o handler deve salvar por uma transação, por exemplo com SaveChangesInTransactionAsync, ou abrir e confirmar a transação explicitamente. Sem essa transação, a infraestrutura deve falhar em vez de permitir um fluxo de eventos não confiável.
Quando usar cada estilo de save
- Use
SaveChangesAsyncpara persistência direta sem exigência de consistência entre eventos e Outbox. - Use
SaveChangesInTransactionAsyncquando eventos de domínio e persistência no Outbox precisam fazer parte do mesmo commit. - Use
BeginTransactionAsync,CommitAsynceRollbackAsyncquando o handler tem várias etapas e precisa decidir o resultado final com base em umResult.
Regra prática: se uma operação de escrita publica eventos de domínio que podem produzir eventos de integração, mantenha alterações de banco e registros de Outbox na mesma transação.
Managing Migrations
O Lino envolve o ciclo de migrations do Entity Framework Core com comandos CLI que conhecem serviço, módulo, arquitetura, provider, DbContext, startup project, arquivo de versão e local de saída dos scripts. Isso reduz erros de comando manual e mantém a metadata da migration rastreada pelo modelo do projeto Lino.
Migrations registram a evolução do banco a partir de mudanças em entidades, Value Objects, relacionamentos, índices e configurações do Entity Framework. Antes de criar uma migration, compile a solução ou pelo menos o serviço afetado para que o EF Core carregue o modelo atual.
Criar uma migration
lino database migrations add --service <ServiceName> --module <ModuleName>
O comando solicita serviço, módulo, versão atual do serviço em src/Services/<ServiceName>/version.txt e descrição da migration. O Lino cria um nome usando versão e sequência, então executa dotnet ef migrations add para o projeto de persistência e startup project corretos.
O Lino também gera um script SQL da migration usando dotnet ef migrations script. O script é escrito abaixo do projeto de persistência afetado em uma pasta versionada, seguindo o padrão:
Infrastructure.Persistence/Scripts/<Version>/<Sequence>_<Description>.sql Infrastructure.Persistence/Scripts/v1.2.3/001_AddCustomerIsActive.sql
Nota: além dos arquivos .cs gerados pelo Entity Framework, o Lino gera o script .sql correspondente com instruções DDL. Isso facilita revisão por equipes de infraestrutura, DBAs e processos de deploy controlado.
Aplicar, listar, reverter e remover
lino database migrations add --service <ServiceName> --module <ModuleName> lino database migrations list --service <ServiceName> --module <ModuleName> lino database migrations apply --service <ServiceName> --module <ModuleName> lino database migrations revert --service <ServiceName> --module <ModuleName> lino database migrations remove --service <ServiceName> --module <ModuleName>
add: cria uma migration para mudanças pendentes no modelo.list: lista migrations conhecidas para o contexto selecionado.apply: executadotnet ef database updatepara aplicar migrations no banco configurado.revert: volta o último caminho de migration bem-sucedido quando o fluxo permitir.remove: remove o último registro de migration criada e os arquivos EF gerados quando aplicável.
O grupo canônico é database migrations. Aliases como db e migration podem existir para produtividade, mas a documentação deve preferir a forma completa.
O que o Lino escolhe por você
- Em serviços tradicionais, o projeto EF é
src/Services/<ServiceName>/Infrastructure.Persistencee o startup project ésrc/Services/<ServiceName>/Api. - Em serviços modulares, o projeto EF é
src/Services/<ServiceName>/Modules/<ModuleName>/Infrastructure.Persistencee o startup project ésrc/Services/<ServiceName>/Host. - O contexto é o
ApplicationDbContextgerado para o serviço ou módulo selecionado. - A tabela de histórico de migrations do EF é configurada com
Constants.Database.MigrationsHistoryTableeConstants.Database.Schema.
Workflow recomendado
- Modele ou altere entidades, Value Objects, relacionamentos, índices ou configurações de persistência.
- Rode build e corrija erros de compilação antes de gerar a migration.
- Execute
lino database migrations addpara o serviço e módulo corretos. - Revise a migration C# e o script SQL gerados antes de aplicar.
- Aplique a migration em ambiente local ou de desenvolvimento e verifique o schema do banco.
- Commite arquivos de migration e scripts SQL junto com a mudança de domínio ou aplicação que exigiu a alteração.
Checklist: revise o diff da migration, aplique em banco local, rode build e teste o fluxo afetado antes de publicar.
