terça-feira, 18 de novembro de 2008

MS Architecture Journal: Computação Distribuída

Para os leitores do Jornal de Arquitetura da Microsoft, a edição 17 já encontra-se acessível no The Architecture Journal Reader. No momento, disponível apenas em Inglês.

O assunto desta edição é a computação distribuída. Há uma série de artigos interessantes nesta edição, contemplando modelagem e padrões arquiteturais para sistemas distribuídos, entre outros.

Links

segunda-feira, 27 de outubro de 2008

Teste unitário para dispositivos móveis (SmartDevices)

O desenvolvimento de aplicativos para dispositivos móveis está cada vez mais difundido no mundo todo. Com essa onda de desenvolvimento nos deparamos com algumas situações novas, como, por exemplo, teste unitário de aplicações para dispositivos móveis. As aplicações para dispositivos móveis tem se tornado cada vez mais ricas em termos de utilização de recursos dos dispositivos. Antes o que parecia impraticável, hoje já é trivial, como utilizar uma base de dados local (SQLCe por exemplo). Com esses novos recursos e funcionalidades torna-se praticamente imprescindível utilizarmos testes unitários. E para facilitar nossa vida o Visual Studio 2008 tem suporte para testes unitários. É possível rodar o teste unitário tanto no dispositivo quanto no emulador.

Testes unitários

Como a audiência deste blog é bem variada, para quem não sabe o que é, teste unitário é uma forma de verificar se pequenas unidades de código estão funcionando corretamente. É a menor porção testável de uma aplicação. Idealmente, os testes unitários são independentes entre si e geralmente são desenvolvidos por programadores para garantir que o código elaborado por eles se enquadra nos requisitos do software e se esse código se comporta como o esperado.  Em termos de orientação a objetos, a menor porção possível para ser testada é um método. Caso tenha dúvidas a respeito de orientação a objetos, recomendo os excelentes post publicados pelo Eduardo Klein que falam dos aspectos básicos da orientação a objetos.

Pré-requisitos para os testes

Primeiro, além do Visual Studio 2008 Professional naturalmente, é necessário também um SDK para desenvolvimento de aplicações móveis, como Windows Mobile 5 SDK ou o Windows Mobile 6 SDK. O Visual Studio 2008 Pro vem com o SDK do PocketPC 2003 e do Windows CE pré-instalados. Nos testes que eu realizei, optei pelo Windows Mobile 6 SDK com um projeto para Windows Mobile 6 Professional (para .NET Framework 3.5).

Criando um projeto de testes

Os testes unitários podem ser criados a partir de um código fonte ou a partir de um assembly .NET. Nos testes que eu realizei, gerei os testes unitários a partir dos códigos fontes. Para criar os testes de uma maneira facilitada é só utilizar o Unit Test Wizard (Test - New Test). Neste processo é possível criar um novo projeto de teste unitário ou adicionar testes em um projeto de teste unitário já existente (verifique a opção Add to Test Project). Lembre-se de selecionar a opção Create a new Smart Device Visual C# Test Project..., ou adicione em um projeto de teste de Smart Device, caso contrário não irá aparecer os fontes da solução. Não vou entrar em muitos detalhes sobre isso aqui porque este processo é bem trivial. Após a geração dos testes você observará que foi criado um projeto que testa todas os métodos que foram selecionados. É possível executar todos os testes unitários ou selecionar os desejados, bem como clicar com o botão direito em cima de um método e pedir para ser executado. Outra observação que vale ser colocada aqui é que, além de gerar testes de todos os métodos públicos, o Wizard também gera testes para os métodos privados. Para acessar o método privado, é criado uma classe Acessor (com o nome <Classe a ser testada>_Acessor). Através dessa classe o teste unitário acessa e testa o método privado.

Debug do teste unitário

Ao tentar depurar o código dos testes unitários, notei que os breakpoints não eram atingidos. Com um pouco de pesquisa percebi que não era possível depurar testes unitários para Smart Devices. Porém, na documentação do próprio Visual Studio, encontrei um "contorno operacional", vulgo "gambiarra", para fazer o teste unitário funcionar em modo debug. Os passos são os seguintes:
  1. Habiltar o debug no dispositivo: Acesse a ferramenta Visual Studio Remote Registry Editor (que está dentro da pasta Visual Studio Remote Tools). Crie uma nova key em HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETCompactFramework de nome Managed Debugger. Adicione a esta chave um valor do tipo DWORD de nome AttachEnabled e com o valor 1.
  2. Coloque breakpoints no código: Coloque um breakpoint no ponto em que deseja parar a execução do teste. Mas não os breakpoints usuais, e sim coloque breakpoints com
    System.Diagnostics.Debugger.Break();
    Após este código, os breakpoints normais também serão atingidos.
  3. Inicie o teste unitário
  4. Espere o programa parar: Depois que o breakpoint é atingido, aparecerá uma janela de warning no dispositivo.
  5. Anexe (Attach) o processo do dispositivo ao Debugger do Visual Studio: No menu Debug do Visual Studio clique em Attach to Process, depois mude o Transport para Smart Device. Mude também o Qualifier para o dispositivo que está rodando o teste unitário e selecione SmartDeviceTestHost.exe na lista de processos (Avaliable Processes). Finalmente, clique em Attach.
Por último clique em continue no dispositivo e o código poderá ser depurado passo a passo.

Links

quinta-feira, 23 de outubro de 2008

Persistência de dados com o db4o

Olá a todos, este é meu primeiro artigo aqui no blog de Arquitetura de Sistemas e Mobilidade. Como o Eduardo já introduziu em um post anterior, também trabalho na Mobiltec, na parte de arquitetura e análise de sistemas, especialmente na parte bancária. Porém, também sou adepto da descoberta e utilização de novas tecnologias, e justamente por isso, irei falar um pouco do db4o. Nos exemplos estou utilizando o LINQ (Language INtegrated Query), mas não irei comentar nada sobre ele por enquanto, deixei isto para um próximo post.
Para quem não conhece o db40, trata-se de uma ferrameta responsável por armazenar os objetos em uma base de dados. Todo o controle do armazenamento dos objetos e do acesso ao mesmo é feito pela ferramenta. Nenhum mapeamento ou conexão com um servidor de dados é necessário. O db4o trabalha com um arquivo em disco para salvar os objetos. Como um exemplo vale mais do que mil palavras: Digamos que possuímos um objeto Task e desejamos armazená-lo, tudo que devemos fazer é:
//Criando um objeto task com duas propriedades "Name" e "Enabled"
Task t = new Task { Name = "nome tarefa", Enabled = true };
//Salvando o objeto
db4oBase = Db40Factory.OpenFile("task.db4o");
db4oBase.Store(t);
db4oBase.Commit();
"Só isso mesmo?", você deve estar se perguntando. Sim, exatamente isso. Essa é justamente a idéia desta ferramenta, ser simples. Segundo os próprios donos da ferramenta: "db4o is the open source object database that enables Java and .NET developers to store and retrieve any application object with only one line of code, eliminating the need to predefine or maintain a separate, rigid data model.". Para saber mais sobre base de dados orientado a objetos, sugiro a leitura da referência http://en.wikipedia.org/wiki/Object_database

Problema ao armazenar um objeto estruturado

Durante o desenvolvimento de uma ferramenta para registrar tarefas, de uso pessoal, me deparei com o seguinte problema:
  • Existe uma classe chamada TimeSheetInfo que armazena as informações sobre o tempo de uma tarefa que possui, por sua vez, uma referência a uma classe chamada TaskInfo que possui as informações da tarefa. Existe também uma função que salva a hora que a tarefa começou, que recebe como parâmetro somente o nome da tarefa. Neste caso, o código abaixo funciona corretamente?
IObjectContainer db4Conn = 
Db4objects.Db4o.Db4oFactory.OpenFile("base.db4o");
try
{
    //Criando uma nova instância para armazenar na base de dados
    TimeSheetInfo timeSheet = new TimeSheetInfo();
    //Vinculando o tempo a tarefa 'teste' já existente
    timeSheet.Task = { Name = "teste", Enabled = true };
    //Colocando as definições da tarefa
    timeSheet.Date = DateTime.Now.Date;
    timeSheet.ClockIn = DateTime.Now;
    timeSheet.NotWorkingTime = _blnNotWorkingTime;
    db4Conn.Store(timeSheet);
    db4Conn.Commit();
}
catch (Exception ex)
{
    db4Conn.Rollback();
    throw;
}
finally
{
    db4Conn.Close();
}

Por mais que possa parecer óbvio, o código acima não funciona da maneira correta. Se consultarmos a base de dados, veremos duas entradas para a tarefa passada como parâmetro. O que aconteceu? O db4o encarou a tarefa criada como uma tarefa nova, e não uma referência a uma já existente. Mas pela lógica do método eu não quero criar uma tarefa nova, e sim vincular um horário a tarefa passada como parâmetro. Isso ocorre porque o db4o trabalha com as instâncias exatas do objeto, ou seja, para atualizar um objeto ou vinculá-lo a outro (como neste exemplo) devemos buscar o objeto e realizar as operações com aquela instância que foi recuperada. Duas modificações são possíveis, uma é receber a classe TaskInfo ao invés do nome. Porém vale lembrar que este objeto deve ser o mesmo que foi armazenado na base de dados, caso contrário continuará criando entradas extras como no exemplo do código acima. E outra alternativa, mais fácil de implementar, mas que pode trazer um pouco de ônus no tempo de execução do método (dependendo do tamanho da base), é buscar o objeto antes e criar o vínculo com o TimeSheetInfo, como no exemplo abaixo:
timeSheet.Task = (from TaskInfo t in db4Conn
                  where t.TaskName == "teste"
                  select t).Single<TaskInfo>();
Conversando com o Eduardo Klein, fui questionado porque eu fiz assim no projeto que estou fazendo. Porque eu passo uma string ao invés do objeto TaskInfo direto? Pensando um pouco, essa é a melhor aborgagem para este tipo de código. A resposta é que eu tinha uma versão do programa que trabalhava com o SQLite e eu simplesmente alterei o DAL para suportar o db4o. Mas realmente, nesse tipo de abordagem, onde o objeto é importante devemos mudar um pouco o paradigma. Ao invés de receber uma string, o correto era receber mesmo o objeto, ou seja, trabalhar com orientação a objetos.

Performance

A performance do db4o depende do tipo de query que se utiliza. Existem basicamente 4 tipos de queries possíveis:
  • Query By Example (QEB)
  • Native Queries
  • LINQ
  • SODA Query API
Cada uma tem suas particularidades e problemas. Para ser sincero, não trabalhei com todas, apenas com o LINQ, mas estudei o tutorial do próprio db4o para saber um pouco de cada uma dessas queries. A mais básica de todas (e a mais limitada) é a QEB. Como o próprio nome diz, cria-se um exemplo de objeto e realiza-se a busca. Várias são as limitações dessa abordagem:
  • Não é possível fazer expressões avançadas com AND, OR, NOT, etc.
  • Não é possível procurar por objetos com campos com valores vazios ou nulos (ou zero no caso de inteiros)
Para contornar essas limitações, o db4o recomenda o uso das Native Queries. Com esse tipo de query é possível realizar várias consultas na mesma linha de código. O LINQ dispensa maiores apresentações. Mas, a mais importantes de todas as queries, é a SODA. Pois todas as anteriores, por baixo dos panos, são transformadas em SODA. Ou seja, SODA Query API é o mais baixo nível do db4o. Com certeza, utilizar diretamente a SODA torna o código mais performático, porém menos legível e mais suscetível a erros, pois as propriedades das classes são passadas como strings para que a consulta seja realizada. Utilizando SODA parece que estamos utilizando as queries que normalmente temos no modelo clássico de DAL. Nem preciso citar que o refactoring fica comprometido nesse caso.

Compatibilidade

O db4o tem versões para Java, .NET Framework 1.1, 2.0 e 3.5 (Compact Framework inclusive). A versão que eu utilizei para testes foi a 3.5 para Compact Framework. Logo, se procurar para uma versão mais leve que o SQLCe, o db4o é uma alternativa interessante.

Conclusões

Utilizar o db4o traz um ganho de legibilidade muito grande para o código, além de diminuir consideravelmente o número de linhas de código a serem implementados. Porém existem algumas questões que ainda não analisei e que devem ser levadas em consideração no caso de utilizarmos o db4o em um projeto mais sério e complexo: performance com grande volume de dados, questões de refatoração com uma base de dados já iniciada (o que aconteceria se retirarmos um parâmetro, por exemplo?).

Links