tucaz.blog.now() Rotating Header Image

Performance: NHibernate versus ADO.NET

Disclaimer

Os testes neste post apresentados não representam uma amostra exata ou fidedigna que represente uma condição real de acesso a dados de uma aplicação a fim de comparar a performance das duas tecnologias. A idéia é apenas mostrar alguns dados e exemplos com o objetivo de dismistificar a idéia de que ORM é uma ferramenta lenta e estes testes são apenas um exercício.

Sou usuário de NHibernate e defendo o uso de ORM’s portanto as conclusões apresentadas com certeza não são as mais imparciais possíveis.

Motivado por esta thread no DNA hoje decidi fazer alguns testes de perfomance pra comparar acesso a dados utilizando ADO.NET nativo (queries AdHoc e Stored Procedures) versus NHibernate.

Sempre rolam diversas discussões a respeito do assunto e a conclusão que geralmente se chega é de que qualquer ORM vai ser mais lento do que uma chamada nativa. É uma conclusão óbvia já que utilizar um ORM é adicionar uma camada de abstração a mais dentro da nossa aplicação. No entanto, até então eu nunca havia efetuado nenhuma medição pra ver qual a diferença de performance.

Todos os testes foram executados na minha máquina com código compilado em modo Release com banco de dados Sql Server 2008 também local.

Vamos aos testes.

Cenário de testes

Criei uma tabela (Product) e populei com cerca de 500 registros vindos do AdventureWorks (banco de dados exemplo do SqlServer).

Modelo de Dados

Contra essa tabela executei duas categorias de testes:

  1. Carregar todos os registros da tabela em um List<>
  2. Carregar apenas um registro

Pra ficar mais interessante fiz algumas variações dos testes:

  • Query AdHoc/Inline
  • Query AdHoc/Inline com hidratação[1] via reflection
  • Query utilizando uma procedure
  • NHibernate com LINQ
  • NHibernate com HQL
  • NHibernate com Criteria
  • Nhibernate com Projections

Como rodando uma única vez não foi possível obter dados suficientes, executei cada teste dentro de um loop com 1000 (numberOfIterations = 1000) iterações que resultou no código abaixo:

Método principal:

static void Main(string[] args)
{
    for (int i = 1; i <= 3; i++)
    {
        Console.WriteLine("Test " + i.ToString());
        Console.WriteLine("======================");

        SqlAdHocAllProducts();
        SqlAdHocAllProductsWithReflection();
        SProcAllProducts();
        NHibernateAllProductsWithLinq();
        NHibernateAllProductsWithHql();
        NHibernateAllProductsWithCriteria();
        NHibernateAllProductsWithProjections();

        SqlAdHocOneProduct();
        SqlAdHocOneProductWithReflection();
        SProcOneProduct();
        NHibernateOneProduct();

        Console.WriteLine();
    }

    Console.ReadLine();
}


Um dos métodos usando Sql AdHoc e outro usando NHibernate:

private static void SqlAdHocAllProducts()
{
    List<Product> allProducts = null;

    var connectionString = "Data Source=(local);Integrated Security=SSPI;Database=TDC2010;";
    var select = @"SELECT P.Id, P.Description, P.Name, P.Price FROM dbo.Product P";

    var connection = new SqlConnection(connectionString);
    connection.Open();

    Stopwatch watch = new Stopwatch();
    watch.Start();

    for (int i = 0; i < numberOfIterations; i++)
    {
        allProducts = new List<Product>();
        var command = new SqlCommand(select, connection);
        var reader = command.ExecuteReader();

        while (reader.Read())
        {
            allProducts.Add(new Product()
                {

                    Id = Convert.ToInt32(reader["Id"]),
                    Name = Convert.ToString(reader["Name"]),
                    Description = Convert.ToString(reader["Description"]),
                    Price = Convert.ToDecimal(reader["price"])
                });
        }

        reader.Close();
    }

    watch.Stop();

    connection.Close();
    connection.Dispose();

    Console.WriteLine(
        "Loading " + allProducts.Count + " Products with Sql AdHoc took " + watch.ElapsedMilliseconds + " ms");
}

private static void NHibernateAllProductsWithLinq()
{
    List<Product> allProducts = null;

    var session = CreateForSqlServer().OpenSession();

    Stopwatch watch = new Stopwatch();
    watch.Start();

    for (int i = 0; i < numberOfIterations; i++)
    {
        allProducts = session.Linq<Product>().ToList();
    }

    watch.Stop();

    session.Close();

    Console.WriteLine(
        "Loading " + allProducts.Count + " Products with NHibernate took " + watch.ElapsedMilliseconds + " ms");
}

Vou omitir o restante dos métodos para evitar duplicações já que eles são apenas variações dos dois exemplos acima.

Resultado Geral

Resultados dos Testes

Análise dos Resultados

Stored Procedures versus Queries AdHoc/Inline [500 registros]

A diferença entre o uso de stored procedures e queries AdHoc é praticamente inexistente. A diferença média medida foi de menos de 1%.

Isso acontece, pois não existe complexidade suficiente neste tipo de query para que a armazenagem do plano de execução no banco de dados faça diferença.

Portanto, na grande maioria dos cenários Stored Procedures não são necessárias.

NHibernate versus NHibernate [500 registros]

Na média, todas as variações de consulta utilizando NHibernate também tiveram mais ou menos o mesmo resultado (~1500ms) com exceção do uso de Projections, que levou o dobro (~3000ms) do tempo.

Não conheço o NHibernate suficiente pra afirmar com 100% de certeza o motivo, mas acredito que seja pelo fato de esse tipo de query retornar Arrays bidimensionais que são criados e redimensionados em runtime até que todos os itens possam ser acomodados.

NHibernate versus ADO.NET [500 registros]

Este e o comparativo mais importante. Carregando (e hidratando) 500 registros o ADO.NET é cerca de 30% mais rapido do que o NHibernate. A causa dessa discrepância é uma só e se chama reflection.

Apesar de todas as otimizações o NHibernate utiliza-se de reflection para efetuar a hidratação[1] de todos os objetos e é dai que vem a queda de performance que fica clara quando executamos queries AdHoc usando hidratação via reflection conforme o código abaixo.

private static void SqlAdHocAllProductsWithReflection()
{
    List<Product> allProducts = null;

    var connectionString = "Data Source=(local);Integrated Security=SSPI;Database=TDC2010;";
    var select = @"SELECT P.Id, P.Description, P.Name, P.Price FROM dbo.Product P";

    var connection = new SqlConnection(connectionString);
    connection.Open();

    Stopwatch watch = new Stopwatch();
    watch.Start();

    for (int i = 0; i < numberOfIterations; i++)
    {
        allProducts = new List<Product>();
        var command = new SqlCommand(select, connection);
        var reader = command.ExecuteReader();

        while (reader.Read())
        {
            var newProduct = Activator.CreateInstance<Product>();
            SetProperty(newProduct, "Id", Convert.ToInt32(reader["Id"]));
            SetProperty(newProduct, "Description", Convert.ToString(reader["Description"]));
            SetProperty(newProduct, "Name", Convert.ToString(reader["Name"]));
            SetProperty(newProduct, "Price", Convert.ToDecimal(reader["Price"]));
            allProducts.Add(newProduct);
        }

        reader.Close();
    }

    watch.Stop();

    connection.Close();
    connection.Dispose();

    Console.WriteLine(
"Loading " + allProducts.Count + " Products with Sql AdHoc and Reflection took " + watch.ElapsedMilliseconds + " ms");
}

private static void SetProperty(object instance, string property, object val)
{
    Type t = instance.GetType();
    var prop = t.GetProperty(property, BindingFlags.Instance | BindingFlags.Public);
    prop.SetValue(instance, val, null);
}

Este código quando executado demora cerca de 6000ms, ou 4 vezes mais, do que o código executado pelo NHibernate.

Mas por que a diferença não é de 30%? Porque o NHibernate possui otimizações quanto ao modo de hidratar um objeto via reflection. No meu código acima podemos ver, por exemplo, que toda vez que chamo o método SetProperty o Type da propriedade a ser refletida ainda não está criado. Provavelmente o NHibernate deve manter cache deste tipo de informação (e de outras) a fim de otimizar o processo de hidratação das entidades.

Stored Procedures versus Queries AdHoc/Inline [1 registro]

Mesmo resultado do cenário onde 500 registros são carregados. Não há diferença.

NHibernate versus ADO.NET [1 registro]

Aqui a diferença é gigantesca sendo de quase 4000% a favor do NHibernate. Isso acontece, pois o NHibernate implementa cache nível 1 nativamente então dentro de uma mesma ISession o objeto é carregado apenas uma vez enquanto com ADO.NET é necessário ir ao banco e carregar o objeto diversas vezes.

Conclusões

Olhando para os números apenas, em casos onde diversos registros precisam ser carregados, ADO.NET nativo oferece uma performance superior e parece ser a escolha óbvia. No entanto:

  • Os testes com NHibernate foram executados utilizandos exemplos simples e sem qualquer tipo de otimização.
  • NHibernate oferece nativamente cache de resultados de queries e cache nível 2 que se utilizados iriam exibir um resultado bem próximo ao cenário “NHibernate versus ADO.NET [1 registro]” onde o NHibernate é 4 vezes mais rápido.
  • ADO.NET oferece um custo de desenvolvimento e manutenção altissimo e este custo torna-se ainda maior se utilizado com Stored Procedures que transformam o cenário em algo totalmente caótico de gerenciar devido a dificuldade de manter a rastreabilidade desses diabinhos malignos.

No caso de sistemas OLTP onde as transações carregam unidades individuais e/ou pequenas coleções de entidades por sessão, NHibernate não só é mais rápido como também oferece muito mais flexibilidade pra lidar com praticamente todos os cenários existentes. Este post do Ayende mostra 25 funcionalidades importantes (cache, gerenciamento de concorrência, etc) que você vai precisar quando estiver lidando com dados e que custariam muito caro (tempo e complexidade) caso você queira escreve-las “na mão”.

Em cenários de aplicações de internet onde o número de leituras é infinitamente superior ao número de escritas no banco de dados deve se utilizar cache no front end (IIS), portanto o tempo que se leva pra montar uma página é irrelevante. Mesmo que você carregasse os dados de um servidor remoto via conexão discada não faria diferença uma vez que os dados estivessem em cache.

Por último, se você se encontrar em uma situação utilizando NHibernate onde o acesso a dados é o gargalo da sua aplicação seu problema não é o NHibernate (a não ser que você tenha usado-o de maneira totalmente absurda, mas a probabilidade de você fazer o mesmo com ADO.NET é grande também). Nestes cenários o problema não é o acesso a dados em si, mas o resto da arquitetura que não escala de maneira adequada seja por meio de cache, processamento assincrono, filas, etc.

Sendo assim, acredito que podemos concluir que não faz sentido algum no meio do ano de 2010 utilizar queries AdHoc ou Stored Procedures em aplicações LoB. :)

O código completo utilizado está no GitHub e pode ser acessado online ou baixado em formato zip.

[1] – Hidratação é o processo de preenchimento (filling) das propriedades de uma entidade

Material da palestra no TDC2010 – ORM: Por que isso te interessa?

barra-top-basic

No último fim de semana rolou em São Paulo o TDC2010 organizado pela Globalcode. Fui convidado a palestrar na trilha de .NET pelo @giovannibassi pra falar um pouquinho sobre ORM e porque é uma boa idéia usá-lo.

Abaixo estão os slides da minha palestra, que não fazem muito sentido por si só, mas que tem umas imagens divertidas!

Em um dos últimos slides tem uma porção de links muito legais sobre NHibernate então pra quem não quiser ter o trabalho de ver os slides ai vai:

http://nhforge.org/ -> NHibernate official website
http://fluentnhibernate.org/ -> Fluent Mappings for Nhibernate
http://nhprof.com/ -> NHibernate Profiler with 30 day trial
http://www.summerofnhibernate.com/ -> More than 14 hours of free videos
http://www.manning.com/kuate/ -> “NHibernate In Action” Book by Manning
http://ayende.com/Blog/category/510.aspx -> NHibernate @ Ayende’s
http://tinyurl.com/25reasons -> 25 reasons NOT to write yout own ORM @ Ayende’s
http://fabiomaulo.blogspot.com/search/label/NHibernate -> NHibernate @ Fabio Maulo’s
http://blogs.hibernatingrhinos.com/nhibernate/ -> NHibernate Blog
http://code.google.com/p/unhaddins/ -> unhaddins, Unofficial addins for Nhibernate

Além dos slides, coloquei no GitHub também o código fonte do projetinho que usei na demo que fiz. Apesar de ser uma demo, o código serve como referência pra começar a utilizar o NHibernate e FluentMapping e ter uma idéia de algumas features bem legais que ele oferece.

Download do zip com o código.

Palestraram também, da Stefanini, @vquaiato na trilha de Ruby e .NET, @alnascimento e @manoelp em Agile, @vcavalcante em .NET e @jorgediz em Testes.

Por último, mas não menos importante eu queria deixar registrado meu agradecimento ao Oren Eini (a.k.a. Ayende Rahien) pelos cupons de desconto e licensa do NHProf que distribui ao final da palestra e também a Candace da Manning Publications pelas cópias dos eBooks “NHibernate In Action” que também dei de brinde. Muito obrigado pelas contribuições a comunidade! :)

Now in English so I can make this public to English speakers too. Thanks to Oren Eini (a.k.a Ayende Rahien) for the discount coupons and NHProf license that I gave out and to Candace from Manning Publications for the copies of “NHibernate In Action” that were also given at the event. You guys are amazing. Thanks a lot for contributing with this growing community! :)

Ninject, StructureMap e Padrões de Injeção de Dependência

Este post foi motivado por esta thread no DNA e por essa question no StackOverflow.com a respeito de um problema que encontrei nesta última semana em um cenário relativamente complexo de injeção de dependência.

Contexto

A arquitetura em questão utiliza injeção de dependência com base em interfaces, ou seja, todas as dependências das minhas classes são sempre pra interfaces (contratos) e nunca para classes concretas (implementação) exatamente como manda o figurino.

Meu domínio é o padrão: carrinho de compras com Pedido e seus itens. Uma classe é responsável por efetuar o processamento do pedido (GeradorPedido). Ela executa todos os procedimentos necessários para que um pedido seja gerado de maneira correta, dentre eles aplicar um determinado fator de ajuste de preços dos itens acordo com a loja onde o pedido foi vendido. Este fator de ajuste é determinado pela classe Precificador e deve ser desconhecido para o restante dos outros objetos a fim de não violar o SRP.

O diagrama abaixo representa as classes envolvidas e suas dependências:

Como aplicar este fator ao preço do item não é responsabilidade do GeradorPedido, sempre que uma instância de GeradorPedido é criada, injeto via construtor uma nova instância de Precificador pra ser usado quando necessário. Neste caso, temos uma dependência direta de GeradorPedido para IPrecificador que é resolvida em tempo de execução.

Tudo estava sendo resolvido utilizando Ninject:

MyModule.cs

public class MyModule : NinjectModule
{
    public override void Load()
    {
        Bind<IPrecificador>().To<Precificador>();
        Bind<IGeradorPedido>().To<GeradorPedido>();
    }
}

UnitTest1.cs

[TestMethod]
public void consegue_resolver_GeradorPedido()
{
    MyModule module = new MyModule();
    StandardKernel kernel = new StandardKernel(module);
    var geradorPedido = kernel.Get();

    Assert.IsNotNull(geradorPedido);
}

Precificador.cs

class Precificador : IPrecificador
{
    private decimal _fatorAjuste;

    public Precificador()
    {
        _fatorAjuste = 10m;
    }

    public decimal CalcularPreco(ItemPedido item)
    {
        return item.Preco * _fatorAjuste;
    }
}

GeradorPedido.cs

class GeradorPedido : IGeradorPedido
{
    private IPrecificador _precificador;

    public GeradorPedido(IPrecificador precificador)
    {
        _precificador = precificador;
    }

    public string Processar(Pedido novoPedido)
    {
        decimal total = 0;

        foreach (var item in novoPedido.Itens)
        {
            total += _precificador.CalcularPreco(item);
        }

        //Faz mais algumas operações com pedido

        //Número do pedido
        return "ABC123";
    }
}

Até aqui, tudo OK, certo? Ai entra um novo requisito e com ele aparece o problema…

Novo Requisito

Alguém decidiu que o fator dos preços deveria variar de acordo com a loja em que o pedido estava sendo feito. Se isso é uma regra de preço, a qual classe pertence? Precificador!

Quais opções temos pra implementar isso com o mínimo de impacto possível no sistema (já que a interface IPrecificador já estava sendo usada) e de maneira que a coesão e desacoplamento seja mantido?

Opção 1 – Adicionar um paramêtro no método CalcularPreco

A primeira opção que me veio a cabeça foi adicionar o paramêtro com a loja diretamente no método CalcularPreco da interface IPrecificador.

IPrecificador.cs

class Precificador : IPrecificador
{
    private decimal _fatorAjuste;

    public decimal CalcularPreco(ItemPedido item, Loja vendaEfetuadaEm)
    {
        if (vendaEfetuadaEm == Lojas.LojaUm)
            _fatorAjuste = 10m;
        else if (vendaEfetuadaEm == Lojas.LojaDois)
            _fatorAjuste = 15.4m;
        else
            _fatorAjuste = 0.9m;

        return item.Preco * _fatorAjuste;
    }
}

Parecia uma boa saída, mas depois de pensar alguns minutos encontrei dois side-effects graves que me fizeram mudar de idéia:

  1. O novo paramêtro, Loja, passaria também a virar uma dependência direta pra todos que consumissem a interface IPrecificador já que estes [consumidores] seriam responsáveis por repassar a loja para poder realizar a chamada a CalcularPreco. Com isso estariamos violando o SRP adicionando um motivo a mais pra classe GeradorPedido e outras mudarem.
  2. Diversos testes seriam quebrados pela adição do novo paramêtro, indicando que talvez não fosse a melhor alternativa já que essa alteração deveria afetar apenas uma classe

Opção 2 – Adicionar este paramêtro ao construtor da classe Precificador e utilizar uma Factory

Já que esta alteração diz respeito apenas a responsabilidade da classe Precificador, que tal adicionar este paramêtro ao construtor da classe? Ótima idéia, não?

De fato foi a solução que fez mais sentido já que necessariamente para chegar ao fator de preço a ser aplicado a classe precisa saber com qual loja estamos lidando. Para implementar, bastaria fornecer a loja via construtor e armazenar o valor em um membro privado da classe pra uso posterior. Dessa forma, todos os consumidores dessa classe iriam receber uma instância de IPrecificador já configurada e pronta pra uso sem a necessidade de se preocupar em fornecer a loja.

Nosso novo Precificador.cs ficaria assim:

class Precificador : IPrecificador
{
    private decimal _fatorAjuste;

    public Precificador(Loja vendaEfetuadaEm)
    {
        if (vendaEfetuadaEm == Lojas.LojaUm)
            _fatorAjuste = 10m;
        else if (vendaEfetuadaEm == Lojas.LojaDois)
            _fatorAjuste = 15.4m;
        else
            _fatorAjuste = 0.9m;
    }

    public decimal CalcularPreco(ItemPedido item)
    {
        return item.Preco * _fatorAjuste;
    }
}

No entanto, na hora que fui implementar esta solução esbarrei no meu container de DI, até então o Ninject, que resolvia a dependência de IPrecificador pra mim automaticamente sempre que necessário. Contudo, pra esta implementação eu precisaria fornecer um valor (a Loja) que só podia ser obtido em runtime na hora de construir a instância de IPrecificador.

Então a solução seria utilizar uma Factory!

GeradorPedidoFactory.cs

class GeradorPedidoFactory
{
    public static IGeradorPedido Criar(Loja vendaEfetuadaEm)
    {
        IPrecificador precificador = new Precificador(vendaEfetuadaEm);
        return new GeradorPedido(precificador);
    }
}

Mas trinta segundos depois descartei essa idéia porque IPrecificador estava sendo usado por outras classes além de GeradorPedido então eu teria que criar uma factory pra cada uma das classes que fosse consumidora de IPrecificador. Também dessa forma eu estaria anulando meu Container de DI espalhando a criação de tipos concretos por diversas factories ao invés de centralizar em apenas um ponto.

Opção 3 – Manter o construtor e utilizar uma Abstract Factory pra criar IPrecificador

A terceira e ultima opção antes da solução final foi utilizar uma Abstract Factory, que seria injetada via DI em todo mundo que precisasse de IPrecificador eliminando a necessidade de uma factory pra cada construtor e mantendo a responsabilidade no lugar adequado. Algo assim:

PrecificadorFactory.cs

class PrecificadorFactory : IPrecificadorFactory
{
    public IPrecificador Criar(Loja vendaEfetuadaEm)
    {
        return new Precificador(vendaEfetuadaEm);
    }
}

MyModule.cs

public class MyModule : NinjectModule
{
    public override void Load()
    {
        Bind<IPrecificadorFactory>().To<PrecificadorFactory>();
        Bind<IGeradorPedido>().To<GeradorPedido>();
    }
}

GeradorPedido.cs

class GeradorPedido : IGeradorPedido
{
    private IPrecificadorFactory _precificadorFactory;

    public GeradorPedido(IPrecificadorFactory precificadorFactory)
    {
        _precificadorFactory = precificadorFactory;
    }

    public string Processar(Pedido novoPedido, Loja vendaEfetuadaEm)
    {
        IPrecificador precificador = _precificadorFactory.Criar(vendaEfetuadaEm);
        decimal total = 0;

        foreach (var item in novoPedido.Itens)
        {
            total += precificador.CalcularPreco(item);
        }

        //Faz mais algumas operações com pedido

        //Número do pedido
        return "ABC123";
    }
}

Contudo, na hora que implementei pra ver como ficaria, percebi que tinha o mesmo problema da primeira solução: todo mundo que precisasse consumir IPrecificador teria que receber o paramêtro informando a Loja onde a venda foi efetuada para repassar para a abstract factory de IPrecificador a fim de criar uma instância concreta da classe. A dependência tinha voltado, apesar de a responsabilidade estar um pouco melhor distribuida. :(

A Solução Final

Quando eu já estava quase desistindo de procurar outra alternativa (mais uma) o @pedroreys deu uma idéia excelente: fornecer previamente ao container de DI a instância de IPrecificador que deveria ser usada quando ela fosse necessária.

Na hora fui atrás de como fazer isso com o Ninject que, era meu container até então. Infelizmente ele não implementa essa funcionalidade especifíca. Pelo menos não da maneira que eu gostaria [1].

Foi ai que decidi mudar para o StructureMap, container utilizado no exemplo do @pedroreys. O StructureMap, apesar de ter sido um dos primeiros containers de DI/IoC lançados (e de ser um pouco verboso demais pro meu gosto), se mantém atualizado e com uma excelente interface fluente exatamente como o Ninject.

A troca foi simples já que eu utilizo um Wrapper (omitido por breviedade) pro Ninject e a aplicação não tem contato com o container em si. O resultado acabou sendo um Mix de todas as soluções:

PrecificadorFactory.cs

class PrecificadorFactory
{
    public static IPrecificador Criar(Loja vendaEfetuadaEm)
    {
        return new Precificador(vendaEfetuadaEm);
    }
}

UnitTest1.cs

[TestMethod]
public void consegue_resolver_GeradorPedido()
{
    Loja vendaEfetuadaEm = RecuperarLojaOndeVendaFoiEfetuada();

    IPrecificador precificador = PrecificadorFactory.Criar(vendaEfetuadaEm);

    Container container = new Container();

    IGeradorPedido geradorPedido = container.With<IPrecificador>(precificador).GetInstance<IGeradorPedido>();

    Assert.IsNotNull(geradorPedido);
}

Dessa forma consigo:

  1. Criar a instância de IPrecificador separadamente
  2. Dizer ao container que quero que esta instância especifica seja usada somente nesta resolução de IGeradorPedido
  3. Manter cada classe com sua responsabilidade

Any thoughts o this?

[1] – O Ninject, na última versão (2.0), implementa o método Rebind() que permite trocar o bind de uma interface em runtime. No entanto, esta troca é permanente e afeta todos os consumidores do container (Singleton, no meu caso) e no meu contexto, não faz muito sentido.

A caminho do Zen

Hoje temos a nossa disposição milhares de fontes de informação e algumas dezenas de grandes agregadores de informação (rss, twitter, etc) que multiplicam a quantidade de informação a que somos expostos diariamente.

Agora, o desafio não é encontrar a informação, mas sim escolher qual informação iremos consumir. Até pouco tempo atrás quantidade significava qualidade, mas isso já não é mais verdade e a maioria das pessoas está tentando diminuir a quantidade de coisas que faz e a quantidade de informação a que é exposta. Menos virou mais.

Diariamente, incluindo RSS, Twitter, DNA e os links gerados a partir destes agregadores (que são os que mais uso) acho que gasto umas quatro horas lendo. Isso sem contar a pilha de livros que compro mensalmente e que vai se acumulando para leitura posterior.

Por conta disso, acabo não tendo tempo de executar os projetos que gostaria, entre eles escrever mais neste blog. Ler é excelente, mas a rede da web faz com que cada link aberto gere mais outros 200 e não conseguimos processar tudo ao mesmo tempo. Por isso, foco é fundamental.

Nos últimos dias defini uma lista de projetos/items que quero concluir nas próximas semanas. Sendo assim, vou tentar reduzir minha exposição à toda essa informação pra dar uma “desintoxicada” e focar nestes itens.

Estou compartilhando essa dificuldade por dois motivos: 1) Tornar um problema público faz com que a gente se mexa pra resolve-lo, afinal ninguém gosta de ter seus problemas expostos (ainda mais sem resolve-los, né?) e 2) Ver se alguém compartilha do mesmo “problema” e entender como cada um lida com isso, portanto você que está lendo isso, saia do read-only mode e comente este post! :)

Você é importante?

Reza a lenda[1] que, trabalhando na Apple, a qualquer momento você pode encontrar Steve Jobs e ouvir dele a seguinte pergunta: “O que você faz aqui?” e, caso ele não goste da resposta você pode ser demitido na hora.

Apesar de essa ser uma atitude drástica, acho que faz bastante sentido, principalmente no cenário de desenvolvimento de software. Alguma vez você já se perguntou o que realmente faz na empresa onde trabalha? Se a resposta for tão simples como “escrevo código” talvez você esteja prestes a ser (merecidamente) demitido promovido ao mercado de trabalho . Mesmo como programador seu trabalho não é apenas escrever código, mas sim transformar idéias em maravilhas tecnológicas que vão fazer seu usuário delirar e com isso agregar valor para a empresa ou qualquer outra coisa que o valha. Isso vale para todas as posições.

Desde gerentes (que tradicionalmente só controlam cronogramas, ou seja, fazem nada) até gerentes de produto que ficam pra cima e pra baixo mandando as pessoas fazerem coisas. O ponto é, se você não contribui para o produto final você é inútil, totalmente dispensável e deveria arrumar uma outra coisa pra fazer. Sua contribuição não precisa ser necessariamente técnica (com código), mas também com soft skills (organização, segurança, criatividade, etc) que ajudem efetivamente seu time a chegar ao objetivo.

Acho que esse é um ponto importante de reflexão. Para sermos melhores no que fazemos temos que entender realmente o que queremos e temos de fazer.

O que você acha?

[1] – Segundo relatos existentes de (ex) funcionários no livro “A cabeça de Steve Jobs”

Cuidado com os especialistas!

Hoje em dia com toda essa modernidade (como diria minha vovó) e a facilidade na disseminação da informação que a internet nos trouxe surgem a cada dia mais e mais especialistas. Em cada esquina da internet é possível encontrar um. Não importa o assunto.

O cara começa com um blog ou um grupo de discussão, manda bastante tweets, adiciona uma pitada de promoção pessoal em todo canto, dai avança pra meia dúzia de palestras e até se propõe a ministrar um curso sobre o assunto. De repente, em poucos tempo virou especialista E referência no assunto!

Vendo tanta propaganda você deve achar que o cara tem muita experiência e é uma ótima fonte de informação. Boa fonte de informação pode até ser, mas repetir tudo que ve como um papagaio é ser um especialista? Ministrar cursos e posar de conhecedor do assunto apenas com conhecimento teórico é suficiente? É ético?

Será que é essa mesmo a definição de especialista que procuramos? Ainda mais sobre assuntos não técnicos ou soft-skills onde toda a bagagem e experiência que podemos conferir é escrita/falada?  São essas nossas referências?

Tenha cuidado quando alguém lhe apresentar o título de “Especialista em XYZ”. Papel e meios de comunicação similares aceitam qualquer coisa, já dizia minha professora na quina série.

Infelizmente não há maneiras práticas e fáceis de reconhecer falsos especialistas. O único jeito é desconfiar sempre e não disseminar informações baseadas na falácia da autoridade. Seus amigos, seu chefe e seu bolso agradecem!

Lidando melhor com WCF – Ciclo de vida no cliente

<Importante>

Todos sabem que WCF é uma plataforma poderosa e extensível. O que muita gente não sabe é que WCF é para poucos.

WCF é uma plataforma que permite a construção de sistemas distribuidos e sistemas distribuidos raramente são necessários. De maneira simplificada, podemos dizer que um sistema é distribuido quando tem seus componentes instalados em mais de um computador.

O problema da construção de sistemas distribuídos é que devido a não centralização de seus compnentes existe um overhead de comunicação entre os computadores e serialização/deserialização de objetos e isso consome muita memória e banda de rede.

Como gastamos mais recursos do que necessário temos como resultado final um aumento no tempo de resposta e gasto extra de hardware que, as vezes pode ser um problema grave além é claro do aumento da complexidade técnica.

No entanto, não sei por qual motivo, no momento em que lançou o produto, a Microsoft esqueceu de avisar seus “consumidores” a respeito destes pequenos detalhes. Talvez porque queria promover o produto?

</Importante>

<Importante 2>

WCF é uma tecnologia relativamente simples, mas apenas depois que você entende o BeABá da coisa (ou ABC[1] no caso do WCF). Até lá, qualquer coisa que fuja do point&click que o Visual Studio nos oferece é relativamente complexo.

Portanto, sugiro que antes de aplicar WCF você procure entender um pouco mais dos fundamentos e do papel de cada objeto que ele utiliza.

</Importante 2>

Agora que entendemos que não devemos usar WCF em toda aplicação que construimos (talvez isso seja tópico pra outro post?) vou falar um pouco da experiência que estou tendo com esta tecnologia e das boas práticas e atalhos que conheci.

Pretendo fazer uma pequena série e pra começar vou abordar boas práticas de consumo de serviços WCF, ou em outras palavras, como acessar um serviço WCF a partir de um cliente da maneira mais eficiente possível.

Para que seja possível consumir um serviço WCF é necessário que alguém o tenha publicado e também que tenha disponibilizado o WSDL[2] do serviço.

O WSDL nada mais é do que um arquivo XML que descreve quais são os tipos de dados que o serviço irá trafegar e quais operações ele oferece.

Geralmente, assim que temos um WSDL para consumir vamos ao Visual Studio e utilizamos a funcionalidade de Add Service Reference que ele nos oferece para gerar o objeto proxy que irá cuidar pra nós da comunicação com WCF. A partir dai utilizamos este objeto em toda nossa aplicação. E este é o nosso maior erro.

Este proxy, gerado pelo Wizard, encapsula todas as classes necessárias para ler o WSDL, criar os tipos necessários e lidar com os canais de comunicação em cada operação que executamos. No entanto, ele o faz da pior maneira possível.

Cada vez que invocamos uma operação através deste proxy, ele cria uma instância da classe ChannelFactory<T>. Esta classe é responsável por ler as configurações definidas em nosso app.config e baseado nestas configurações abrir os canais de comunicação com o serviço que iremos consumir. Este processo é muito lento, pois todos os sockets, listeners e objetos necessários da própria infra estrutura do WCF são criados e configurados neste momento.

No entanto, este processo não precisa ser feito a cada chamada para o serviço WCF uma vez que estas informações não mudarão enquanto a aplicação estiver rodando. Então, para otimizar bastante o consumo de serviços devemos ignorar o proxy gerado pelo Wizard e cuidar da criação e destruição de canais nós mesmos mantendo este objeto (ChannelFactory<T>) como Singleton que irá durar pelo tempo de vida da aplicação. Dessa forma toda vez que chamamos uma operação remota não existe a necessidade de executar o setup e tear down desse objeto.

Para facilitar a gerência desses objetos construi uma pequena DLL que é composta apenas de uma classe. O código está disponível no github e a DLL compilada está aqui. A documentação inicial também está lá. Tudo pronto para ser usado.

Referências adicionais (e importantes)

[1] ABC = Address, Bindings e Contracts
[2] É possível consumir um serviço WCF sem acesso ao WSDL, mas este é um cenário pouco comum

[Off-Topic] Problemas com monitor Samsung e exemplo de péssimo atendimento

Não costumo fazer esse tipo de off-topic, mas essa situação me revolta simplesmente pelo fato de não poder fazer nada além de reclamar e “chorar” por ai.

Em dezembro de 2007 adquiri um monitor Samsung 2232BW pelo valor de R$1027,00. Até onde eu sei esse valor não é baixo e tem gente que não ganha isso em um ano inteiro de trabalho.

Pouco mais de 2 anos passados, mês passado ele começou a apresentar um problema com seu painel. Isso já me deixou p*** da vida, pois 2 anos de vida útil em um produto deste valor é rídiculo. Meu CRT 17 nunca deu um problema em quase 5 anos que ficou em uso.

Como um monitor equivalente hoje custa na faixa de R$500, decidi que valeria a pena tentar levar meu monitor para conserto e caso o custo do serviço ficasse em até R$200,00 acredito que compensaria.

Procurei no site da Samsung as assistências técnicas autorizadas, que apesar de serem terceirizadas, levam o nome Samsung e deveriam responder com o  mesmo padrão de qualidade que a Samsung vende em seus produtos e encontrei 3 em um ráio de 20km da minha casa. Fui as três e realmente conclui que elas atendem seguinde o mesmo padrão inexistente de qualidade da Samsung com filas de mais de uma hora e meia apenas para ser atendido na recepção pra tentar descobrir se é possível o conserto e qual seria o valor.

Conclusão: é mais fácil ser atendido no poupa tempo ou em um posto do INSS do que em um serviço autorizado da Samsung que iria receber pelo atendimento.

Obrigado Samsung pelos momentos de frustração e pela demonstração de como operar um negócio onde o cliente claramente não tem importância nenhuma além, é claro, de me colocar em uma posição onde eu tenha que implorar para ter meu monitor consertado!

Qual é a dos apertadores de botões?

Não importa onde eu vá, seja a empresa grande ou pequena, sempre existem pessoas dos dois grupos: apertadores de botões e as pessoas que vão pra frente.

As pessoas que vão pra frente são fáceis de identificar e não me interessa falar muito sobre elas neste post. Elas estão sempre empolgadas, comentam a respeito de novidades e procuram fazer ela e as pessoas em sua volta prosperarem, mesmo quando essa não é a intenção.

Já os apertadores de botão estão sempre na mesma. O incrível é que esse perfil (ou falta de perfil?) existe em todos os papéis e profissões. DBAs -que só rodam scripts e não analisam o log (nem quando solicitado!) pra ver o que aconteceu e ajudar o pobre desenvolvedor que não tem acesso ao DB-, Desenvolvedores -que programam o que está especificado não importando o quão errado esteja do ponto de vista técnico ou funcional-, entre muitos outros.

No entanto, podemos ainda criar outra categoria de apertadores de botões: os que fazem apenas tarefas repetitivas. Quem nunca viu o “operador de computador” (acho que essa expressão é do mesmo tempo CPD) que todo dia, o dia inteiro, fica copiando/alterando arquivos de lá pra cá, sempre igual?

Por mais que eu não goste do meu trabalho (ainda bem que eu gosto!), acredito que devemos tirar o melhor proveito dele. Ou melhor, devemos tirar o melhor proveito de tudo que fazemos na vida. Portanto, o que motiva uma pessoa a executar o mesmo processo igual todo dia sem nem mesmo pensar em uma maneira de automatizá-lo ou de faze-lo de maneira diferente?

Mesmo que você não consiga remover todos os processos repetitivos ou não crie uma obra de arte quando conseguir é importante tentar. Não importa se você vai ser reconhecido por isso, não importa se você irá ganhar $algo$ em troca e não importa se você não é pago pra automatizar nada. Faça por você! Mesmo quando o esforço não valer a pena você vai no mínimo ter aprendido alguma coisa nova.

Portanto, se você se encaixa em algum desses perfis, já passou da hora de se mexer e mudar, não acha?

Reunião .NET Architects em 16/01/2010 – Referências

Ontem durante a reunião do nosso grupo de discussão de .NET o Fabio Galuppo falou sobre um pouco sobre F#. A apresentação foi muito boa e logo após, durante a “mesa redonda”, eu mencionei uma série de aulas gravadas na Universidade de Berkeley sobre o assunto Linguagens Funcionais. É o curso completo gravado. Como o pessoal se interessou e pediu os links ai vai:

Depois da reunião oficialmente terminada fomos almoçar e como acabamos entrando no assunto de gestão de projetos comentei um pouquinho a respeito de gestão democrática e prometi deixar alguns links disponíveis: