Cell CMS — Persistindo Dados
No último post preparamos a nossa autenticação! Agora é hora de começarmos a pensar em como vamos armazenar os dados deste usuário.
A arquitetura inicial previa que apenas um usuário utilizaria o sistema para publicar conteúdos, o Administrador, porém como utilizamos o Azure AD para autenticar podemos acabar em situações em que mais de um usuário tem acesso ao App.
Por enquanto vamos pensar em um cenário sem separação de usuários. Ou seja, se a pessoa esta autenticada no Active Directory ela terá acesso a tudo.
“Ah Rodolpho, mas como vamos impedir de alguém fazer login no nosso App então e acabar com nossos dados!?”
Lembram de um comentário que coloquei nas configurações do Token no Post Anterior? Futuramente vamos alterar o TokenValidationParameters para validar o Issuer do Token com base nos Tenants que esperamos que tenha acesso ao App! Essa configuração poderá ser alterada em tempo de execução, seja por ambiente ou appsettings.json , permitindo que deploys diferentes validem contra Tenants diferentes.
Planejando e modelando nossos dados
Tudo que for feito para este post estará no commit de merge do branch feature/models-persistencia, então bora conversar sobre dados! Pensando numa versão inicial pensei em três entidades básicas:
- Feeds : Serão os “canais” onde publicaremos os conteúdos. A ideia aqui é que, numa aplicação futura, consigamos apontar para um Feed próprio dessa aplicação!
- Tags ou Labels : Serão “marcadores” dentro de um Feed. A ideia é permitir algum nível de filtro, pelo usuário final, dentro de um feed.
- Content: Será o conteúdo, propriamente dito. Uma entidade contendo ID, relação com Tags, pertencendo a um feed e possuindo um textão markdown
Ou seja, temos os seguintes relacionamentos:
- um Feed contém várias *Tags * (1:N)
- um Feed contém vários *Contents * (1:N)
- um Content possui várias Tags , uma Tag possui vários *Contents * (N:N)
Nada cabeludo até agora. O relacionamento N:N dará um pouco mais de trabalho, mas nada absurdo! Hora de partimos para o código! Abra seu Visual Studio, crie uma nova pasta Models na Api e quatro novas classes para nossos objetos!
Sim, isso mesmo, quatro classes. Para implementarmos um relacionamento N:N com o EntityFrameworkCore precisamos decompor o relacionamento em dois relacionamentos 1:N. Mais informações estão disponíveis na documentaçãodo EntityFrameworkCore.
Vamos às classes:
Criadas as classes, vamos para o EntityFrameworkCore!
Instalando dependências
Instale os seguintes pacotes NuGet em sua API :
- Microsoft.EntityFrameworkCore : A principal biblioteca do EFCore.
- Microsoft.EntityFrameworkCore.Sqlite : A biblioteca contendo suporte para utilizarmos o SQLite.
- Microsoft.EntityFrameworkCore.Design: Biblioteca para a geração de Migrations
Além disso, abra seu terminal e instale a ferramenta do EntityFrameworkCore para dotnet: dotnet tool install --global dotnet-ef. Tudo pronto? Vamos a alguns conceitos do EFCore:
O EntityFrameworkCore é um ORM (Object-Relational Mapper) que permite que mapeamos Objetos para Dados Relacionais, fazendo com que não seja necessário escrever uma camada para Acesso aos Dados em nossas Aplicações [fonte]
Migrations são o meio pelo qual o EntityFramework garante que os Modelos (classes) sejam sincronizados com os Schemas do Banco de Dados. Desta maneira, alterações nas classes (como adicionar um novo campo) são refletidas no banco (através da adição de uma nova coluna) e são versionadas junto ao código.
As configurações das entidades , que serão refletidas no banco, podem ser realizadas através de DataAnnotations ou pela FluentApi. O EntityFramework tentará , por convenções, aplicar configurações automáticas , porém é boa prática escrevermos nossas próprias configurações.
O EntityFramework é uma biblioteca bem vasta então recomendo ir consultando a documentação oficial conforme novos requisitos forem surgindo! Além do bom e velho StackOverflow!
Configurando os Models
Vamos começar a escrever as configurações de nossas entidades. Para isso, crie uma nova pasta na API chamada “Persistence” e vamos criar 3 novas classes:
- FeedConfiguration
- TagConfiguration
- ContentConfiguration
- ContentTagConfiguration
Agora vamos escrever o código para que estas classes realizem a configuração de nossos Models. Para isso precisamos implementar, em cada uma delas, a interface IEntityTypeConfiguration substituindo T pelo Model que será configurado! Feito isso, implemente o método Configure e utilize o builder para montar as configurações, como abaixo:
Com as entidades configuradas agora devemos montar o nosso Context. A ideia aqui é criar um único ponto para acesso aos Dados controlados pelo EntityFramework. Na raiz da Api crie uma nova classe CellContext , ela deverá herdar o DbContext e terá algumas propriedades para expor as tabelas de nossos Models:
Agora basta adicionarmos o Context aos Services e podemos partir para gerar as Migrations :
Criando e Aplicando Migrations
Antes de tudo precisamos compilar a API. Para isso execute dotnet build ou utilize os menus do VisualStudio e garanta que seus appsettings.json contem uma ConnectionString válida para o context a ser migrado!
Se o projeto não estiver compilado ou se estiver sem uma ConnectionString válida e um Context acessível o EntityFramework não conseguirá gerar as Migrations!
Agora abra seu terminal de preferência e execute dotnet ef migrations add InitialCreate -o Persistence/Migrations para criar as Migrations iniciais do projeto com o nome InitialCreate e colocando na pasta Persistence/Migrations:
Serão gerados dois arquivos : Um Snapshot e um “ InitialCreate ”. Um deles é a Migration, que diz quais passos devem ser realizados para atualizar ou desatualizar a base e o outro é uma “Captura” do status esperado que a base fique após aplicação de todas as Migrations.
Finalmente, vamos voltar ao terminal e gerar a nossa base de dados! Digite o comando dotnet ef database update para que o EntityFramework, com base em sua ConnectionString, atualize a base de dados (como ainda não temos uma base, ele irá criar a base para nós!)
Como realizar deploy da base de dados
Um assunto que sempre gera controvérsias com os usuários do EntityFramework é qual a maneira correta de aplicar as Migrations a uma base de Produção. Afinal, temos várias maneiras de fazer isso:
- Através da ferramenta dotnet ef
- Através de Scripts SQL gerados pela ferramenta
- Através do próprio sistema
Pessoalmente eu sou fã da alternativa 3 — Através do próprio sistema. Acredito que no cenário atual com Containers, Kubernetes e tudo ficar gerando scripts para um dba aplicar manualmente é muita mão de obra e vai contra a ideia de termos Entregas e Integrações Contínuas. Obviamente que isso não é verdade para todos os casos, mas sempre que possível sigo por esta linha.
Minha maneira favorita para realizar esta tarefa é:
- Utilizar uma flag , de ambiente, para indicar que a rotina de migração automática deve ser realizada
- Extender o IHost para verificar esta flag e verificar se existem Migrations pendentes
- Se houver Migrations pendentes : Tentar , até um limite de vezes, atualizar a base.
O Snippet abaixo realiza todos estes passos, utilizando a biblioteca Polly para o controle de tentativas, a qual eventualmente abordarei em um post separado por ser extremamente flexível e útil:
Para maiores informações dê uma olhada nestes links:
- https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli
- https://github.com/dotnet/efcore/issues/5096
- https://stackoverflow.com/questions/48617880/ef-core-migrations-in-docker-container
- https://github.com/dotnet/EntityFramework.Docs/issues/814
Considerações Finais
Terminamos o post de hoje por aqui! O assunto EntityFrameworkCore é muito abrangente e poderiamos perder horas e horas falando sobre ele, porém a ideia aqui é dar uma pincelada e permitir que você saia daqui com um “norte” a seguir!
Como sempre tudo que foi realizado durante este post estará disponível no repositório do Cell CMS, em específico no merge do branch feature/models-persistencia!
Obrigado por lerem até aqui e até o próximo post! Abraços!
Top comments (0)