Posts Marcados azure

Banco NoSql em Azure

Olá pessoal, continuando a série iniciada aqui, esta semana vou detalhar como migramos nossa API de controle de acesso de um banco de dados relacional Sql Server para um banco de dados NoSql, em Azure.

Os bancos NoSql estão sendo cada vez mais empregados em aplicações web devido à sua escalabilidade e menor custo. No entanto, seu uso exige uma série de cuidados e nem todos os domínios podem ser facilmente mapeados para utilizar este tipo de estrutura de dados. Domínios que tenham consultas complexas, que dependam de índices ou de extensas manipulações de entidades ou que utilizem um número elevado de transações em entidades diversas, podem ter uma alta dificuldade de mapeamento.

No nosso caso, escolhemos migrar a nossa API de segurança e autenticação. Ela foi uma boa opção por ser baseada em domínio pequeno, com ações pontuais (login, cadastro) e poucas consultas avançadas. Outro ponto favorável é que nosso sistema de segurança é multi-tenant, o que é relativamente fácil de implementar em um banco NoSql mas no mínimo problemático, em um banco relacional. Mais detalhes sobre isto abaixo.

Para a migração para NoSql no Azure, avaliamos utilizar o CosmosDb e o Azure TableStorage padrão. O interessante é que a forma de acesso e a estruturação de dados são praticamente iguais nos dois, o que muda é que no CosmosDb temos alguns recursos a mais (como por exemplo a remoção automática de registros por tempo), além de um melhor suporte para escalabilidade. Porém, enquanto o Azure TableStorage é bem barato (praticamente cobrança baseada em espaço alocado), o CosmosDb já é bem mais caro – cobrado por operações realizadas. No nosso cenário, o custo mensal do CosmosDb chegou perto do custo do Sql Server – cerca de USD 60 por mês, para o tamanho e uso do nosso banco. Como não temos grandes necessidades de contingência, acabamos escolhendo o TableStorage, e o seu custo mensal ficou inferior a 10 USD.

A ideia do uso de um banco NoSql usando o TableStorage é relativamente simples: um conjunto de tabelas identificadas por 2 tipos de índices, PartitionKey e RowKey. Alguns bancos suportam mais tipos de índices, mas no caso do Azure TableStorage, só temos estes 2 mesmo. Por ser uma aplicação muti-tenant, o identificador do Tenant é o PartitionKey, o que nos deixa somente a RowKey para identificar as entidades. Se partirmos de um mapeamento clássico relacional, o RowKey seria a Primary Key (PK) que identificaria cada instância. As Foreign Keys (FK) podem ser mapeadas por propriedades (colunas) na tabela, resolvendo os mapeamentos do tipo 1:N. O problema são chaves compostas e relacionamentos N:N, que exigem um esforço maior de mapeamento. A ausência de outros índices explica por que é complicado mapear um sistema com grande quantidade de relatórios e consultas, já que teríamos que criar uma estrutura para representar cada índice complexo e armazenar neles as RowKeys relativas às entidades que são identificadas pelo índice. Possível, mas complexo.

No nosso caso, simplificamos o modelo para poucas entidades (ver diagramas abaixo, do modelo original e do NoSql), cada um identificado pela sua RowKey. Tínhamos algumas necessidades de relacionamentos N:N, como por exemplo a de aplicações e de permissões em usuários. Para estes casos, criamos uma coluna na tabela do usuário contendo a serialização da coleção destas entidades. Estas colunas são carregadas e deserializadas quando a entidade é trazida da TableStorage.

Modelo Relacional
Tabelas Azure Table Storage
Dashboard Azure Storage

Para fazer a manutenção das tabelas no Azure TableStorage, sugiro usar o Azure Storage Explorer. É uma aplicação bem simples mas que permite efetuar todas as operações necessárias.

Outro ponto que é bem diferente do modelo relacional é carga (load) das entidades. No modelo relacional, o típico é mapear todas as colunas para o objeto e carrega-lo de uma vez do banco de dados. Como no NoSql podemos ter muitas colunas que são utilizadas como repositório (como as de coleções, por exemplo), carregar todas as colunas seria um custo elevado. Assim, tipicamente, a cada operação de acesso ou de salvamento, somente as colunas afetadas são solicitadas ou alteradas.

Para facilitar a persistência das entidades, criamos um pequeno framework com as operações básicas. Denominamos “entidade raiz” aquelas que estão ligadas diretamente ao Tenant, tais como Application, User ou RefreshToken (ver modelo acima). As entidades raiz são carregadas diretamente pelo RowKey e pelo TenantId específico – ver trecho de código abaixo. As demais entidades são subordinadas a uma entidade raiz (como Roles ou Permissions) nelas, a PartitionKey é sempre a chave da entidade Raiz e a RowKey é a sua chave específica – ver abaixo. Fizemos também métodos para carregar ou salvar objetos na TableStorage que permitem especificar as colunas necessárias para cada operação. Finalmente, fizemos também métodos para facilitar a serialização/deserialização de coleções quando são repositórios em colunas.

public Task<T> LoadTenantRootEntity<T>(Guid id, params string[] columns) 
	where T : TableStorageEntity, new() {
	return LoadTenantRootEntity<T>(GuidToString(id), columns);
}

public async Task<T> LoadTenantRootEntity<T>(string id, params string[] columns) 
	where T: TableStorageEntity, new() {
	var retrieveOperation = TableOperation.Retrieve<T>(tenant.Identifier, id, columns.Length > 0 
		? columns.ToList() 
		: null);
	var result = await Table<T>().ExecuteAsync(retrieveOperation);
	return result.Result as T;
}

Código para carga de entidade raiz com TenantId

protected async Task<IList<T>> LoadAll<T>(string partitionKey, params string[] columns) where T : TableStorageEntity, new() {
	var query = new TableQuery<T>().Select(columns).Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey));
	TableContinuationToken continuationToken = null;
	var results = new List<T>();
	do {
		var result = await Table<T>().ExecuteQuerySegmentedAsync(query, continuationToken);
		continuationToken = result.ContinuationToken;
		results.AddRange(result.Results);

	} while (continuationToken != null);
	return results;
}

Código para carga de entidades filtradas por Partition Key

O resultado ficou bem interessante. As operações básicas ficaram bastante simples e o tempo de resposta das operações ficou muito bom. Em vários cenários, temos um desempenho bem superior ao modelo relacional – nas operações de login, por exemplo, tivemos um ganho muito expressivo. A implementação de multi-tenant ficou muito mais simples do que seria em um modelo relacional, graças aos índices de PartitionKey.

A conclusão é que o banco NoSql é uma boa alternativa pra vários cenários, mas nem sempre é algo simples de se modelar. É um mecanismo de persistência muito mais barato, em termos de Azure, quando comparado a um banco Sql Server. E, dependendo da implementação, é mais rápido e mais facilmente escalável do que um Sql Server tradicional. Como tudo em TI, é conveniente pesar os prós e contras antes de partir para uma linha NoSql. E sempre fazer uma prova de conceito antes de iniciar a migração. Mas tenho certeza que, se bem construído, um sistema com banco NoSql pode ser uma melhor opção do que a linha relacional padrão, em vários cenários.

Fiquem à vontade para comentar ou se tiverem alguns outros cenários de aplicações NoSql e quiserem trocar uma ideia. O próximo post será sobre o Azure Api Management. Até lá!

, , ,

Deixe um comentário

Azure DevOps

Oi pessoal! Iniciando a sequência de artigos sobre tecnologias e plataformas citadas aqui, vou começar pelo básico: configuração de ambiente DevOps no Azure.

Embora DevOps esteja em adoção crescente há vários anos, sua adoção em larga escala só se tornou possível com o foco da Microsoft no Azure DevOps. A migração do antigo TFS para o GIT, a integração de todas as ferramentas em https://dev.azure.com/[suaempresa] e  a automatização via pipelines de CI (Continuous Integration) e CD (Contiunous Deploy) tornou todo o processo simples de ser implementado.

Bom, vamos assumir que estamos começando do zero e montar um ambiente de DevOps no Azure e mostrar todos os passos necessários. O único requisito é ter uma assinatura Azure válida para se registrar os artefatos.

O primeiro passo é criar a empresa no DevOps. Isto pode ser feito em https://dev.azure.com, bastando seguir os tutoriais. O primeiro artefato que temos que criar é o projeto inicial. O termo projeto aqui pode gerar confusão, pois há a tendência de se tratar um projeto como projeto Visual Studio. Mas, na nossa experiência, é mais produtivo tratar um projeto DevOps como sendo todo o contexto de um cliente. Assim, todas as solutions, projetos visual studio, artefatos etc. relativos a um cliente normalmente são armazenados em um único projeto DevOps. Abaixo a tela de gestão do projeto no Azure.

Dentro de um projeto podemos ter múltiplos repositórios GIT. Usualmente é criado um repositório para cada projeto do tipo bibliotecas, serviços ou sistemas. Já vi algumas defesas de se ter um repositório único para tudo (nós até já chegamos a usar este formato por algum tempo), mas isto complica a geração dos pipelines de CI. Assim, preferimos usar repositórios separados mesmo. Como é típico em um repositório GIT, temos o branch master, que armazena a versão de produção e branchs de desenvolvimento, usados para gerar as versões para testes e homologação. Em organizações que precisam de um compliance mais forte, pode ser criada uma política que impeça o commit direto no branch master, exigindo um pull request do desenvolvimento para ele. Este pull request pode também exigir aprovadores, o que garante um alto controle do que segue para o master. Claro que isto tem um impacto negativo em produtividade. Como na White Fox o objetivo é agilidade, nós não temos estes controles ativados.

Outro ponto importante para configurar no projeto é um feed de artefatos (último elemento na barra lateral esquerda). Este feed tem como finalidade a publicação dos pacotes Nuget das bibliotecas utilizadas naquele projeto DevOps. Embora o feed seja para uso interno (ele exige autenticação), dá para se configurar acesso para outros projetos ou mesmo para outras empresas. Na White Fox, estamos migrando todos os nossos pacotes Nugets de um servidor público para um feed de projeto, simplificando nossa infra e facilitando o processo de CI/CD.

Com o feed de artefatos criado, o próximo passo é criar os pipelines de CI para cada projeto utilizado. Ao se criar um pipeline, temos duas opções: utilizar pipelines clássicos ou baseados em arquivos yaml. Em geral, o pipeline clássico atende os cenários típicos, enquanto que os baseados em arquivos yaml permitem uma maior flexibilidade e customização das tarefas. A figura abaixo mostra a tela de criação. Inicialmente se escolhe o repositório de origem e aí basta seguir o assistente que vai gerar o arquivo yaml. Para escolher criar um pipeline clássico, basta usar o link no final da tela.

Usualmente nossos pipelines de CI fazem as seguintes tarefas: 1 – baixar artefatos nuget; 2 – compilar projetos; 3 – executar testes unitários; 4 – publicar artefato no próprio pipeline. Abaixo um pipeline clássico web e um de arquivo yaml.

Um alerta: apesar da Microsoft já ter recentemente permitido o desenvolvimento de Azure Functions em .NET Core, ainda não é possível fazer um pipeline para deploy deste tipo de Function. Se alguém tiver este cenário, é necessário por enquanto ainda fazer uma publicação manual – o que não é nada complexo, dá pra fazer direto do Visual Studio. Creio que em breve este tipo de pipeline também estará disponível.

Após termos os pipelines de CI funcionando, o último passo é criar os pipelines de release – CD. Usualmente o pipeline de release é criado baseado em um trigger de pipeline de CI bem sucedido. O pipeline de CD simplesmente baixa os artefatos e, caso seja uma aplicação web, publica direto no Web App; caso seja um Nuget, publica direto no feed de artefatos. Abaixo exemplos de pipelines Nuget e web app. Abaixo um pipeline web.

Finalizando os pipelines de release, voi-la, temos todo o nosso ambiente operacional em DevOps CI/CD. Um commit no master irá iniciar o pipeline de CI, que irá compilar, testar e deixar pronto pra publicação. Quando o CI finalizar, o pipeline de CD irá pegar os artefatos e automaticamente publicar no servidor Nuget ou direto no Web App.

Um último item que gostaria de apontar é sobre notificações. O DevOps por padrão já envia e-mails no sucesso ou falha do CI para quem fez o commit. É tranquilo também configurar o envio de e-mails para um grupo no caso de sucesso ou falha – dá até pra criar automaticamente workitems em falhas, se for desejado. No nosso caso, como somos usuários do Microsoft Teams, nós preferimos receber as notificações por ele.

Existe um bot público chamado Azure Pipelines – basta buscar em aplicativos no Teams. Ao instalar, ele permita que façamos assinaturas dos pipelines e publiquemos notificações em canais específicos. Assim, toda vez que um Release é feito com sucesso, todos nós da White Fox recebemos uma notificação no Teams automaticamente.

É isto pessoal. Uma vez configurado, o Azure DevOps funciona impressionantemente bem. Todos os nossos artefatos principais já estão em CI/CD e isto tem poupado muito tempo nosso em testes e deploys. Recomendo a qualquer empresa de software que passe a usar o Azure DevOps com CI/CD, se ainda não o faz!

No próximo artigo vou falar um pouco de services .net core usando NoSql. Até breve!

, , , , , , , ,

Deixe um comentário

Casa de Ferreiro…

Olá pessoal! Como estão de quarentena, com quase 5 meses de isolamento? Neste tempo muita gente aproveitou para aprender novas habilidades como cozinhar, pintar etc. No meu caso, resolvi empregar o tempo em algo que estávamos precisando fazer há muito tempo. Como prega o ditado, “casa de ferreiro, espeto de pau”, os sistemas utilizados internamente na White Fox já tinham passado da hora de serem atualizados. Ainda tínhamos sistemas em tecnologia antiga, hospedados em formatos que não recomendaríamos para ninguém.

Então, de março para cá, gastamos muitas horas para atualizar 100% do nosso parque tecnológico. E não só atualizar, aproveitamos para colocar tudo no estado-da-arte da que existe hoje. Como já citei várias vezes, estar atualizado é parte fundamental de nosso trabalho. Mas, principalmente por falta de tempo, a gente foca em empregar novas tecnologias e práticas nos nossos clientes e deixa de lado os nossos sistemas internos. Bom, não mais! Depois destes meses, estamos novamente orgulhosos de nossos sistemas! Eles estão utilizando o que há de melhor e mais moderno, tanto em boas práticas quanto em tecnologia. Neste artigo, vou fazer uma sinopse de como ficou a estrutura completa e, nas próximas semanas, detalhar melhor algumas das tecnologias e plataformas empregadas.

Para começar, fizemos uma refatoração e uma separação de camadas, isolando todas as nossas regras de negócios em APIs REST. Estas foram hospedadas em um API Gateway com o uso o Microsoft Azure Api Gateway – que é fantástico, com certeza teremos um artigo só para ele. O Api Gateway age como fachada para a cada API, gerenciando o acesso através de assinaturas e implementando as regras que se fazem necessárias. A figura abaixo mostra o uso do nosso API em uma semana normal.

Uso do White Fox Api Gateway

Aproveitamos que queríamos ter um caso de estudo de banco de dados NoSql e convertemos todo nosso sistema de autenticação e segurança para usarmos um banco de dados deste tipo. Fizemos testes com o Cosmos Db mas acabamos utilizando mesmo o Azure TableStorage padrão. O resultando ficou muito bom, detalho isto em artigo futuro.

Cada uma das APIs foi documentada em swagger usando o swaggerhub – que é completamente integrado ao Azure Api Gateway. Todas as APIs foram implantadas com o uso do Microsoft DevOPs, com Continuous Integration (CI) e Continuous Delivery (CD). Assim, após o commit, todos os testes unitários são automaticamente executados e, quando aprovados, atualizados diretamente no API Gateway, ficando disponível para aplicações web, móveis ou outras.

The API gateway pattern versus the direct client-to-microservice ...
Aplicaçoes usando API Gateway

O uso de APIs Rest nos sistemas Windows e Web ficou bem simples com o uso do Unchase Open API Connected Service (que pode ser instalado no Visual Studio). Ele se usa das especificações swagger para gerar um proxy local, similar à uma referência WCF.  O primeiro sistema que atualizamos foi o sistema interno de gestão, com o uso de um site Angular 10. Um ponto que tivemos cuidado de implementar no sistema web foi a utilização de chamadas de APIs de ação de forma 100% assíncrona. Isto melhora o tempo de resposta para o usuário e a escalabilidade do sistema. Mais sobre isto em artigos futuros

Dashboard White Fox Web

O segundo sistema que atualizamos foi o nosso aplicativo Windows. Ele é utilizado por alguns desenvolvedores que preferem ter algo sempre ativo no desktop. Com o uso do Unchase, também foi super simples migrá-lo para utilizar o Api Gatway.

Agora a “jóia da coroa” deste período foi a Lana. Já éramos usuários do Microsoft Teams há um bom tempo, e tínhamos ouvido que o Microsoft Bot Framework 4 estava valendo a pena, especialmente integrado ao Teams. Assim, mergulhamos a fundo e o resultado foi excelente. Construímos um bot, chamado Lana, para o Teams da empresa, que age como interface para todas as nossas APIs. E por ser um bot, comunicamos em linguagem natural escrita de maneira rápida e intuitiva. O resultado ficou tão bom que poucos ainda usam o sistema web ou aplicação Windows. Com certeza farei um artigo sobre a Lana.

Tudo tem lados positivos e negativos e esta pandemia não foi diferente. Sem ela não teríamos todo este tempo disponível para atualizar e avançar nosso conhecimento sobre as mais recentes plataformas e tecnologias. Podemos afirmar com certeza que agora em casa de ferreiro, temos um espeto de aço inoxidável da melhor qualidade! Nos próximos artigos detalho um pouco mais cada um destes componentes. Até breve!

, , , , ,

2 Comentários

Produtividade – Camada de Domínio – Revisão

Olá pessoal! Em 2009 (nossa!), eu escrevi uma série sobre produtividade, onde a ideia era colocar as técnicas que usamos no nosso dia a dia na White Fox para o desenvolvimento de sistemas. Embora os princípios continuem todos válidos, a tecnologia evolui. Assim, a ideia é fazer uma série atualizando cada uma destas postagens, de acordo com a linha que usamos atualmente.

A primeira delas é sobre a camada de domínio. Nesta camada, tivemos um amadurecimento de tecnologias que hoje tornam mais fácil a vida de quem precisa utilizar um ORM. No entanto, cresceram as dúvidas e questionamentos teóricos sobre que mecanismo utilizar para acessar dados. A linha do NoSQL ganha força e é o padrão para muitos tipos de sistema, especialmente agora, com um uso maior de sistemas baseados em PAAS como Azure ou Amazon.

Mesmo para os sistemas com domínio orientado à objeto e banco SQL, há vários questionamentos sobre se faz sentido o uso de um ORM. Existem várias pessoas que apontam os problemas de se tentar usar um (exemplo aqui) e nós mesmos já sofremos bastante com problemas oriundos deste mapeamento; recentemente fiz até um post sobre isto. Existem até aqueles que questionam o uso de OO em si, ou os que estão partindo para linguagens funcionais como o F#.

Mas, para quem usa bancos SQL, o uso de ORM ainda é a melhor alternativa. Claro que isto pode gerar problemas e definitivamente há casos em que é melhor não usar. Mas em termos de produtividade e manutenabilidade, ainda é a melhor opção. Temos usado consistentemente em todos nossos sistemas e, salvo algumas exceções em pequenas áreas, o resultado tem sido excelente.

Em termos de tecnologia, temos mais alternativas do que o nHibernate, que reinava absoluto em 2009. Vários pequenos frameworks como o nPoco tem um uso mais difundido. E o Entity Framework (EF) da Microsoft amadureceu e hoje compete de igual para igual com o nHibernate. Nossos sistemas usam predominantemente o nHibernate, até porque temos grandes sistemas em operação que começaram com ele. Mas, para novos sistemas, estamos preferindo o EF, principalmente por sua excelente integração com o LINQ do .NET. Utilizamos o database-first, com a declaração fluente de mapeamento e ainda usamos arquivos .TT para gerar todos os artefatos como repositórios, containers etc.

Quando o sistema é muito pequeno ou quando desempenho é um requisito especialmente severo, criamos uma versão de ORM que utiliza stored procedures diretamente. É claro que isto tem sérias restrições, mas para estes tipos de sistemas conseguimos otimizar removendo quase todas as camadas e utilizando o máximo poder do servidor SQL.

Para tentar minimizar a manutenção, criamos bibliotecas comuns a todos os ORM que utilizamos. Assim, trabalhar em sistemas que usam ORMs diferentes é uma experiência similar da camada de negócios para cima. Temos caso até de, em um mesmo sistema, termos diferentes áreas usando diferentes ORMs. Claro que nestes casos, temos que ter uma camada de comunicação para garantir integridade, muitas vezes baseadas em microservices.

Em termos de regras de negócio, continuamos utilizando um modelo anêmico. Esta continua sendo uma guerra santa na comunidade técnica, mas no nosso caso, não temos como negar todos estes anos de sucesso. O modelo anêmico facilita o isolamento de regras de negócio, simplifica o treinamento de novos desenvolvedores e a manutenção de todos os nossos sistemas. E estamos ainda colhendo alguns bônus adicionais, pois este tipo de modelo tem facilitado o isolamento de porções de regras de negócio para encapsulamento em micro-serviços, o que nos tem permitido evoluir nossos sistemas mais antigos de maneira viável. E, na inevitável migração para a nuvem, que deve ocorrer ao longo dos próximos anos, vamos poder também fazer uso de recursos avançados como Azure Servless Functions, graças a esta arquitetura.

Nos próximos posts devo comentar sobre a camada de interface, onde tivemos as maiores mudanças e as maiores evoluções tecnológicas. Até lá!

, , , , ,

Deixe um comentário