Posts Marcados C#

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

C#, Closures e Resharper

Olá pessoal! Tivemos um problema esta semana, em tese de origem simples, mas que nos tomou um tempo enorme de debug. Quero compartilhar aqui para tentar evitar que outros sigam pelo mesmo caminho!

Para começar, imaginem este código ilustrativo abaixo. Uma rotina simples para enumerar duas vezes por uma coleção e retornar a mesma, modificada. Claro que isto é somente representativo de um código real, mas imaginem qualquer função que por alguma razão modifica elementos de uma lista e retorna a mesma ou um subconjunto da mesma. Trivial, certo? O resultado esperado desta execução são duas linhas com o número “2”.

class MinhaClase {
    public decimal Valor { get; set; }
}
    
class Program {
    static IList<MinhaClase> Calcular(IList<MinhaClase> classes) {
        foreach (var minhaClase in classes) {
            minhaClase.Valor = 1;
        }
        foreach (var minhaClase in classes) {
            minhaClase.Valor++;
        }
        return classes;
    }
    static void Main(string[] args) {
        var classes = new[] {
                                new MinhaClase { Valor = 0}, 
                                new MinhaClase { Valor = 0}, 
                                new MinhaClase { Valor = 1}
                            };
        var result = Calcular(classes.Where(c => c.Valor != 1).ToList());
        foreach (var minhaClase in result) {
                Console.WriteLine(minhaClase.Valor.ToString());
        }
        Console.ReadLine();
    }
}

Todo o problema começa quando a gente tenta refatorar e melhorar o código. Como todo bom desenvolvedor, nós usamos o Resharper, que é uma ferramenta fantástica da JetBrains para nos ajudar a gerar um bom código. Na White Fox, nós a usamos praticamente todo o tempo, ela nos poupa muito esforço de código e ajuda a evitar problemas graves de maneira completamente automática. Porém, neste caso, como a rotina “Calcular” simplesmente enumera, o Resharper vai sugerir que a gente mude o IList<> para sua classe base, um IEnumerable<>. A regra seguida por ele faz sentido pois se só estamos usando métodos da classe base, não seria necessário usar a classe derivada na assinatura. O código gerado então após a refatoração fica da seguinte forma:

class MinhaClase {
    public decimal Valor { get; set; }
}
    
class Program {
    static IEnumerable<MinhaClase> Calcular(IEnumerable<MinhaClase> classes) {
        foreach (var minhaClase in classes) {
            minhaClase.Valor = 1;
        }
        foreach (var minhaClase in classes) {
            minhaClase.Valor++;
        }
        return classes;
    }
    static void Main(string[] args) {
        var classes = new[] {
                                new MinhaClase { Valor = 0}, 
                                new MinhaClase { Valor = 0}, 
                                new MinhaClase { Valor = 1}
                            };
        var result = Calcular(classes.Where(c => c.Valor != 1));
        foreach (var minhaClase in result) {
                Console.WriteLine(minhaClase.Valor.ToString());
        }
        Console.ReadLine();
    }
}

Depois deste refactor, o resultado do programa não deveria mudar, certo? Errado! Os mais experientes irão saber que o resultado da execução deste segundo código é uma lista vazia!

A explicação para isto tem a ver com o suporte do C# para closures (ver este post, que explica o que são de maneira simples, ou esta explicação mais acadêmica aqui). As operações LINQ simplesmente geram predicados para uma closure que é executada no momento que a coleção é acessada. Tem a ver também com a diferença básica entre um IList<> e um IEnumerable<>, já que num IList<>, um GetEnumerator (executado em um foreach) sempre aponta pra coleção interna, já existente… Já em um IEnumerable<>, o GetEnumerator vai sempre acionar o container para criar coleção e aí enumerar os elementos… Isto significa que a coleção de um IEnumerable<> produzido por uma closure vai ser gerada A CADA foreach, e não uma única vez. Ou seja, cada foreach da rotina “Calcular” executa a closure, bem como o foreach do resultado final. Como neste caso a closure é um filtro de Valor != 1, no segundo foreach da rotina “Calcular” e no último (onde ele imprime o resultado no console) ele simplesmente vai retornar 0 elementos!

Onde erramos? Os mais experientes vão notar que erramos na hora que tiramos o .ToList() do final da closure que define o filtro. A razão da remoção foi que como a rotina “Calcular” agora aceita um IEnumerable<>, o ToList() não seria mais necessário. Sem o ToList(), a closure não é mais executada naquele momento e sim passada para a variável e aí toda a rotina Calcular para de funcionar. Basta colocar o .ToList() de volta que tudo passa a funcionar novamente. Este erro, aparentemente óbvio, na prática é muito fácil de ser cometido, pois o uso de expressões LINQ é cada vez mais comum e, em um código complexo de negócios, se a função sendo chamada tem parametros IEnumerable<>, uma closure pode acabar sendo enviada inadivertidamente.

Portanto, neste caso, o Resharper não só facilita os erros, como pode ainda produzir alguns muito difíceis de identificar (no nosso casso, foram horas gastas até achar onde estava a closure sendo passada). Claro que desligamos esta sugestão na nossa configuração do Resharper e o problema foi resolvido. É importante ressaltar que, mesmo apesar deste caso, continuamos a achar o Resharper uma ferramenta essencial de trabalho para nós.

Outro ponto que merece ser comentado, é que pesquisando este problema, entendemos melhor o funcionamento do IEnumerable<> e o IList<>. Como o IEnumerable<> sempre tem que gerar a coleção a cada GetEnumerator(), podemos ter um desempenho pior das coleções, na maioria dos cenários, comparado a um IList<>. Só isto já seria motivo suficiente para favorecer o uso do IList<> ao invés do IEnumerable<>.

Bom, espero ter ajudado a poupar o tempo de outras pessoas ao deparar com problemas similares. Por causa deste problema, acabamos tendo que conhecer bem mais sobre closures e coleções no .NET, assim, no final, a existência deste bug até que foi benéfica!

Até a próxima!

, ,

4 Comentários

Repositórios e Expressões Lambda

Esta semana fizemos mais uma grande melhoria na nossa camada de domínio, incorporando expressões lambda .Net nas nossas consultas e parâmetros de ordenação de repositórios. Retomando o post sobre a camada de domínio, os repositórios são utilizados para tirar a dependência da aplicação dos detalhes de persistência de objetos e para ser uma representação abstrata de uma coleção de entidades de domínio.

A idéia é que eles facilitem a busca de entidades específicas ou implementem métodos mais complexos para a execução de consultas. Como no nosso caso usamos o NHibernate para camada de persistência, nós temos duas opções de efetuar estas consultas, os Criterias (DetachedCriteria ou expressões ICriterion simples) ou o HQL, que é uma linguagem de consulta parecida com SQL só que composta pelas entidades de domínio.

Consultas HQL são baseadas em strings (ver exemplo abaixo do nosso sistema de ServiceDesk), o que é extremamente flexível porém não suporta intellisense nem é compilada (o que pode facilitar erros no caso de refactors). Mas, para o HQL não temos muita opção, a alternativa que seria usar o NH Linq ainda não é viável (pelo que eu tenho visto, ainda não suporta muitas coisas).


public int TotalInbox(User user) {
   const string hql =
       @"select count(*) from Incident i inner join i.Allocation a
         inner join a.SupportGroup s inner join s.Users u 
         inner join i.Company c
         where i.ClosedOn is null and a.SupportUser is null
         and i.State != isnull(c.WaitingUserConfirmationState,-1) 
         and i.State != isnull(c.WaitingUserInformationState, -1) 
         and u = ?";
   return (int) ExecuteScalar<long>(hql, user);
}

O uso de Criterias é mais simples e é indicado para consultas com restrições diretas na entidade base (ou nas que possuem relacionamento direto com a entidade base). Porém, nos criterias é necessário especificar o nome da propriedade como string. Novamente, sem intellisense e sujeito a problemas de refactor. Como o uso de Criterias é muito mais freqüente, nós automatizamos na nossa camada de domínio a geração de uma classe estática com todas as propriedades das entidades de domínio (usando arquivos .tt de um editor T4). Desta forma, temos intellisense e no caso de refactor que cause alguma quebra, teremos um erro de compilação (já que as classes são sempre regeradas). E, conforme falado no post da camada de domínio, construímos ainda uma fluent interface para facilitar a escrita. O exemplo abaixo mostra isto em funcionamento.

public long TotalClosed(User user) {
    return Query.Where(Restrictions.Eq(PN.User, user))
           .And(Restrictions.IsNotNull(PN.ClosedOn)).Count();
}

Esta solução estava bastante adequada até aparecer a alternativa de usarmos expressões lambda ao invés de gerar as classes estáticas. Existem algumas iniciativas já fazendo isto, como o projeto NHLambdaExtensions. Neste caso específico, porém, eles estão abordando isto como uma biblioteca à parte para a geração de Criterias do NH e não como algo integrado aos repositórios.

Felizmente, na nossa camada de domíno, os repositórios já são tipados para a entidade de domínio que eles representam. Desta foram, fica simples gerar as expressões. O exemplo abaixo mostra a mesma rotina acima, agora usando expressões lamdas.

public long TotalClosed(User user) {
    return 
      Query.Where(i => i.User == user && i.ClosedOn != null).Count();
}

Como pode ser visto, bem mais legível e intuitivo. E sem a necessidade de se usar classes estáticas de apoio! Ainda temos algum trabalho a ser feito, pois não é simples traduzir todos os tipos de lambda para restrições válidas. Mas para os casos mais simples como o acima, está já 100% funcionando.

Outro ponto interessante é que as funções que aceitam lambda podem ser expostos para camadas superiores de domínio, já que usar uma expressão lambda não causa dependência para a camada de persistência. Isto diminui de maneira significativa o próprio tamanho da implementação dos repositórios.

Finalmente, a utilização deste tipo de solução acaba sendo algo tão flexível que estamos passando a adotar em vários outros cenários. Na definição de classes de ordenação para consultas, na identificação de campos usados como atributos em formulários e controllers e assim por diante. Realmente algo muito simples que está aí desde o lançamento da versão 3.5 do .Net mas que demorou para  a “ficha cair” achar uma maneira legal de aplicar na nossa infraestrutura.

Quem quiser saber algum detalhe mais técnico da implementação basta me mandar um email. Até a próxima.

, , , , ,

Deixe um comentário

Interlúdio – Boxing

Pequena pausa na série. Programando hoje, apanhei tristemente de uma coisa ultra simples. Estava aprimorando o meu gerador de forms dinâmicos na parte de carga de dados do post. A partir dos parâmetros trazidos, ele preenche a instância do objeto desejado, salvando automaticamente se necessário.

Imaginem um objeto com uma prorpriedade value qualquer (Id p. ex, que é Int32). Como o dynamic form preenche cada propriedade por reflection, no final eu tenho sempre dois objetcs.  Algo tipo assim:

object oldValue = myOldObject.Id;
object newValue = myNewObject.Id;

Só que quando eu comparo (oldValue == newValue) ele retorna sempre false, mesmo que o Id seja o mesmo! Óbvio, por causa do boxing, no == ele compara as referências e aí como não é o mesmo objeto, sempre retorna false.  O que não foi tão óbvio foi como sair desta encrenca já que, em tese, eu deveria fazer o unboxing primeiro, só que como eu só tenho o type por reflection, isto não seria fácil.

Depois de perder quase meia hora e já pensando no pior, achei um post sobre o System.Object.Equal. Ele faz a comparação do conteúdo  e aí funciona para value types. Aí o código fica simplesmente:

System.Object.Equal(oldValue, newValue)

Simples, certo? Mas não óbvio e resolvi postar aqui pra facilitar a vida de outras pessoas que tenham a mesma dificuldade!

Até breve!

Hoje apanhei (pra variar) com uma coisa simples….  Todos devem conhecer o conceito de boxing e unboxing (se não vejam pra ontem!! rsrs), mas no caso de reflection, eu apanhei com algo bobo.
Imaginem um objeto com uma prorpriedade int (Id p. ex). Eu fiz um dynamic form para preencher automaticamente, só que como é por reflection, no final eu tenho dois objetcs.  Algo tipo assim:
object oldValue = myOldObject.Id;
object newValue = myNewObject.Id;
Só que quando eu comparo (oldValue == newValue) ele retorna sempre false, mesmo que o Id seja o mesmo! Óbvio, por causa do boxing, no == ele compara as referencias e aí como não é o mesmo objeto, sempre retorna false. ? O que não é óbvio era como sair desta encrenca já que

, , , , ,

Deixe um comentário

Response.Flush()

Nestes últimos 2 dias eu perdi um tempo impressionante com algo que em tese deveria ter sido extremamente simples de fazer, tipo 5 minutos…  A idéia era fazer um página que iria rodar uma operação longa ir mostrando o andamento da operação, algo como “Passo 1 executado”, “Passo 2 executado” e assim por diante…

Como a regra de negócio estava pronta, achei que fosse trivial ir gerando as tags <p> e ir mostrando o andamento com flushs… Não poderia estar mais errado! Inicialmente tentei usar o HtmlWriter do Render pra ir fazendo os Writes intercalados com os Response.Flush() (fiz um teste com Sleep() pra simular o processamento). Mas não funcionou, ele mostrava a página só no final, com todos os passos de uma vez.

Aí comecei o processo conhecido de buscar no google alguma dica ou esperiência pra entender o que estava errado. Nem precisa falar que o help da MSDN era inútil, aliás copiei e colei o exemplo deles e mesmo comportamento. Comecei a ver que este é um problema para muitas pessoas, aparentemente em algumas situações o Response.Flush() simplesmente não funciona e um mesmo código que funciona para um (como o exemplo da MSDN) não funciona pra outros.

Claro que deve ter algo na página ou no ambiente que faz com que este probelma apareça. Gastei umas boas 4 horas tentando eliminar o que era, utilizando as dicas que achei de pessoas no google para tentar mapear o que era. Mas a questão é que parecia que vários tipos de problemas acabavam se sobrepondo, o principal sendo um próprio entendimento do que o Flush() deveria fazer. Na minha visão (e na visão de várias pessoas que eu vi), ele deveria simplesmente fazer com que o conteúdo gerado até ali fosse para o browser. Mas até sobre isto haviam dúvidas. E aí vinham várias receitas, pra ligar ou desligar buffer, pra fazer com código <% %> intercalado na página ou em determinado evendo em code-behind e até para fazer várias vezes em sequência o comando Flush() para que ele funcionasse! Nenhuma alternativa funcionou pra mim, fui dormir para tentar de novo no dia seguinte.

No dia seguinte, mais 2h. O tempo gasto nisto já estava absurdo e poderia até ter feito com javascript simples, mas vocês sabem, vira questão de honra!  Continuando as busca no google, achei um comentário dizendo que uma instrução específica, faria toda a diferença: Response.BufferOutput = false. E neste cenário, realmente funcionou, mas somente no código aspx direto! Já que neste caso estava funcionando, comecei a tentar isolar o que realmente causava o probelma. Mas não tive sucesso, qualquer pequena alteração fazia com que o código deixasse de funcionar. Desisti, o código final ficou desta forma:

—————————————————————————————————————–

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”  “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”><html  >
<head><title>Correçao de Pesquisa</title></head>
<body style=”font-family: Calibri, Tahoma, Arial”>
<p>Correção em Progresso. Aguarde por favor….</p>
<%  Response.Buffer = false;
Response.BufferOutput = false;
for (var i = 0; i < 10; i++) {
%><span>teste</span><br /><%
Response.Flush();
System.Threading.Thread.Sleep(1000);
} %>
</body>
</html>
—————————————————————————————————————–
Impressionante que este código não funciona se for colocado no evento Load ou Render da página. De fato deve ter alguma coisa no ambiente que impede isto, mas tive que dar um basta no tempo perdido para este problema. Ah, mais um ponto interessante, a tag SPAN funciona melhor do que a tag P – que mesmo com o código funcionando dá uma impressão de agrupamento ao ser mostrado no browser.

Enfim, é impressionante como perdemos tempo com coisas triviais. Esta do Response.Flush é mais um exemplo. Fazer software é muito bom, mas este tipo de coisa não é muito!

, , , ,

3 Comentários