Posts Marcados .NET

Como eu Desenvolvo Software – Parte 4

Oi pessoal, espero que todos tenham tido um ótimo natal! Eu consegui descansar um pouco, aproveitando para passar um fim de semana agradabilíssimo com a família em Bonito – MS. Para quem não conhece, recomendo muito, a região é fantástica, com locais maravilhosos.

Retomando o post anterior, eu estava falando sobre a minha maneira de desenvolver software, com o objetivo de fornecer uma perspectiva possível para os que estão começando, ou uma outra visão para os que estejam tentando definir uma forma prática para trabalhar com desenvolvimento. No post anterior, eu apresentei um problema fictício e a decisão de construir um sistema web para resolvê-lo. Iniciei com a análise sobre as condições de contorno do sistema e toda a parte de infra-estrutura envolvida. Estávamos no ponto onde tínhamos o esqueleto definido, com a parte de segurança já estabelecida. Até este momento, estávamos vendo questões de caráter não-funcional do sistema. Num cenário real, estas questões podem ser muito mais complexas do que o meu exemplo, pois pode já haver algum tipo de restrição de desempenho ou de segurança, por exemplo, que exija uma infra-estrutura muito mais complexa, fazendo com que esta etapa se torne um projeto em si.

Mas assumindo que seja algo padrão, a próxima fase seria começar a trabalhar na parte funcional do sistema. Neste momento, o que eu faço é começar por uma tarefa que seja algo que possa dar valor imediato para o cliente. Se o objetivo do sistema é controlar despesas de viagem de funcionários, algo que poderia ter valor imediato seria disponibilizar a lista das últimas viagens realizadas, com a consulta de viagens individuais e o cadastro de nova viagem. Claro que fazer algo de grande valor pode fazer com que seja necessário existir previamente toda uma gama de telas de apoio. Por exemplo, se estamos controlando viagens, teríamos que ter o cadastro de funcionários, de destinos, de tipos de despesa contabilizadas etc. Neste ponto começa a surgir o conceito de “Domínio” do problema (ver DDD), onde os termos já passam a ter um significado específico (ubiquitous language) e são utilizados para uma melhor comunicação com o cliente. Esta comunicação é fundamental para o sucesso e a evolução posterior do sistema, portanto é de grande importância tentar definir os termos corretos e já empregá-los toda vez que houver alguma comunicação.

Cada entidade de apoio deve ter seu mecanismo de persistência definido. No meu caso, diferentemente do DDD, eu não utilizo agreggates para centralizar a persistência. Para mim, cada entidade de domínio vai ser traduzida diretamente em uma tabela do banco de dados – a única exceção são as entidades que são classificadas como enumerations (enums), estas ficam só em código. O que diferencia enums de entidades de apoio, para mim, é o significado para o sistema da inclusão de novos registros. Se um usuário puder incluir uma instância da entidade e o sistema não tiver que ter sua parte de negócio alterada por causa disto, ela é uma tabela; caso contrário, é uma enum. Por exemplo, um cliente pode cadastrar uma cidade de destino a qualquer momento e nenhuma regra de negócio precisa ser alterada. Agora, imaginemos que existam categorias de faturamento pré-definidas que vão definir graus de reembolso. Se cadastrar um novo tipo de categoria ou uma categoria inteira implicar na necessidade de alteração do sistema, ela é um enum. Claro que sempre pode-se tentar modelar um sistema onde haja pouca necessidade de modificações, com quase tudo parametrizado em entidades e praticamente nenhuma enum. Mas pode ser que isto não valha a pena em determinados cenários.

Uma vez que as entidades de apoio estejam definidas, vão ser criados os objetos de domínio que as irão representar. No meu caso, esta tarefa é feita na seguinte ordem: 1) Primeiro a entidade tem seu principais atributos identificados (mentalmente); 2) É criada a tabela no banco de dados que vai armazenar a entidade – eu tento manter a tabela com os nomes de colunas o mais próximo possível dos atributos do objeto; 2) Usamos o framework, para gerar a entidade no diagrama do ActiveWriter; 3) Usamos os geradores de código .TT do framework para efetivamente construir a entidade e o respectivo repositório (sim, cada entidade tem o seu) e factory. Este processo é muito rápido, é questão de segundos fazer uma entidade usando esta sequência. Como tudo é feito no framework, a partir da geração tudo está 100% funcionando, já que não há nenhuma intervenção manual; assim nem é necessário utilizar testes unitários para entidades. Alguns até argumentam que eles seriam úteis para facilitar na evolução no caso de modificações de entidades; porém na minha experiência, este tipo de coisa é tão rapidamente detectado e corrigido que não justifica o esforço de testes (nem mesmo o esforço de tentar automatizar a construção dos mesmos via framework, pelo menos não até o momento).

Sempre com o objetivo de gerar a tela de melhor valor, deve-se então identificar, das entidades de apoio, quais são as necessárias para cadastrar dados que irão ser utilizadas na tela. É importante detectar dados de apoio que são tão importantes que merecem ser tratados como uma tarefa própria. Neste nosso exemplo, existe uma grande chance de o cadastro de funcionário ser um caso destes, seja pelo volume de informações, seja pela necessidade de um maior número de regras de negócio que devem ser seguidas durante o cadastro. Para cada um destes casos identificados é criada uma nova tarefa, de prioridade maior e da qual a tela de melhor valor será dependente.

Para os outros casos, é criada uma tela de CRUD. Telas deste tipo são também feitas com suporte do framework; construí-las é simplesmente definir colunas de visualização e busca na lista de entidades, definir os campos que serão utilizados no formulário de inclusão/alteração e escrever eventuais regras de validação de inclusão/modificação. Um CRUD simples pode ser feito no framework em poucos minutos (o meu record é 8 minutos para uma entidade simples, considerando a criação da tabela, da entidade e das telas). E como todas as telas de CRUD também são construídas de forma padronizada, também não utilizamos testes unitários para este tipo de tela. A criação de CRUDs é um processo tão rápido que há uma tentação de se tentar fazer um sistema inteiro usando telas deste tipo. Isto dificilmente é viável. Primeiro porque telas que possuem formulários muito grandes podem ser confusas para o usuário. Segundo porque para fazer uma atividade simples, um usuário teria que navegar em muitas telas, o que também atrapalharia a usabilidade. Assim, eu prefiro somente utilizar CRUDs para as entidades de apoio mesmo.

Neste ínterim o cliente está sempre sendo atualizado. Ele é notificado e participa cada vez que uma nova tarefa é gerada. A cada CRUD liberado ele também é notificado. Em novos sistemas, os primeiros releases já podem conter várias telas de CRUD 100% operacionais. Isto contribui para que o cliente perceba o andamento do projeto e já possa atuar em eventuais falhas de comunicação ou entendimento.

Novamente este post já está muito grande. Vou parar por aqui, continuo na semana que vem. Acho que em mais 1 ou 2 posts eu consiga finalizar esta série. O assunto é muito extenso, cada um dos tópicos que eu mencionei poderia se tornar um post completo. Espero que esteja conseguindo ao menos dar uma visão geral do processo. Por favor, fiquem à vontade para perguntar ou sugerir.

Desejo a todos um feliz 2011, repleto de realizações e com muita saúde e sucesso!

, , , , ,

Deixe um comentário

Como eu Desenvolvo Software – Parte 3

Continuando o post anterior sobre desenvolvimento, estava falando sobre como um software surge a partir de uma necessidade. É importante notar que, apesar da necessidade orientar o desenvolvimento do um sistema, ele sempre vai existir em uma plataforma, sendo exemplos de plataformas comuns: Windows (consumindo serviços WCF ou acessando direto o banco), web (hospedado localmente ou em algum provedor e acessado por um browser) ou alguma plataforma móvel (android, iOS). A definição da plataforma normalmente implica em algum tipo de restrição ou característica especial no software sendo construído.

Existem ainda as diversas questões de infra-estrutura: a aplicação vai usar banco de dados (SGBD)? Qual? Onde será hospedado? Qual versão de .NET? MS-MVC ou Monorail? E assim por diante… Para simplificar, vou assumir a plataforma web MS-MVC (devo comentar algo sobre outras plataformas no decorrer dos artigos), utilizando a versão mais atual do .NET, utilizando um banco de dados típico (que no meu caso usualmente é o Microsoft SQL Server) e que será desenvolvida utilizando o nosso framework – aplicações que não fazem uso dele ficam tão mais caras que na maior parte das vezes são inviáveis.

Voltando ao exemplo do post anterior, imaginem uma planilha para, por exemplo, controlar viagens de funcionários de uma empresa. As viagens são controladas por funcionário e cada viagem possui informações como data, custo, meio de transporte, destino, despesas diversas e reembolsos. Por alguma razão (volume, por exemplo), alguém avaliou que um sistema poderia diminuir custos de gestão desta planilha, disseminar melhor a informação e facilitar a vida do usuário permitindo, por exemplo, que ele digite as informações diretamente por uma página da intranet da empresa.

Se fosse assumir um sistema deste, a primeira coisa seria entender qual o custo atual do cliente para operar as planilhas, o quanto ele espera gastar e obter com o sistema e avaliar se o que é esperado é factível dentro do que eu acho que seria gasto, baseado na minha experiência no desenvolvimento de aplicações com nosso framework. Teria que ver também se o cliente já está familiarizado com o formato ágil de desenvolvimento e com a nossa forma de trabalho. Caso positivo, teria que identificar a seguir aspectos sobre a infra, ver se já existe um SGBD disponível, onde o servidor web está localizado, se a equipe de infra tem experiência em instalar e manter a aplicação, se os servidores estão atualizados com as últimas versões do .NET etc… Se houver algum problema com algum destes pontos, eu tento resolvê-lo antes mesmo de iniciar o desenvolvimento, pois ele pode se tornar um impeditivo para todo o projeto.

A seguir, ainda na parte de infra, eu identifico como será feita a autenticação de usuários. Normalmente, para aplicações intranet, espera-se uma autenticação integrada com o Active Directory ou algum serviço similar. Para aplicações públicas da Internet, é necessário ver também as questões de segurança, como acesso seguro, certificados para HTTPS, firewalls etc… Muitos desenvolvedores acham que estas tarefas não cabem a nós; eu não consigo concordar com isto, pois falhas nestas áreas podem tornar o melhor software do mundo completamente inútil.

Finalmente, tem-se que decidir como fica o direito de propriedade e uso do software e se o cliente deseja exclusividade sobre algumas porções do software – caso positivo, isto também torna o desenvolvimento mais caro. Como se pode ver, é um grande esforço destinado somente à análise de custo e da infraestrutura, antes mesmo que qualquer hora seja empregada no software em si. Não adianta fazer um excelente software se ele não puder se pagar, ser instalado ou ser mantido adequadamente. Mas apesar da quantidade de tópicos, um checklist com todos estes itens pode ser preenchido em uma ou duas reuniões com as pessoas certas.

Após esta fase, eu posso iniciar a construção da infra-estrutura do sistema. Utilizando o framework, eu posso montar a parte básica da aplicação, que contém uma página default, o banco de dados vazio com a tabela de usuários do sistema, a tela de login com seus respectivos controllers e toda a parte de integração com um sistema de autenticação. Normalmente eu consigo colocar isto no ar em menos de 4h após o início do projeto, pois tenho estas estruturas padronizadas no framework de desenvolvimento. Os casos que demoram mais são os que exigem uma aparência específica do site feito por um designer que me obriga a recortar htmls para a geração dos layouts. Em casos extremos, isto pode levar alguns poucos dias. Esta etapa seria o que alguns chama de Iteração 0. Eu não uso um nome específico, simplesmente utilizo uma parte do esforço da primeira iteração. Como resultado, o cliente já tem o site rodando e logando, caindo na página inicial, com toda a parte de segurança funcionando, e, quando aplicável, com funcionalidades como troca de senhas, “esqueci minha senha” e login integrado.

Paralelamente a esta fase, é feita a primeira reunião de priorização de atividades com o cliente. Nesta reunião são identificadas as funcionalidades principais do sistema e feita a priorização das mesmas. Quando eu faço isto, eu tento já identificar o que pode trazer maior ROI ou maior impacto positivo para o cliente é já sugiro uma ordem de atividades. Quando mais experiência o cliente tem em desenvolvimento no formato ágil, mais ele consegue contribuir nesta etapa. Mas, de uma forma ou de outra, eu tento sempre conduzir para que ele utilize o esforço de desenvolvimento da maneira mais benéfica possível para atingir o objetivo do sistema. No caso da planilha, o que seria mais positivo? Colocar a página pública logo para que os funcionários pudessem já ver o novo sistema e começar a testar o lançamento de dados? Ou colocar a apuração de despesas disponível? Ou facilitar, por exemplo, uma exportação de dados da planilha? Estas decisões são feitas caso a caso, mas imaginemos que no nosso o mais benéfico fosse o lançamento de dados pelo funcionário.

Bom, o post está ficando muito longo, assim vou ter que continuar num próximo, provavelmente a ser publicado só depois do Natal. Até lá e aproveito para desejar a todos um Feliz Natal e Boas Festas!

, , , , ,

Deixe um comentário

Como eu Desenvolvo Software – Parte 2

Se da última vez que escrevi, eu achava que estava sobrecarregado, eu ainda não tinha idéia do que estava vindo! Estes últimos meses tem sido extremamente agitados na White Fox, estamos abrindo várias frentes, criando iniciativas onde um bom software possa ser um diferencial competitivo. Estamos tendo mais sucesso do que eu esperava inicialmente, este ano está sendo fechado com chave de ouro. Em breve, vou escrever sobre algumas destas inciativas aqui. Mas deixe-me continuar finalmente o post anterior, falando sobre desenvolvimento de software.

Como citei no post anterior, a ideia é tentar falar um pouco da maneira pela qual eu desenvolvo software, considerando as metodologias e correntes atualmente em voga. Para começar, acredito que o mais importante de todo o processo é entender que desenvolver software é entregar, dentro de um prazo e um custo acordado, algo que atenda uma necessidade de alguém de maneira satisfatória. Por mais óbvio que isto pareça, a maior parte dos desenvolvedores esquece estes objetivos primordiais. Acredito que isto aconteça porque, como desenvolvedores, tendemos a não lembrar que desenvolver software é uma atividade extremamente cara para quem compra. Assim, não cumprir um prazo pode significar uma perda de oportunidade que inviabiliza todo o investimento. E é claro que não cumprir custo pode fazer com que um investimento seja inviável em termos de resultado financeiro, determinando a falência do projeto. E mesmo atingindo ambos os objetivos, o software ainda tem que servir para melhorar algum processo ou trazer alguma receita, de forma que o investimento feito seja compensado. Claro que muitas vezes isto é difícil de ser avaliado, mas, por mais difícil que seja, isto é o que queremos obter quando desenvolvemos comercialmente. É importante ter isto em mente porque nossas decisões, como desenvolvedores ou arquitetos, vão justamente impactar estas características e elas só fazem sentido quando permitem que a construção do software seja financeiramente viável.

Entregar um software que funciona bem também parece ser algo óbvio, mas que na prática é justamente uma dos pontos mais subjetivos da nossa profissão. Por exemplo, na prática poderíamos construir praticamente qualquer sistema utilizando somente telas de CRUD (listar, editar, alterar, remover). Apesar de funcionar, um sistema assim muito provavelmente seria quase impossível de ser utilizado com eficiência pelos usuários – eu mesmo já tive a chance de conhecer vários sistemas feitos desta forma! No extremo oposto, o software pode ter poucas telas, extremamente complexas e que fazem “tudo”, o que podem pro um lado ser um pesadelo para quem usa ou, mesmo sendo fácil de usar, ser um pesadelo para manter e evoluir – novamente, já vi vários assim. Encontrar o ponto ideal de usabilidade versus produtividade é pra mim um dos maiores desafios do desenvolvimento. No entanto este é um assunto que eu raramente vejo em fóruns técnicos.

Um outro ponto importante dos conceitos gerais de desenvolvimento é a questão da manutenabilidade de software. Esperamos que o software que construímos seja usado por muito tempo, talvez até muitos anos. Assim, se um software for extremamente caro para manter (e manter aqui significa alterar e evoluir o mesmo conforme necessário para o negócio), ele também pode se tornar inviável para quem está comprando. Daí toda esta preocupação de desenvolvedores em fazer software utilizando uma estrutura que permita que ele seja entendido e alterado com facilidade por alguém no futuro. O problema é que isto é também é algo extremamente subjetivo, já que é difícil determinar qual o grau de estruturação que vai permitir atingir este custo ideal de manutenção. Em outras palavras, é muito complicado determinar o quanto a mais tem-se que gastar no desenvolvimento e calcular em quanto isto deixa a manutenção mais barata. Alguns acham, por exemplo, que somente software que faça uso da última moda em BDD, TDD, DDD e etc. teria um custo de manutenção adequado. Mas muitas vezes, o que se gasta para se incluir uma determinada metodologia ou técnica em um processo de construção torna o software tão caro pra fazer que a conta não fecha. Por outro lado temos exemplos de software com anos de uso, em FORTRAN ou CLIPPER, que estão em uso até hoje e que possuem ainda um custo de manutenção que não justifica uma eventual migração. Claro que cada caso é um caso, porém, na minha visão, este é um dos conceitos que mais geram dúvida e que mais é erroneamente empregado por desenvolvedores em geral.

Pra mim a manutenabilidade é super crítica, já que o software que eu construo tem a tendência de ser usado por muito tempo – tenho alguns que estão em uso há mais de 10 anos. Ao longo dos anos, surgem e desaparecem metodologias e técnicas, plataformas e linguagens são evoluídas. A minha solução para isto foi sempre criar um framework que abstraia os artefatos produzidos, na medida do possível, de toda esta mudança, sem perder as novidades. Pra mim, de longe, o mais caro no software é construir interfaces. Assim, desde os tempos do ASP eu trabalho com algum tipo de abstração de interface para tornar isto um pouco mais barato – ver posts sobre produtividade. E interessante que, apesar de todas as mudanças, estes frameworks têm alguns pontos comuns. Claro que hoje em dia, com MVC e jQuery, a facilidade para abstração é muito maior. Porém algumas áreas como visualização e separação lógica, permanecem de maneira muito similar.

Com relação à usabilidade, eu tento manter a criação de telas complexas no limite do que o framework permite. Claro que com a evolução do framework, hoje é possível fazer praticamente qualquer tela. Mas ainda assim, eu sempre prefiro utilizar telas que sejam mais simples de expressar no framework, desde que não atrapalhe muito a usabilidade. É claro que é sempre possível fazer algo super complexo, porém deixo claro para quem compra que a escolha por uma interface complexa vai gerar um maior custo pra fazer e um muito maior pra manter, o que usualmente é um argumento bastante convincente. E interessante de ser ter software usado por muito tempo é que ao longo dos anos, os indicadores mostram que a maior parte do custo de evolução se concentra justamente nas telas complexas, confirmando a teoria de que telas grandes são algo bastante difícil de se justificar, financeiramente.

Então, retomando o tópico principal, como desenvolvo software? Tudo começa com uma necessidade. Para efeito de ilustração, vamos assumir daqui por diante uma necessidade de um cliente que deseja, por exemplo, passar a controlar dados que estavam numa planilha. A partir daqui vou tentar imaginar um software que atenda a necessidade, que seja feito utilizando os artefatos do framework e que custe algo pra fazer que possa ser retornado em algum tempo. Neste exemplo, de uma planilha, o ganho do cliente seria a redução da perda é de tempo dos usuários (afinal manter planilhas grandes é algo complicado), a diminuição do risco de um erro de digitação (planilhas grandes são difíceis de operar) e no aumento da disponibilidade da informação proporcionada por um sistema web.

No próximo post, continuarei deste ponto mostrando como uma necessidade é transformada em software e quais os critérios que uso na construção dos artefatos e na implantação final do produto. Prometo não demorar pra escrever a continuação!

, , , , ,

1 comentário

Como eu Desenvolvo Software – Parte 1

Olá pessoal. Acabei ficando um longo tempo sem escrever, tenho estado anormalmente ocupado nestes últimos meses. Junho foi um mês muito complicado, com as paradas para a Copa do Mundo e a presença no Agile Brazil – isto sem falar em alguns projetos críticos sendo entregues e o tempo dedicado a novos projetos. Com isto, na White Fox, passamos o mês bem sobrecarregados e só agora em Julho é que começamos a voltar ao ritmo normal. Finalmente agora volto a ter um pouco de tempo para escrever alguns artigos!

O evento Agile Brazil foi muito bom. Contou com a presença de grandes nomes do desenvolvimento mundial, como o Martin Fowler e o Philippe Kruchten. E foi excelente pra fazer contato com o pessoal da comunidade .NET do Brasil. Mas este post é sobre algo que conversei bastante com outras pessoas lá e que gera sempre discussões “acaloradas” toda vez que é levantado em uma roda de desnvolvedores (ou em listas de desenvolvedores como a excelente dotnetarchitects): qual o melhor “jeito” de desenvolver software.

Com a aceitação cada vez maior das metodologias ágeis, a minha impressão era que a comunidade estaria mais tranquila sobre metodologias e técnicas para desenvolvimento. Elas sempre evoluem, claro, mas pelo menos em linha gerais, estaríamos em um caminho mais definido. Mas o que eu vejo é que ocorreu algo diferente, uma profusão de metodologias e técnicas e de pessoas que pregam que determinada linha é muito melhor do que qualquer outra. Acho que isto piorou ainda mais porque o modelo “waterfall”, hoje praticamente execrado pela maior parte dos desenvolvedores “modernos”, foi pintando como o maior vilão e origem de todos os males e que a metologia XXX ou YYY chegou para resolver isto!

Longe de mim defenter o uso de waterfall, na maior parte dos cenários. Mas, apesar de todos os problemas e falhas deste tipo de desenvolvimento, o fato é que até hoje temos muitos e muitos exemplos de sucesso,  e que continuam a acontecer ainda hoje. A palestra do Kruchetn foi muito boa neste aspecto, ele citou justamente esta “demonização” do waterfall como um sinal de que nem tudo está bem.

E para quem está começando, eu tenho notado, em geral, uma total confusão. Como as universidades estão alguns ciclos atrás (a maior parte delas ainda prega o waterfall como única e melhor maneira de desenvolver) e sem ter a experiência de ter passado por sucesso e falhas em determinada metologias, os iniciantes ficam sem saber que caminho seguir: Waterfall? nem pensar! Scrum? Como viver com todos os scrum-buts? XP? Como aplicar no meu dia a dia? Isto sem falar nas múltiplas variantes técnicas da construção em si, onde entra TDD, BDD, DDD etc., aliada a uma miríade de plataformas, frameworks, ORMs etc. E sem nem entrar no mérito de linguagens em si, onde temos novamente uma outra gama de escolhas como C#, C++, Java, Rubi, Python etc. E em todos estes, temos muitos “gurus” que afirmam que aquilo é o supra-sumo da agilidade e é a melhor invenção do mundo deste a pólvora, sendo a única maneira de fazer software bem. Realmente é difícil não ficar confuso. Felizmente temos algumas vozes de bom senso, como o Alistair Cockburn, no seu “Juramento de Não-Lealdade”, que talvez comecem a melhorar este cenário.

Na minha visão, escolher uma forma de desenvolver não deveria causar tanta dificuldade. É certo, desenvolver software é algo complicado, exige um misto de dedicação, talento, experiência e muito, muito estudo. Mas, em toda a minha experiência, o que eu tenho visto é que as pessoas que se eforçam para se tornar bons codificadores acabam o sendo de uma maneira geral. Ou seja, um bom programador em C# vai ser um bom programador em Python (claro que uma transição não é simples, pelo volume de novas informações, mas se isto for desejado ou necessário, vai acontecer). Ou seja, os princípios básicos, o cuidado e o gosto pelo que se faz, são fatores mais importantes no sucesso do que qualquer linguagem metodologia, tecnologia ou plataforma que exista. E pra mim, isto vai continuar sendo assim mesmo com o todo o nosso ritmo de evolução… (acho que só vai parar o dia que pudermos solicitar um programa verbalmente e o próprio computador  o gerar… ou seja, daqui a muito tempo!). Óbvio também que nada existe sem contexto, isto é especialmente válido para desenvolvimento de software. Assim, levando em consideração o contexto, algumas escolhas podem levar a uma maior produtividade do que outras.

Para tentar ajudar nestes dilemas, resolvi escrever este e mais um artigo sobre desenvolvimento, e como eu o vejo. Vou colocar alguns princípios que adoto, na medida do possível, de onde eles vieram, e como eu vejo a inserção de toda esta parafernália metodológica/técnica no meu processo de desenvolvimento. Meu objetivo é tentar defender a idéia mais importante dos princípios ágeis, de que pessoas são mais importantes do que qualquer processo, metodologia ou ferramenta, e que os principios básicos ainda são comuns a qualquer atividade de desenvolvimento. Vou também colocar um pouco do meu contexto, para justificar algumas escolhas que faço e como isto afeta a nossa produtividade.

Até a próxima!

, , , , , ,

3 Comentários

Instalando o TFS 2010

Esta semana, para aproveitar a mudança da sede da WhiteFox, resolvemos já também migrar o nosso Microsoft Team Foundation Server (TFS) de 2008 para 2010. Como toda instalação de TFS, estávamos bem preocupados, já que no 2008 uma instalação completa era sempre difícil de funcionar da primeira vez. No TFS 2010 não foi diferente, foram praticamente dois dias de trabalho de duas pessoas (dev e infra) para conseguir colocar tudo funcionando! Para que outros não tenham que passar por esta mesma sina, seguem abaixo algumas dicas e experiências que aprendemos na “marra”.

Primeiramente, resista à idéia de “vamos instalando” pra ver no que dá. Cada instalação de componentes do Windows 2008 e SQL Server deve ser feita de uma maneira específica ou então erros estranhíssimos acontecem. Tentamos desta forma inicialmente e obviamente, não funcionou. O TFS Installation Guide é, portanto, leitura obrigatória. Mas atenção que a versão que vem no DVD NÃO é a mais atualizada; é necessário baixar a mais atual da MSDN. O link para download está na primeira página do próprio Installation Guide. A versão mais atualizada é bem mais detalhada, com os passos mais específicos para cada item. Siga à risca! A única coisa que não seguimos foi com relação as permissões da conta TfsService. No Guide eles recomendam não dar privilégio administrativos; somente alguns específicos e “logon as a service”. Como esta parte era uma fonte enorme de dor de cabeça no TFS 2008, resolvemos ignorar e colocar esta conta como Domain Admin.

Um ponto que gerou uma dúvida seria se o TFS 2010 pode ser executado no Windows Server 2008 R2. Os pré-requisitos diziam que sim, mas eu encontrei alguns blogs afirmando que o suporte no R2 é possível, mas mais complicado. Acabou que confirmamos que de fato é possível, só não está claro no Installation Guide (a impressão que dá é que o Guide foi feito para o 2008, não para o 2008 R2) que o SQL Server 2008 não é compatível com o Windows 2008 R2, assim, imediatamente após a instalação do SQL Server, é necessário instalar o Service Pack 1.

Agora o ponto mais crítico que encontramos (e este nos fez perder quase 1 dia para descobrir o que era) é que, como toda empresa de desenvolvimento parceira Microsoft, nós utilizamos o SQL Server 2008 Developer Edition como banco de dados. Porém, a instalação desta versão tem uma característica única: ela por default não habilita o acesso TCP/IP. Sem este acesso, ocorre um erro na hora do installer do TFS criar o suporte ao ReportServer. E o erro é uma daquelas mensagens 0x8XXXX que não dizem absolutamente nada. Identificar este erro não foi simples, felizmente encontramos alguns blogs que acabaram nos apontando o caminho correto (isto depois de várias reinstalações frustradas). A correção, no entanto, é trivial: basta ativar o TCP/IP no SQL Server Configuration, imediatamente após a instalação do mesmo.

Com isto, tudo finalmente funcionou. O processo completo de instalação do Windows 2008 R2 + Sql Server 2008 + Sql Server 2008 SP1 + TFS demorou cerca de 2,5h no nosso hardware (que não é muito potente).

Após a instalação, nos deparamos com a nova feature do TFS 2010, que são as Project Collections. Ele cria uma por default na instalação, a “DefaultCollection”. No nosso caso, como temos vários clientes, resolvemos apagar esta e criar uma para cada cliente. Para apagar uma Project Collection, é necessário utilizar o “TfsConfig.exe”, que fica no diretório Tools de instalação do TFS 2010.

Ainda estamos analisando o uso das Project Collection. No Team Explorer, quando se abre uma Collection, ele fecha a anterior. Isto pode ser um problema para gente, já que temos projetos de infra-estrutura que são reutilizados entre os clientes. Mas iremos tentar esta abordagem e vamos ver como fica, já que ela aparentemente faz sentido.

Um último ponto diz respeito à segurança de acesso de collections criadas. No TFS Administrator, existe uma opção chamada “Group Membership”. Ali é possível dar permissão para outros usuários após a criação do mesmo. Porém, o problema do TFS 2008 permanece no TFS 2010, ou seja, dar permissão ali não significa acesso automático ao Sharepoint ou ReportServer. Assim, lembre-se sempre de dar permissões nestes dois itens após criar cada Collection. No ReportServer, isto pode ser feito na configuração raiz, vale para todas as Collections. No Sharepoint, é necessário fazer para cada site após a criação da respectiva Collection.

É isto pessoal, espero ter contribuído para tornar este processo um pouco menos penoso para outros. Por favor comentem caso tenham notado outros pontos ou se eu deixei alguma coisa relevante sem ser mencionada. Até a próxima.

, , ,

7 Comentários

Engine MVC baseada em XSLT

Na série sobre produtividade, no post sobre a camada de interface, eu  falei um pouco sobre o uso de XSLT na transformação de arquivos XML para gerar HTML. Várias pessoas me pediram mais detalhes sobre a engine utilizada. Neste artigo, vou entrar em mais detalhes desta engine usando o MS-MVC e fazer um paralelo com a implementação Monorail. Este artigo é bem técnico e pressupõe que o MS-MVC seja bem conhecido, em especial na parte de criação de custom view engines.

No download do MS-MVC existe uma View Engine baseada em XSLT. Existem também outras iniciativas como a Chris Hampson. Estas não nos atenderam porque se baseiam em um arquivo XML que é transformado diretamente pelo XSLT. No nosso caso é necessário injetar, além do arquivo XML de definição, dados que serão utilizados também pelo XSLT pra construir a tela. Finalmente, as atividades realizadas pelo controller também interferem na página resultante. Por exemplo, se uma ação gera um erro, a página resultante deve ser um redirecionamento para a página de tratamento de erros. Estas razões fizeram com que a gente desenvolvesse a nossa própria ViewEngine.

Em linhas gerais, a nossa ViewEngine simplesmente obtém o XML de definição, o XML de dados (que chamamos de DataIsland) e aplica um XSLT para gerar um HTML resultante (ver o post sobre camada de interface para exemplos destes artefatos). Para fazer isto no MS-MVC, é necessário implementar duas classes: uma que implemente a interface IViewEngine e uma que implemente a IView. Além destas duas, a implementação de nossa engine usa um controller base, para que possamos interceptar alguns métodos (ver adiante).

A IViewEngine tem que implementar os métodos FindView, ReleaseView e o FindPartialView. No nosso caso, a única coisa importante é o FindView. Este método é responsável por identificar o arquivo de definição XML e passar o tipo de ação e dados de apoio para a ViewBase, conforme código a seguir:

private ViewEngineResult FindView(ControllerContext controllerContext) {
            
            var server = controllerContext.HttpContext.Server;
            const string extension = "html";
            var area = string.Empty;

            if (controllerContext.RouteData.Values.ContainsKey("area")) 
                area = controllerContext.RouteData.Values["area"] + "/";

            var controller = (BaseController)controllerContext.Controller;
            var controllerName = 
                controller.GetType().Name.Replace("Controller", "")
                   .ToLowerInvariant();
            var path = string.Empty;
            if (controller.OutputType == OutputType.Html || 
                controller.OutputType == OutputType.XmlView) {
                if (controller.ViewNameOrigin == 
                        BaseController.ViewNameOriginType.Controller 
                    && string.IsNullOrEmpty(controller.ViewName))
                    path += string.Format("~/views/{0}{1}.{2}", area, 
                            controllerName, extension);
                else
                    path += string.Format("~/views/{0}{1}/{2}.{3}", area, 
                        controllerName, controller.ViewName ?? 
                        controller.ActionName, extension);
                if (!File.Exists(server.MapPath(path)))
                    return new ViewEngineResult(new[] { path });
                path = server.MapPath(path);
            }

            var dataIsland = 
                  (controller.OutputType != OutputType.XmlAction) ? 
                                controller.GetDataIsland() : null;
            var view = new XsltView(controller.OutputType, 
                controllerContext.RouteData.Values["area"].ToString(), 
                path, controllerName, controller.ActionName,  
                dataIsland == null ? null : dataIsland.Root, 
                controller.ElementsToRender, controller.Message, 
                controller.RedirectUrl);          
            return new ViewEngineResult(view, this);
        }

 

Como pode ser visto acima, temos dois casos de arquivos de definição. Alguns que tem o próprio nome do controller, e alguns que tem o nome da action, situados em um folder com o nome do controller. A última parte deste método chama o construtor da XsltView, que implementa a IView.

A XsltView é que contém o código principal da ViwEngine. Ela é responsável por efetivamente fazer a transformação XSLT e depois fazer escrever o HTML gerado. A transformação no nosso caso é um pouco mais complexa do que simplesmente rodar o Transform() de uma classe XsltCompiledTransform do C#. A gente faz também uma série de manipulações no XML de origem, fazendo a junção dele com o XML do DataIsland, alterando paths relativos (por exemplo, substituindo string ‘~/’ pelo path físico da aplicação) e suportando áreas que possuem XSLTs diferentes (por exemplo, temos áreas que simplesmente injetam HTML final; temos outras que usam pequenos templates XSLTs para cada controle; e outras que simplesmente fazem referência a um XSLT externo). Esta transformação poderia ser tema de um artigo por si só, quem tiver interesse em saber mais sobre ela, envie-me um email direto.

O método Render da XsltView é mostrado a seguir. Como mencionei acima, dependendo da ação executda nós podemos ter vários resultados possíveis. Temos HTML simples e, para chamadas que foram feitas por AJAX, temos opção de mandar mensagens de alerta, trechos de HTML para serem substituídos ou simples comandos de Redirect. Para suportar isto, fizemos uma mensagem XML que é decodificada por um arquivo .js que é responsável por tomar a decisão correta do que fazer com base nos elementos enviados.

public void Render(ViewContext viewContext, TextWriter writer) {
  XElement result;
  if (outputType == OutputType.Html) {
      var ns = GetNamespaces(contents.Document);
      if (!String.IsNullOrEmpty(redirectUrl)) {
       writer.Write("<html><script language='javascript'>location.href='" 
           + redirectUrl + "';</script></html>");
       return;
      }
      else {
       result = contents.XPathSelectElement(HtmlElementPath, ns);
       writer.Write(@"<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 
 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>");
      }
  }
  else {
      viewContext.HttpContext.Response.ContentType = "text/xml";
      result = new XElement("Result");                
      if (!string.IsNullOrEmpty(message))
        result.Add(new XElement("Message", message));       
        if (!String.IsNullOrEmpty(redirectUrl)) {
           result.Add(new XAttribute("status", 
                       hasView ? "redirect" : "ok"));
           result.Add(new XElement("Redirect", redirectUrl));
        }
        else {                    
           switch (outputType) {
             case OutputType.XmlAction:
                result.Add(new XAttribute("status",  "ok"));
                break;
             case OutputType.XmlMessage:
                result.Add(new XAttribute("status",  "message"));
                break;
             case OutputType.XmlDataOnly:
                result.Add(new XAttribute("status", "ok"));
                result.Add(contents);
                break;
             case OutputType.XmlView:
                result.Add(new XAttribute("status", "ok"));
                if (elementsToRender != null) {
                   foreach (var element in elementsToRender) {
                      var ns = GetNamespaces(contents.Document);
                      var idName = element;
                      if (element == "Form" || element == "List"
                            || element == "CRUDList") {
                         idName = element + "Contents";
                         result.Add(
                           new XAttribute("configurePage", "true"));
                      }
                      var dataContents =
                      contents.XPathSelectElement(
                      String.Format("//*[name()='div' and @id='{0}']",
                             idName), ns);
                      if (dataContents == null) continue;
                      var item = new XElement("Data");
                      item.Add(new XElement("Id", idName));
                      var html = new XElement("Contents");
                      html.Add(new XCData(dataContents.ToString()));
                      item.Add(html);
                      result.Add(item);
                   }
                }
                break;
          }
     }
   }
   writer.Write(result.ToString());
}

O último componente da engine é o BaseController. Este controller faz o override de dois métodos do MS-MVC, o primeiro é o OnActionExecuting e OnActionExecuted, mostrados a seguir.

protected override void OnActionExecuting(
                                     ActionExecutingContext context) {
    ActionName = 
             context.ActionDescriptor.ActionName.ToLowerInvariant();
    if (HasAttribute<XmlResultAttribute>(context.ActionDescriptor)) {
        OutputType = OutputType.XmlView;
        var viewName = GetViewName(context.ActionDescriptor);
        if (!string.IsNullOrEmpty(viewName)) ViewName = viewName;
    }
    else 
        if (HasAttribute<ActionResultAttribute>(
                                   context.ActionDescriptor)) {
            OutputType = OutputType.XmlAction;
        }
        else
            if (HasAttribute<DataOnlyResultAttribute>(
                                    context.ActionDescriptor))
                OutputType = OutputType.XmlDataOnly;
            else {
                if (HasAttribute <HtmlResultAttribute>(
                                       context.ActionDescriptor)) {
                    var viewName =
                             GetViewName(context.ActionDescriptor);
                    if (!string.IsNullOrEmpty(viewName)) 
                              ViewName = viewName;
                }
                OutputType = OutputType.Html;
            }
    base.OnActionExecuting(context);
}

protected override void OnActionExecuted(ActionExecutedContext context) {
    if (context.Exception != null && !context.ExceptionHandled) {
        if (OutputType != OutputType.Html) {
            ProcessException(context.Exception);
            context.ExceptionHandled = true;
        }
    }
    base.OnActionExecuted(context);
}

Como pode ser visto, antes da execução, através de atributos da action, é possível determinar qual o tipo de retorno desejado. E após a execução, caso tenha ocorrido alguma exceção, é feito um tratamento da mesma.

Acho que deu para ter uma idéia de como a nossa engine foi construída. No Monorail, tudo funciona exatamente da mesma forma, a única diferença são nomes diferentes para interfaces e métodos do controller. No Monorail ao invés da IViewEngine, temos que herdar da ViewEngineBase. Não há interface específica para a View pois quem faz o Render é a própria ViewEngineBase. E no controller, o método a sofrer override é o InvokeMethod. Se alguém desejar informações específicas sobre o Monorail ou sobre qualquer outra área desta engine, basta me mandar um email.

, , , , , ,

1 comentário

Log de Negócio

Esta semana tive uma necessidade de log específica em um de nossos sistemas, que acabou gerando uma excelente adição para o nosso framework de desenvolvimento. A necessidade era saber o que acontecia em produção que acabava gerando um bug raro de valores no final do dia, e que fomos incapazes de reproduzir mesmo com uma grande quantidade de testes unitários. Aparentemente o bug está associado a uma condição de multi-usuário que é muito complicada de reproduzir. Para resolver este bug precisaríamos saber a seqüência exata em que as operações foram realizadas, e isto só seria possível com log.

A necessidade de log de negócio acaba surgindo vez por outra em aplicações. E, praticamente para cada sistema que eu já participei, foi feita uma solução diferente para log, com vantagens e desvantagens. Alguns usam log em banco, colocando triggers nas tabelas e replicando as informações, o que é extremamente simples de fazer mas gera grandes problemas de contenção e espaço utilizado. Outros usam versões do log4net gerando dados em arquivos, o que também é relativamente simples de fazer porém gera também dificuldade em ambientes massivamente multi-usuário – sem falar na dificuldade de processar os arquivos depois. Já fiz também log em banco, com e sem log4net, com bons resultados, mas sempre com uma solução específica para cada caso, o que causava um bom esforço de manutenção.

Outro ponto que sempre me incomodou é a questão da intrusividade das funções de log na regra de negócio. Tirando a solução em trigger de banco, qualquer implementação de log4net foi sempre feita com chamadas específicas de log dentro da rotina de negócio. Isto polui a rotina e dificulta a manutenção. Eu sempre tentei criar algo tipo AOP, mas nunca consegui uma solução boa. Mesmo atualmente, as bibliotecas conhecidas de AOP (PostSharp, Spring.Net etc.) não são uma boa opção para o nosso ambiente por serem um framework completo, de grande impacto…. Usar qualquer uma delas só pra log de algumas regras de negócio pra mim seria usar um canhão pra matar uma mosca.

Assim, resolvemos desta vez fazer algo decente, já incorporado ao framework, e que usasse uma abordagem AOP na medida do possível. Graças à tecnologia do Castle Windsor IoC usada atualmente no framework, isto foi relativamente simples de ser feito. Definimos na nossa arquitetura que o log de uma regra deveria ser expresso através da observação de parâmetros e retornos da função, antes e depois dela ser executada. Através do uso destas condições pré e pós, é possível identificar exatamente como aquela função afetou os dados.

Para implementar, o que fizemos foi usar o log4net para salvar eventos em banco. Até aí, solução padrão. O passo seguinte foi definir atributos AOP para marcar uma regra de negócio como sendo logada. O ideal teria sido definir as condições pré e pós no próprio atributo, porém o C# atualmente não suporta nada que não seja estático nos atributos; e usar strings eu acho uma prática ruim. A solução usada foi definir um tipo para o atributo que apontasse para uma classe que definiria quais são todas as condições, utilizando lambda.

O resultado ficou excelente. Abaixo está um exemplo do atributo na regra de negócio:

[Log(typeof(AtualizarValorDePosicaoLog))]
public void AtualizarValorAnterioDePosicao(Contrato contrato, 
                                    Posicao posicao, decimal valor) {
 

E no tipo de definição das condições, fica assim:

public class AtualizarValorDePosicaoLog : LoggerBase {

  public AtualizarValorDePosicaoLog() : base(
    Properties.PreCondition("valor"),
    Properties.PreCondition<Contrato>(
      c => c.Id, c => c.Tipo, c => c.TipoBacen, 
      c => c.Moeda, c => c.Posto, c => c.ValorMoedaEstrangeira, 
      c => c.ValorMoedaNacional, c => c.ValorUSD, c => c.TipoPagamento),
    Properties.PreCondition<Posicao>(
      p => p.Id, p => p.ValorPosicaoAnterior, p => p.Conformidade, 
      p => p.Baixa, p => p.Transferencias, 
      p => p.TotalCompra, p => p.TotalVenda, p => p.Moeda, p => p.Posto, 
      p => p.Tipo),

    Properties.PostCondition<Posicao>(
      p => p.Id, p => p.ValorPosicaoAnterior, p => p.Conformidade, 
      p => p.Baixa, p => p.Transferencias, 
      p => p.TotalCompra, p => p.TotalVenda, p => p.Moeda, p => p.Posto, 
      p => p.Tipo)) { }
    }
 

Na execução, o interceptor da classe de serviço busca o atributo, instancia o tipo de definição e coleta os dados, antes e depois da rotina executar. Para minimizar o impacto em tempo de execução, o logger simplesmente armazena um dicionário com todas as condições recolhidas, serializando num XML. Posteriormente, de maneira assíncrona e com baixa prioridade, um executor analisa os registros de log gerados e expande cada condição em tabelas do tipo dicionário.

Com isto o impacto ficou bem pequeno e conseguimos fazer algo quase puramente AOP e sem nenhuma definição em string! Finalmente conseguimos uma implementação boa para log, que deve nos atender por muito tempo.

Nesta solução, alem da equipe da WhiteFox, tivemos a colaboração do arquiteto Fernando Bichara, da Perlink.

, , , , ,

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

Produtividade – Conclusão

Oi pessoal, este é um último post da série sobre produtividade. Nos anteriores, eu descrevi a infra-estrutura de desenvolvimento que é usada pela minha equipe atualmente. Toda esta infra-estrutura nada mais é do que uma ferramenta utilizada por nós para se realizar a construção de sofware. Nossos clientes, na grande maioria dos casos, não se preocupam se o software é feito em C# ou em Clipper, ele quer saber se o que ele precisa é barato pra fazer e manter e se é simples e fácil de usar.

Assim, eu vejo esta infra-estrutura toda muito com esta visão pragmáticas, de ser algo que deve permite entregar ou modificar rapidamente uma funcionalidade de negócio, sendo produtivo na hora de atender uma necessidade. Isto faz a diferença para mantermos o nosso cliente satisfeito, com retorno que ele julgue satisfatório para o que ele gasta para se construir software.

Neste sentido, estou bastante satisfeito com a nossa infra-estrutura. Ela é rápida para se iniciar algo do zero, muito rápida para se fazer coisas triviais (como CRUDs) e tão ou menos complexa que outras alternativas para se fazer coisas complicadas. Acho que isto o máximo que podemos esperar de qualquer framework. O meu pragmatismo é aplicado todo o tempo e qualquer alteração no framework (que é sempre caro pra fazer) se justifica somente se ela vai trazer mais produtividade em algum aspecto. Usando o framework atual, estamos conseguindo colocar no ar uma aplicação nova, com controle de acesso (vou detalhar mais isto em posts futuros), estrutura básica e layouts iniciais em poucas horas. Fazer um CRUD de uma entidade leva menos de 15 minutos, considerando do tempo de criação da tabela ao final da tela!

Claro que a produtividade final não depende somente da infra-estrutura. Na minha experiência, 90% do tempo de construção (ou manutenção) de qualquer aplicativo grande fica restrito a umas poucas telas de maior complexidade. Assim, é importante saber trabalhar com o cliente no sentido de minimizar a existência deste tipo de interface. Neste sentido, acho que fazer software não é só entender a necessidade de negócio, é também saber o que é simples ou não de fazer e propor soluções funcionais que sejam mais fáceis de desenvolver. Desta forma, a maneira como se desenvolve acaba também influenciando a necessidade, em uma via de duas mãos. Daí a importância de quem está entendendo a necessidade conhecer muito bem como aquilo vai ser feito, sob pena de se orçar muito mal ou se gastar muito para fazer, com prejuízos para o cliente ou para a própria empresa que desenvolve. Este é um conceito que parece que muitas “analistas” não compreendem, e a causa da falha ou prejuízo de muitos projetos.

Ainda tenho muitas melhorias a serem feitas neste framework. Ainda existem tipos de telas que podem ser melhor suportados, novas funcionalidades que podem ser integradas e alguns serviços que estão sendo refatorados a todo tempo. Porém acho que ele já está maduro o suficiente pra ser empregado em qualquer sistema. Existem áreas para as quais eu ainda não tenho uma solução boa. A geração de relatórios é uma delas. Gerar relatórios na web em geral é algo complexo, pois é difícil montar os layouts e nós sempre encontramos com uma série de limitações de desempenho e banda causadas pelo volume transitado. Não gosto de nenhuma das alternativas existentes atualmente e ainda estou buscando uma que me permita fazer gerar uma DSL e que tenha um comportamento preditível. Informo aqui quando encontrar!

Espero ter contribuído para quem teve a paciência de ler até aqui. Como citei no início, esta infra-estrutura é um exemplo que é difícil de ser replicado para outras equipes ou de suportar uma escalabilidade maior, já que ele exige um treinamento e conhecimentos muito específicos. Mas acredito que os princípios sejam gerais, e possam ser aplicados a outros cenários. Nos próximos meses eu vou adicionar adendos a esta série toda vez que houver uma evolução ou fato novo referente ao framework ou relativo à produtividade em si. Até a próxima.

, , , , , ,

Deixe um comentário

Produtividade – Camada de Interface Parte 1

Em mais um post sobre a série sobre produtividade, vou escrever sobre a camada de interface. Por interface eu me refiro à web, já que é raro nós termos grandes desenvolvimentos de aplicativos Windows – e mesmo nestes casos não é algo problemático, o desenvolvimento em Windows Forms do .NET é bastante tranqüilo para aplicações de pequeno porte. Intrefaces baseadas em serviços também são super simples para serem construídas, ainda mais usando a WCF Factory. Assim, o foco é a construção de páginas web. Para não tornar este post muito longo, eu o dividi em duas partes.

Na minha experiência, o desenvolvimento de interfaces web é onde se gasta mais de 90% do tempo de desenvolvimento e manutenção de qualquer sistema de médio ou grande porte. É também onde a evolução tecnológica se faz mais sentir, a disseminação de uso do AJAX, por exemplo, mudou completamente o modo como se desenvolve para web. E para os usuários, a qualidade da interface e navegabilidade muitas vezes é um dos maiores fatores de sucesso ou fracasso de um sistema, fazendo com que o investimento nestes artefatos seja primordial.

Para ilustrar o que busco, vou começar descrevendo o que pra mim é o pior cenário possível para interfaces. Tenho certeza que quem desenvolve para web já viu algo assim em algum momento da carreira. Todo sistema começa bem e o ASP.NET WebForms ainda é o método mais comum de construção utilizado. Assim, no início da vida do sistema, as telas são feitas de maneira mais ou menos rápida. Porém com o tempo, os usuários pedem evoluções para suportar algo pouco usual, um ou outro desenvolvedor menos experiente acaba colocando sua “contribuição” para as telas e o que antes era simples se torna extremamente complexo. Depois de alguns anos (ou meses, dependendo do sistema), o que temos é uma coleção de aberrações, cada tela com código específico, com coisas indo pra sessão outras para viewstate, código inline (com o famigerado <% %>), regras de negócio em .cs de páginas (ou ainda, pesadelo dos pesadelos, no mais famigerado <% %>). Se o sistema foi portado do ASP então, a visão dantesca se completa, pois nestes caos é difícil encontrar qualquer coisa minimamente estruturada. O uso de componentes de terceiros ou frameworks só complica, pois também tendem a gerar ainda mais telas fora do padrão, com todos os tipos de “puxadinhos” imagináveis. Ou seja, em casos como este é muito mais fácil refazer a aplicação do que tentar arrumar. Porém quando se resolve reconstruir, o ciclo se repete e daqui a alguns anos (ou meses!) está tudo como era – normalmente deixando mais um legado para se manter, para a infelicidade de quem ficou.

Como resolver isto? Como desenvolver rapidamente e ainda assim gerar sistemas que não se autodestruam com a manutenção/evolução? A solução que eu uso são frameworks de interface que tentam garantir a estruturação das interfaces de uma maneira que facilite que os desenvolvedores fiquem em um padrão pré-estabelecido e ao mesmo tempo tentando gerar o máximo de flexibilidade possível. Nesta linha, eu tenho sistemas desenvolvidos há quase oito anos que continuam bem estruturados. Naquela época a amarração era tão forte que as telas produzidas eram muito simples, gerando sistemas de usabilidade ruim, comparando com os atuais. Porém existem vários operando até hoje que são simples de manter e (até hoje) de evoluir. De lá pra cá a evolução do framework já permite gerar telas extremamente amigáveis, mantendo ainda os níveis de manutenabilidade originais. Claro que esta não é uma solução para qualquer empresa ou time, o nível de treinamento e envolvimento com o framework é muito alto para ser facilmente replicável. Mas no nosso caso está sendo muito bem sucedido.

O framework utiliza um tipo de padrão MVC, porém com uma separação clara em 4 componentes: 1) Estruturas de Interface – aqui entram caixas de texto, checkbox, botões, listas etc. São itens que contém os dados sendo apresentados e que serão manipulados pelos usuários. 2) Layout – que é como as estruturas de interface são mostradas em tela. No layout entra toda a parte visual como estilos, manipulação visual (via jQuery por exemplo), controles, figuras e até coisas como uso de janelas popup ou chamadas AJAX. 3) Dados – utilizando um conceito que também utilizado no Microsoft Sharepoint, definido em uma ilha de dados XML que contém todas as informações que serão utilizados pela interface. 4) Controllers – controladores da interface, onde os dados são construídos e as ações executadas, fazendo uso das entidades de domínio e chamando os serviços da mesma.

Separando desta forma, fica fácil notar quais elementos são afetados quando há uma mudança, fazendo com que a mesma impacte o menos possível os demais. Esta estruturação faz com que o desenvolvimento de novas telas seja concentrado em 2 pontos, na definição de estrutura de interface e nos controllers. A clara separação existente evita que uma área afete outra.

Na minha experiência, o Layout é um dos pontos que mais evoluem na interface. Embora em linha gerais ele fique estático (p. ex., pode se definir que o sistema vai ter um cabeçalho com o nome do sistema, usuário logado e menu; no meio com uma área de formulários e listas e com inclusões em popup, tudo usando Ajax), as pequenas modificações acabam sendo freqüentes. É a inclusão de um novo elemento no cabeçalho, o suporte de uma lista hierárquica, um novo tipo de componente e assim por diante. Para garantir que não aja uma mistura de responsabilidades, eu uso XSLT para a definição de todos os artefatos de layout. Este é um dos pontos de maior dificuldade de aprendizado no framework, já que não é usual que desenvolvedores usem diariamente XSLT. Mas, como falei no post sobre princípios, o XSLT é uma linguagem declarativa que evita o uso de código imperativo e o uso de artefatos como sessão, viewstate etc. E uma vez dominado, o XSLT é extremamente eficiente para se gerar o HTML final (sem falar que já gera o HTML correto!).

Para gerar o HTML final, os arquivos XSLT de layout utilizam duas fontes de transformação: os dados e a estrutura de interface. Isto já faz com que os dados tenham que ser facilmente descritos em XML. Eu vou a um ponto mais extremo, fazendo com que todos os nossos dados sejam descritos somente em XML – também como é feito no Microsoft Sharepoint. Com isto, minimizamos o uso de objetos DTO e tornamos a estrutura de dados da interface completamente maleável e fácil de evoluir.

Finalmente, a estrutura de interface também deve ser descrita em XML, para que seja facilmente transformável. O que fizemos foi criar uma DSL, integrada ao Visual Studio (com intellisense, claro), para descrever cada elemento de interface. Vejam como fica um exemplo simples de um formulário de login:

Exemplo de DSL de Interface:

<Form defaultFocus="Login">
     <Field data="Login" type="textbox" required="true" label="Login"  />
     <Field data="Senha" type="textbox" required="true" label="Senha"  password="true" />
     <Command label=”Logar” action=”Login”/>
</Form>

Exemplo de XSLT de Layout (super reduzido para efeito de ilustração):

<xsl:stylesheet version="1.0" >
     <xsl:template match="Form">
          <html>
                <body>
                     <form method="post">
                          <xsl:apply-templates select=”Field|Command” mode=”Control”/>
                     </form>
                 </body>
           </html>
      </xsl:template>
</xsl:stylesheet>

No XSLT acima, cada Field é transformado em um input box e o Command em um button. Percebam que não há código inline – não existe o <% %>. Este tipo de arquitetura era muito dificil de ser construída e mantida no ASP.NET Webforms, o que tornava o framework bastante complexo. Felizmente com o advento de bibliotecas MVC, isto ficou muito mais simples. Inicialmente, criamos  uma View Engine para suportar esta DSL no Castle Monorail. Com o advento posterior do ASP.NET MVC, criamos uma a View Engine para ele que funciona com exatamente a mesma DSL e os mesmos arquivos de layout. Esta migração mostrou a capacidade de evolução do framework, o que me deixa tranquilo para encarar a manutenção dos nossos sistemas por, quem sabe, outros 8 anos por vir.

No próximo post vou explicar como as ações são executadas e entrar nos detalhes dos controllers, mostrando como isto tudo se integra.

Até breve!

, , , , , , , , ,

1 comentário