Welcome to MSDN Blogs Sign in | Join | Help

Manifesto SOA

 

Para quem não viu ainda, está no ar um manifesto SOA bem interessante que defende a prática consciente de SOA.

Vou reproduzir aqui abaixo, mas você pode ler e assinar diretamente em http://www.soa-manifesto.org/aboutmanifesto.html.

Gostaria de ouvir a opinião de vocês....

Aqui vai:

SOA Manifesto

Service orientation is a paradigm that frames what you do. Service-oriented architecture (SOA) is a type of architecture that results from applying service orientation.

We have been applying service orientation to help organizations consistently deliver sustainable business value, with increased agility and cost effectiveness, in line with changing business needs.

Through our work we have come to prioritize:

Business value over technical strategy

Strategic goals over project-specific benefits

Intrinsic interoperability over custom integration

Shared services over specific-purpose implementations

Flexibility over optimization

Evolutionary refinement over pursuit of initial perfection

That is, while we value the items on the right, we value the items on the left more.

 

Guiding Principles

We follow these principles:

Respect the social and power structure of the organization.

Recognize that SOA ultimately demands change on many levels.

The scope of SOA adoption can vary. Keep efforts manageable and within meaningful boundaries.

Products and standards alone will neither give you SOA nor apply the service-oriented paradigm for you.

SOA can be realized through a variety of technologies and standards.

Establish a uniform set of enterprise standards and policies based on industry, de facto, and community standards.

Pursue uniformity on the outside while allowing diversity on the inside.

Identify services through collaboration with business and technology stakeholders.

Maximize service usage by considering the current and future scope of utilization.

Verify that services satisfy business requirements and goals.

Evolve services and their organization in response to real use.

Separate the different aspects of a system that change at different rates.

Reduce implicit dependencies and publish all external dependencies to increase robustness and reduce the impact of change.

At every level of abstraction, organize each service around a cohesive and manageable unit of functionality.

Abraços

Posted by Otavio Pecego Coelho | 0 Comments
Filed under:

Private Class ResumoPDC

1 semana de férias + 1 semana de PDC + rede lenta no hotel = -posts

Então não devo ter notícias novas para vocês. Waldemir, Condé, Giovanni e Rogério falaram boa parte das novidades. Vou ficar com o que ficou de mais importante na minha memória:

1) , é claro, a entrada do Azure no ar para valer em fevereiro;

2) Tudo que tem de novo nesta última versão do Azure:

a) A API de monitoração (http://code.msdn.microsoft.com/windowsazuremmc ) onde você pode agora pegar o Log do IIS, eventos do Windows, iniciar/remover instâncias de roles do Azure, enfim, tudo para você começar a identificar quando aumentar ou diminuir o número de processos no Azure (elasticidade);

b) Agora você define novos tipos de workers. Você cria endpoints e pode usar o WCF para a comunicação entre eles, o que implica em migração mais fácil para quem tem aplicativos multicamadas usando o WCF;

c) Existe agora a noção de update domains: conjunto de workers que serão atualizados em conjunto. Com isto, podemos atualizar um update domain e testá-lo antes de atualizar os demais, garantindo uma instalação mais suave e controlável. Além disto, podemos pedir a atualização de um tipo de worker – nada de ter que baixar/levantar todo o conjunto de workers por causa de um oatch simples;

d) O PinPoint: um Market Place para mostrar seus produtos que rodam no Azure para seus futuros clientes (ver http://pinpoint.microsoft.com/en-US/ );

e) O Windows Azure Content Delivery Network (CDN), isto é, um cache global distribuido em 18 países que você pode usar para colocar imagens usadas no seu site do Azure (veja http://blog.smarx.com/posts/using-the-new-windows-azure-cdn-with-a-custom-domain);

3) Tudo que está para vir no Azure

a) O SQL Azure Data Sync: para sincronizar um ou mais bancos com o SQL Azure (mesmo o SQL Compact) (ver em http://www.microsoft.com/windowsazure/developers/sqlazure/datasync/);

b) O Projeto Dallas: serviços de dados da Nasa e outros que você pode incorporar no seu aplicativo (ver em http://www.microsoft.com/windowsazure/developers/dallas/);

c) A promessa de novos tamanhos limites para o SQL Azure;

4) A palestra do Erik Meijer sobre o Reactive Extension (http://blogs.msdn.com/somasegar/archive/2009/11/18/reactive-extensions-for-net-rx.aspx)

san francisco 321

4) O Silverlight 4 chegando e fazendo drag and drop entre aplicativos no Windows e o aplicativo Silverlight no Browser (http://silverlight.net/ );

5) O RIA Services, levando o Silverlight para tratar aplicativos multicamadas e usando agora o WCF para levar queries Linq do cliente para o servidor, e dados em ambas as direções (ver http://blogs.msdn.com/brada/archive/2009/11/19/pdc09-talk-building-amazing-business-applications-with-silverlight-4-ria-services-and-visual-studio-2010.aspx);

6) O Container do Azure que estava a mostra:

 

Tem muito material para ser visto.

Vale uma olhada nos vídeos do PDC em http://microsoftpdc.com/Videos (os do Azure estão listados em http://blogs.msdn.com/jnak/archive/2009/11/19/videos-of-the-windows-azure-sessions-at-pdc09.aspx )

Os treinamentos no http://channel9.msdn.com/learn/courses/Azure/

...e , claro, as últimas Ferramentas e SDK em http://www.microsoft.com/windowsazure/tools/

 

Abraços

Posted by Otavio Pecego Coelho | 0 Comments
Filed under: ,

Faça seu Gerador de Código com o T4 do Visual Studio

Tenho visto poucos arquitetos utilizando uma tecnologia interessante para gerar código de acordo com templates e que já está embutida no Visual Studio, sejam 2005, 2008 ou 2010.

Trata-se do T4 (Text Template Transformation Toolkit) Code Generation. Ele está contido no Visual Studio SDK e pode ser usado para gerar um código de acordo com um template que você ou seu time escreve.

Pense nele como a metade de uma infraestrutura para um gerador de código.

Um gerador completo costuma ter duas partes: a primeira é a responsável pela leitura dos metadados de uma especificação ou modelo; a segunda é a que lê estes metadados e gera algum artefato (código, html, etc.) a partir destes.

O Visual Studio SDK já vem com o Domain Specific Language Tools onde você pode especificar uma DSL visual (o Carlos Hulot falou muito disto no seu blog) e utilizar dentro dos templates T4 um conjunto de classes que permitem ler estes metadados para gerar o código correspondente.

Mas o DSL do SDK é um pouco complexo e você pode precisar de algo bem mais simples. Saiba que você pode construir seu próprio parser ou mero formulário para dar a entrada dos metadados que o template T4 usará para gerar seu código. Por exemplo, você pode fazer uma biblioteca para ler os metadados de um banco de dados e colocá-los a disposição para o template T4 percorrê-los gerando, por exemplo, classes com seus campos - como o Entity Framework faz.

Como exemplo, veja a figura abaixo.

image

Nela, você pode ver código que será rodado pelo template para gerar código (ele está sempre entre <# #> ). Em especial, o código no bloco que começa com <#+ é uma função de ajuda que retirar espaços de dentro de strings e é utilizado no bloco logo abaixo para gerar strings do nome de cidades sem espaços. Textos fora dos blocos <# #> são copiados diretamente para o arquivo de saída.

Quando abrimos o arquivo gerado (genclass.cc) deste exemplo encontramos o seguinte texto:

Cidades sem " " entre nomes
NewYork
London
Seattle
SanFrancisco
NewDelhi

Simples, não é?

Existe um post ótimo do Scott Hanselmann que dá vários links interessantes sobre o assunto.

Fica a dica: se você está querendo trabalhar com MDD e pensa em fazer seu próprio gerador, dê uma olhada no T4!

Abraços

(Dica 2: pense no Oslo como o parser e o T4 como gerador)

Quando usar o Azure?

Existem dois tipos de respostas aqui: uma que visa o diferencial do aplicativo do ponto de vista Controle X Custo, já abordado em http://msdn.microsoft.com/pt-br/azure/dd638038.aspx; outra que visa a adequação tecnológica no uso do Azure . Hoje vou tratar da segunda.

Em minha opinião, nem todo tipo de arquitetura de aplicativo é susceptível de ir para o Azure, seja por impossibilidade técnica atual, seja por custo.

Para ficar mais claro qual tipo de arquitetura eu creio ser viável no Azure, criei o seguinte quadro:

image

 

Para tornar mais claro, vamos tratar de cada caso:

1) Web 2.0 (Privado/Público): imagine cenários que precisam compartilhar um volume muito grande de dados não transacionais, podendo ter que escalar eventualmente para milhares ou mesmo milhões de usuários. Pense em Facebook, Youtube, sites de blogs, sites de notícias, etc. Neste caso você vai poder usar o Azure em todo seu potencial, utilizando a elasticidade de processamento e storage (Azure Tables), armazenando tudo num volume virtualmente infinito de recursos. Estes é um dos cenários mais favoráveis para o Azure!

image

2) SaaS (Privado/Público): imagine fazer um aplicativo para ser vendido para milhares ou mesmo milhões de clientes, cada um utilizando seus dados de forma exclusiva, tendo um máximo de 10G de banco SQL (limitação atual do SQL Azure), mas podendo guardar em Blobs e Tables do Azure um volume virtualmente infinito de documentos, fotos, vídeos, etc – o que diminui consideravelmente a limitação de volume máximo do banco. Imagine poder crescer/decrescer o número de clientes de acordo com a sazonalidade sem custo de compras ou ociosidade de recursos. Este é também um cenário para o Azure.

 image

3) High Performance Computing: imagine um aplicativo que precise de um alto grau de processamento paralelo, para criar animações de filmes, analisar proteínas, construir plataformas de petróleo, etc, utilisando algoritmos como MapReduce. Se não houver impeditivo na carga/descarga de dados a serem trabalhados (devido à latência ou volume), o Azure pode ser utilizado para a infraestrutura deste aplicativo, com a vantagem de você só alocar os servidores quando realmente necessitar rodar o processamento. Por que só bom? Ainda não existe framework MapReduce pronto, você terá que fazer o seu. Outra questão é o eventual volume de dados para ser carregado para uma análise.

image

4) Small/Medium Transaction Processing: se você tem um aplicativo para empresas pequenas e/ou médias, ou mesmo aplicativos departamentais, provavelmente você poderá usar o SQL Azure de 1G ou 10G, migrando o seu aplicativo para o Azure. Lembre-se que fotos, documentos e outros podem migrar do Banco para Tables e Blobs, pois isto diminui o risco do limite do SQL ser atingido. Por que só bom? O limite atual de 10 Gb pode ser um impeditivo.

image

5) Remote SQL: você pode utilizar o SQL remotamente, ganhando latência (o que é ruim), mas conseguindo tanto um custo pequeno quanto a possibilidade de compartilhar os dados com outras empresas, sites, etc. Porque não deixar a lista de produtos e preços da sua empresa disponível para terceiros? (leitura somente, é claro!). Por que só bom? A latência da Internet pode ser um impeditivo.

image

6) Internet Service Bus: se você precisa da comunicação com aplicativos externos à sua rede, eventualmente de terceiros, o Internet Service Bus é uma infraestrutura interessante. Ele permite a troca de mensagens com autenticação federada, diminuindo o custo de infraestrutura e gerenciamento de um EDI. Por que só bom? Latência, de novo.

image

7) Mistos: imagine uma loja online, onde pedidos são feitos pelo site no Azure (com lista de produtos no SQL Azure e fotos dos produtos nos blobs) e em que toda a infraestrutura de pagamento e entrega está na sua empresa, ou na empresa de terceiros. Neste caso, você poderá complementar sua loja online, que habita no Azure, com os demais sistemas de logística, meios de pagamentos, etc, utilizando o Internet Service Bus como mecanismo de troca de mensagens confiável. Este e muitos outros cenários podem utilizar o melhor de cada tipo de ambiente (nuvem, dispositivos, sistemas internos ou de terceiros), realizando uma verdadeira arquitetura Software+Serviços. Por que só bom? Latência e limite de SQL Azure…

image

Por fim, dois cenários, ao meu ver, não parecem ser convenientes para o Azure atualmente:

1) eXtreme Transaction Processing: que necessitam de Base de Dados muito grandes e que não pode ser resolvido com particionamento (vide post).

image

2) Sites HTMLs puros: neste caso, você até pode colocar o site no Azure, mas como você não estará compartilhando o IIS com outros usuários, o custo, potencialmente, será desvantajoso com o que praticado por hosters do mercado.

image

 

Esta é a minha análise dentro da situação atual. Você concorda?

Abraços

Padrões para Cloud Computing no Azure – Particionamento com o SQL Azure (Sharding)

O interessante da computação em sistemas elásticos, como o Azure, é que eles tornam factíveis padrões computacionais que costumam ser caros para as empresas.

É claro que podemos utilizar o Azure para migrar sistemas convencionais de nossas empresas (como sistemas departamentais ou algum outro sistema LOB), tentando minimizar o custo total da nossa TI. Mas tudo fica mais divertido – e potencialmente lucrativo - quando pensamos nas oportunidades para modelos computacionais que lidam com uma massa grande de dados ou de computação. E, para compreender isto, vale descrever alguns padrões recorrentes nestes tipos de problemas.

Imagine milhões de usuários visitando o seu site. Que padrões você pode utilizar para viabilizar tamanho número de acessos?

Imagine um cálculo que pode levar milhões de MIPS. Tenho padrões similares?

A resposta a estas perguntas costuma ser um grupo de padrões recorrentes. São eles:

1) Replicação: que se caracteriza pelo uso de uma estrutura repetitiva de computação e/ou armazenamento – o que proporciona a elasticidade pelo simples aumento ou diminuição desta “célula de computação”.

Neste caso temos dois sub-padrões comuns:

a. Sites convencionais compostos de WebFarms + storage: onde repetimos o mesmo código para cada servidor Web (web role, em bom azurês) e colocamos os mesmos dados de leitura copiados (em memória cachê ou cópia em storages (Azure Tables, em bom azurês));

b. Sites SaaS: onde repetimos a estrutura de cada célula (servidores+storage) onde, porém, tanto a estrutura de storage quanto a de código podem ser ligeiramente diferentes para cada usuário/inquilino, devido a customizações e/ou dados exclusivos;

2) Particionamento: que se caracteriza pela subdivisão da computação ou armazenamento com fins de distribuir as demandas para subsistemas diferentes, aliviando subsistemas e evitando potenciais gargalos.

Os sub-padrões seriam:

a. Grid: sites que trabalham com algoritmos de alto paralelismo, com a distribuição de subconjuntos (normalmente distintos) de dados para cada servidor, como acontece no MapReduce (ver post) ou processamentos em pipeline;

b. Sites que possam ser particionados em subsistemas distintos, como Vendas, Cobrança e Logística numa loja virtual, onde cada subsistema tem código e dados distintos, referentes à cada passo do processo de compra e entrega de um pedido, e cada sub-sistema se comunica com o outro por redirecionamento, compartilhamento de dados e/ou envio de mensagens (filas, em azurês);

c. Sharding: O particionamento de dados em storages diferentes, visando distribuir estatisticamente o acesso aos dados e, assim, diminuir o tempo de espera médio para uma escrita ou leitura. Tables do Azure, por exemplo, têm este comportamento por natureza/construção;

3) Misto: o mais comum, onde padrões de replicação e particionamento são utilizados em conjunto formando arquiteturas complexas.

O SQL Azure, em particular, nasce um pouco limitado devido à sua condição ACID (ver post). Porém isto não impede de você utilizar algumas destas estratégias visando arquiteturas para milhões de usuários. As estratégias possíveis são:

a) SaaS: um banco de dados para cada inquilino ou pequeno grupo de inquilino (ver 1.b acima);

b) Subsistemas distintos: um banco de dados para cada subsistema (ver 2.b acima);

c) Particionamento: um banco de dados para cada subconjunto de dados (ver 2.c acima);

Como o SQL Azure, ao contrário do MS SQL não tem infra-estrutura interna para particionamento, caberá a você construir uma :(

A boa nova é que o Windows Azure Platform Training Kit de outubro traz um lab com um código exemplo que implementa o particionamento com o SQL Azure usando o sharding.

Aos estudiosos, vale a pena baixar o Kit e ir direto para a Demo “Scaling Out SQL Azure with Database Sharding” e dar uma olhada.

Abraços

Arquitetura de Software – Boas Referências

Há muito venho recebendo pedidos de referências para o estudo e formação em arquitetura de software e soluções. Como este é um campo muito novo (cerca de 20 anos) poucos são os livros e artigos que consolidam as práticas mais atuais de uma forma estruturada, com linguagem simples, direta e com conteúdo atualizado.

A boa notícia é que tenho duas referências que creio serão muito úteis para todos nós.

A primeira referência é o livro Software Architecture: Foundations, Theory, and Practice. Este talvez seja um dos melhores livros que já li sobre o tema. Tem uma excelente cobertura do assunto, abordando práticas reais e mais ágeis de fazer arquitetura. Ele aborda temas atuais, como Domain Specific Software Architectures, tratando de assuntos que vão do básico “o que é arquitetura de software?“ até considerações de design, modelagem, análise, implementação e implantação. Um excelente livro para iniciantes. Um livro para os profissionais revisitarem seus conhecimentos que, dado a novidade do campo, foram muitas vezes adquiridos de forma empírica através de acertos e erros em projetos reais.

51hsJUa7JxL__BO2,204,203,200_PIsitb-sticker-arrow-click,TopRight,35,-76_AA240_SH20_OU01_

A minha segunda referência é o e-book do Waldemir Cambiucci, nosso colega de blog de arquitetura aqui do time de arquitetura da Microsoft Brasil. Ele consolidou e reestruturou um conjunto grande de posts do seu blog em um e-book que pode ser usado como uma referência atual das abordagens de arquitetura de software utilizando as tecnologias da Microsoft. Waldemir é um incansável estudioso e escritor de posts, e aproveita cada projeto, cada preparação, para também divulgar o que está sendo experimentado e aprendido.

image1

Boa leitura!

Linguagens Funcionais, Internet Service Bus, SQL Azure e Windows Indentity Foundation

Com o feriado chegando sempre vale algumas recomendações (referentes a arquitetura de software) para as horas vagas.

1) Que tal melhorar seu conhecimento no uso de linguagens funcionais e alguns padrões de código úteis para construir melhores bibliotecas, frameworks, etc. Minhas sugestões são:

a. O artigo Functional Programming for Everyday .Net Development. Este artigo vai mostrar alguns padrões e princípios interessantes como essência/cerimônia, Princípio da Segregação de Interfaces e outros, ilustrando tudo com lambda expressions em C#. São conhecimentos e truques que podem ser úteis na sua próxima biblioteca;

b. Motivado com o uso de expressões funcionais? Por que não continuar com as duas primeiras aulas do Erik Meijer (um dos autores do Linq) sobre os fundamentos da programação funcional? Você encontra aqui a primeira aula, e aqui a segunda. Imperdível!

2) Que tal colocar seu conhecimento de ISB em dia lendo uma série de 3 artigos do Juval Loy sobre Azure Services? Este talvez seja o futuro do EDI... será? Você pode achá-los em 1, 2 e 3;

3) Se você já se cadastrou no SQL Azure no sql.azure.com, talvez valha apena baixar o SQL Azure Explorer http://sqlazureexplorer.codeplex.com/ para o Visual Studio 2010. Ele vai te dar uma experiência interessante para administrar tabelas, views e stored procedures no SQL Azure;

4) Por que não compreender melhor como você pode criar e testar um aplicativo no Azure que aceita identidades de um identity provider externo (por exemplo, o ActiveDirectory da sua empresa ou de uma empresa parceira) usando o Windows Identity Foundation (WIF)? Neste caso, recomendo a leitura do WIF e Autenticação Federada no Windows Azure Web Role.

…e, claro, reserve um tempo para passear e descançar.

Bom feriado

Otavio

Preços do Azure e simulação por Monte Carlo

Tenho recebido perguntas de como calcular o custo do Azure para compará-lo com o custo de outras alternativas.

Já existem algumas calculadoras disponíveis para isto, mas, infelizmente todas as que eu vi fazem contas simples, que poderiam ser feitas com o Excel com pouco esforço, e que não levam em conta a variação probabilística de alguns fatores que influenciam no preço final.

Quando temos variáveis como GB de storage utilizados no mês, trafego de mensagens, número de transações, etc., melhor do que fazer uma conta com um número estimado (nosso “chute”) é utilizar uma estimativa estatística baseada em algum comportamento padrão – e aqui entram as distribuições normais, uniformes, binárias, etc.

Vamos a um exemplo. Distribuições normais são interessantes para modelar distribuições que tendem a se aglomerar perto de uma média – o que acontece, a meu ver, com todas as variáveis componentes do preço final do uso do Azure à exceção do custo de uso de CPU e SQL.

Outro fator interessante de uma distribuição normal é que podemos simular seu comportamento empiricamente se soubermos dois dados que nos dão os limites do intervalo de confiança em que temos 90% de confiança (ver figura).

normal

E aqui um ponto importante: “chutar” um intervalo de confiança é mais fácil e preciso (!) do que “chutar” o valor esperado. Por exemplo, no Excel, bastaria utilizarmos a fórmula

=norminv(rand(),média,desvioPadrão)

onde

média = (Limite 90% do Intervalo de Certeza + Limite 10% do Intervalo de Certeza)/2

desvioPadrão = (Limite 90% do Intervalo de Certeza - Limite 10% do Intervalo de Certeza)/3,29

Levar em conta valores probabilísticos nos leva a um resultado probabilístico também. Isto é, ao invés de obtermos um único número obtemos uma curva probabilística que nos conta nossa variação com o risco. Por exemplo, podemos perceber que temos 10% de chance de pagar um valor de 800 quando o valor médio estaria em 700 e, ao final, saber estas chances pode nos levar a um processo de decisão mais realista – numa comparação entre preços ofertados no mercado, será factível compreender melhor quando ou com que probabilidade uma composição de preço é melhor que outra.

Como isto é um instrumento útil não só para cálculo de preços, mas para planejamento de capacidades e outros, vale a pena mostrar o como calcular levando em conta as curvas de probabilidades.

A maneira mais comum e genérica que temos hoje para calcular uma expressão que é constituída da composição de fórmulas probabilísticas é o método de Monte Carlo. O método de Monte Carlo é um método de simulação - isto é, são gerados vários números que obedecem as curvas de probabilidade de cada fórmula estatística da expressão. Por exemplo: se temos o custo em GB de storage e o custo de transferência de GB dados, podemos pensar que a expressão do custo dos dois é dado por P_usoGb()*custoPorGB + P_transfGB()*custoTransfPorGB. Como cada função P é de fato uma curva probabilística, podemos simular milhares de resultados ao gerar números (que obedeçam a curva de distribuição de cada função P_) para P_usoGb() e P_transfGB() e calculando milhares de resultados para esta expressão.

Com milhares de resultados, podemos agora calcular intervalos e suas probabilidades contando quantos dados simulados ficaram abaixo de um determinado valor. Outra maneira de lidar com isto é ordenar o array de resultados e calcular, por exemplo, 10 intervalos a partir dos valores mínimos e máximos da amostra. Sabendo os intervalos, podemos contar o total de valores dentro de cada intervalo e, com isto, temos a probabilidade para cada intervalo.

Um Excel ou um programa ajuda no cálculo. Como gosto de programas, vou contar como fiz minha versão 0.01 da minha calculadora do Azure.

Passo 1: Interface – Usei WPF pensando em usar gráficos no futuro.

image

Passo 2: Funções de geração de distribuição aleatórias para serem usadas na simulação – Coloquei em uma biblioteca 3 tipos de distribuição, mas uso apenas a distribuição normal aqui (de um algorítmo achado na internet). Aqui vai o código:

public class MathHelp
{
    static Random r = new Random();

    // Gera número randômico segundo a distribuição normal
    // decimal upper90 => valor superior do Intervalo de 90% de Confiança
    // decimal low90 => valor inferior do Intervalo de 90% de Confiança
    public static double GenByNormal(double upper90, double low90)
    {
        double norminv = 0;

        double probability = r.NextDouble();
        double mean = (upper90 + low90)/2;
        double sigma = (upper90 - low90)/ 3.29; // standard deviation

        double x = 0;
        double p = 0;
        double t = 0;
        double q = 0;

        const double c0 = 2.515517, c1 = 0.802853, c2 = 0.010328;
        const double d1 = 1.432788, d2 = 0.189269, d3 = 0.001308;

        q = probability;
        if (q == 0.5) norminv = mean;
        else { 
            q = 1 - q;
            if ((q >= 0) && (q < 0.5)) p = q;
            else {
                if (q == 1.0) p = 1 - 0.9999999; // JPR - attempt to fix divide by zero below, what is NormInv(1,x,y)?
                else p = 1 - q;
            }

            t = Math.Sqrt(Math.Log(1 / (p * p)));

            x = t - (c0 + c1 * t + c2 * (t * t)) / (1 + d1 * t + d2 * (t * t) + d3 * (t * t * t));

            if (q > 0.5) x = -1 * x;

            norminv = (x * sigma) + mean;
        }

        return norminv;
    }


    // Gera número randômico segundo a distribuição uniforme
    // decimal upper => valor superior do Intervalo 
    // decimal low => valor inferior do Intervalo 
    public static double GenByUniform(double upper, double low)
    {
        return r.NextDouble() * (upper - low) + low;
    }

    // Gera número randômico segundo a distribuição binária
    // decimal oneProb => probabilidade de dar 1
    public static double GenByBin(double oneProb)
    {
        return ((r.NextDouble()> oneProb)?1:0);
    }

}

Passo3: Criação dos arrays com valores aleatórios – Note que minha amostragem é de 1000 simulações para cada variável. Também vale a pena lembrar dos custos do Azure aqui ou aqui.

// Cria array de 1000 posições para cada par Upp-Lower
double[] cpuRequest = GenNormalArray(1000, 0.15, convertDoubleToString(txReqUpp.Text), convertDoubleToString(txReqLow.Text));
double[] kTrans = GenNormalArray(1000, 0.001, convertDoubleToString(txKTransacoesUpp90.Text), convertDoubleToString(txKTransacoesLow10.Text));
double[] gbUsados = GenNormalArray(1000, 0.15, convertDoubleToString(txGBUsadosUpp.Text), convertDoubleToString(txGBUsadosLow.Text));
double[] storageRead = GenNormalArray(1000, 0.15, convertDoubleToString(txLeituraUpp.Text), convertDoubleToString(txLeituraLow.Text));
double[] storageWrite = GenNormalArray(1000, 0.15, convertDoubleToString(txEscritaUpp.Text), convertDoubleToString(txEscritaLow.Text));
double[] sqlRead = GenNormalArray(1000, 0.15, convertDoubleToString(txSQLLeituraUpp.Text), convertDoubleToString(txSQLLeituraLow.Text));
double[] sqlWrite = GenNormalArray(1000, 0.15, convertDoubleToString(txSQLEscritaUpp.Text), convertDoubleToString(txSQLEscritaUpp.Text));

onde

private double convertDoubleToString(string s)
{
    if ( s==String.Empty || s == "" ) s = "0";
    double result = 0;
    try
    {
        result = Convert.ToDouble(s);
    }
    catch (Exception err)
    {
        throw new Exception("Erro de digitação: " + err.Message);
    }
    return result;
}

private double[] GenNormalArray(int size, double value, double upper90, double low90)
{
    double[] arr = new double[size];
    for (int i = 0; i < size; i++)
    {
        arr[i] = MathHelp.GenByNormal(upper90, low90) * value;
    }
    return arr;
}

Passo 4: Cálculo do custo total para cada linha

double[] total = new double[1000];
double custoCPUs = convertDoubleToString(txNumCPUs.Text) * 24 * 30.5 * 0.12;
double custoSQL = convertDoubleToString(txNumSQL10GB.Text) * 99.99 + convertDoubleToString(txNumSQL1GB.Text) * 9.99;

for (int i = 0; i < 1000; i++)
{
    // calcula custo Azure
    total[i] = custoCPUs + cpuRequest[i] + gbUsados[i] + storageRead[i] + storageWrite[i] + kTrans[i];
    if (custoSQL != 0)
    {
        total[i] += custoSQL + sqlRead[i] + sqlWrite[i];
    }
}

Passo 5: Criação dos intervalos – Neste código deixei um valor fixo de 10 intervalos.

// Sort nos resultados para poder achar os intervalos
Array.Sort(total);

// cria 10 intervalos de acordo com o s valores mínimos e máximos encontrados
double interval = (total[999] - total[0]) / 10;
int[] counters = new int[10]; // array para contar número de resultados a cada intervalo

int index = 0;
counters[index] = 0;
double toCompare = total[0] + interval; // valor de teste
// inicia a contagem
for (int j = 0; j < 1000; j++)
{
    if (total[j] >= toCompare)
    {
        index++;
        toCompare += interval;
    }
    if (index < 10) counters[index]++;
    else break;
}

Passo 6: Mostra dos resultados – Nesta versão, só um MessageBox

string s = "";
for (int i = 0; i < 10; i++)
{
    s += "\n Maior Igual a " + Convert.ToString(total[0] + interval * i) + " val " + counters[i];
}

MessageBox.Show(s);

image

Note, neste exemplo, que temos uma variação de custo em dólares de ~290 a ~385, com valor mais provável no intervalo entre 322 a 354 (com ~78,5% de chance).

É claro que é uma versão draft com muitos defeitos. Por exemplo, o tratamento de erros é fraco e um gráfico final seria bom para melhorar sua venda. Mas a intenção é só mostrar um método de avaliação quando temos a composição de valores probabilísticos. Qualquer erro, por favor, e-mail para mim.

O importante é que este método de cálculo pode ser usado com o Azure, com preços de terceiros ou mesmo com o cálculo dos custos da sua TI. Lembre-se, de no custo total, colocar preços como eletricidade, custo de pessoal, etc.

Abraços

Posted by Otavio Pecego Coelho | 0 Comments
Filed under: ,

Revisitando o Agile

As metodologias de desenvolvimento ágeis são baseadas em conceitos simples: rapidez para modificar, entregas menores e contínuas, alinhamento constante com o usuário, etc. Muitos falam de seu uso e suas virtudes, mas nem sempre é muito bem explicada a filosofia de otimização de ROI (retorno de investimento) também existente nelas.

A melhor explicação que tive (e recomendo a leitura) veio do livro “agile management for software engineering applying the theory of constraints for business results”. Nele o autor nos relembra que a produção de software pode ser vista como um processo em etapas para a produção como, por exemplo, análise, desenvolvimento e teste. Cada etapa recebe insumos e produz seus produtos/artefatos: a análise recebe pedidos de features e entrega especificações para o desenvolvimento, este entrega código para o teste que, por fim, entrega softwares para a produção.

SemRealimentacao

O que o autor nos chama a atenção é que esta esteira de processos retilínea não é realista. Cada processo tem suas perdas e estas perdas podem voltar para as pilhas dos processos anteriores de um modo que denominamos de realimentação negativa. O alarmante é que esta realimentação negativa pode causar, no limite, uma parada total da produção, já que podemos encher cada pilha de insumos com os retornos de falhas, consumindo toda a produção nestes acertos… sem mais produzir nada de novo. Já encontrei esta roda viva em projetos reais - não é agradável.

Realimentacao

Um ponto interessante é que estes artefatos são feitos tanto de diagramas e documentos quanto de conceitos e, por isto, deterioram com o tempo. Por exemplo, o código errado, quando volta para o desenvolvedor, deve ser re-estudado. O tempo de re-estudo, por sua vez, é muito maior do que se ele tivesse sido revisto e acertado enquanto o desenvolvedor estava com todos seus conceitos frescos na sua mente. O mesmo acontece com o processo de design.

Uma última conclusão simples: um erro no design tem custo potencialmente maior do que um erro no desenvolvimento. O motivo é simples: ele gasta à toa as pilhas de desenvolvimento e teste com linhas de código e testes errados.

A teoria é boa, mas ela garante o bom resultado das metodologias ágeis? Parece que não.

Um pequeno artigo, que dá uma foto resumida do que temos agora, pode ser encontrado na revista IEEE Software que você pode encontrar em http://www2.computer.org/cms/Computer.org/ComputingNow/homepage/2009/0909/rW_SW_WhatDoWeKnow.pdf. Ele mostra que ainda temos muito a pesquisar e melhorar tanto neste tipo de processo quanto nos processos não ágeis. O artigo também nos dá em separado uma lista de estudos empíricos sobre o Desenvolvimento Ágil (veja em http://www2.computer.org/cms/Computer.org/ComputingNow/homepage/2009/0909/rW_SW_WhatDoWeKnow2.pdf ) .

Pode ser útil.

Abraços

Posted by Otavio Pecego Coelho | 2 Comments
Filed under:

Capacidades, Arquitetura e SOA

Uma das armas mais importantes usadas pelos arquitetos de TI ao longo dos últimos tempos tem sido a noção de capacidade.

Por definição, capacidade é a habilidade de executar uma determinada ação, e compreender de antemão que capacidades um sistema ou uma infraestrutura deve ter é função do arquiteto. Com esta análise em mãos ele pode trabalhar em subsistemas ou serviços que poderão fornecer estas habilidades respeitando a separação de propósitos (SoC - separation of concerns).

Vamos aos exemplos: poder enviar uma mensagem; autorizar e/ou autenticar um usuário; acessar dados; chamar serviços; executar fórmulas; etc.

Em cada um destes exemplos podemos conceber sistemas como o Exchange ou o Active Directory, bibliotecas como o Entity Framework, executáveis como um interpretador, e assim por diante.

Pensar em capacidades é elevar a abstração a um ponto relativamente estável. Ela indica o que queremos fazer e não o como fazer. Continuando o exemplo acima: podemos pensar em um dia substituir o Exchange por um processo smtp ou um Exchange na Nuvem – quem sabe? No entanto, ainda demorará a chegar o dia em que o conceito de envio de e-mail não existirá mais.

Outro benefício é que capacidades têm grande possibilidade de virarem Serviços. Exatamente porque a SoC foi alcançada, poderemos pensar em utilizar serviços remotos ou locais (com patterns de IoC ou uso de Interfaces), estruturando melhor a arquitetura e aumentando, provavelmente, o reuso.

O grande inimigo desta análise das capacidades é identificar os subsistemas ou serviços e pular direto para a sua implementação sem antes garantir outros atributos importantes para o(s) sistema(s). Exemplo: desempenho. Alguns serviços podem estar em uma localidade distante, criando uma latência inaceitável para quem o chama. Outro: escalabilidade. Alguns serviços funcionam bem em regime de baixa demanda, mas podem tornar-se sofríveis com o aumento da demanda.

Ambos os exemplos não são problemas da noção da capacidade, mas da adequação de uma implementação desta capacidade num contexto real.

Em resumo: bons arquitetos pensam abstratamente para organizar o espaço da implementação de suas soluções e infraestruturas. Pensar em capacidades é comprovadamente uma boa heurística.

Abraços

MapReduce no Windows Azure

Hoje vou descrever a vocês um pouco do que é o famoso MapReduce e como podemos simulá-lo no Azure.

Formalmente, o framework foi apresentado em um artigo de 2004 na OSDI por Jeff Dean e Sanjay Ghemawat, ambos da Google, e é hoje usado por vários sites (Facebook, Yahoo, e muitos outros).

O MapReduce é um framework para processamento em paralelo baseado em um meta-algoritmo. Meta-algoritmos são padrões de resolução de problemas, como os conhecidos divisão-e-conquista, backtracking e outros.

No caso do MapReduce a idéia foi a de implementar um padrão simples e bem conhecido que, por sua vez, já era realizado há muito no Lisp e no C++ (neste, com a API do MPI (Message Passing Interface) – usada em computação em grid, como no Windows HPC 2008).

A idéia é simples: se quisermos aumentar o paralelismo de uma operação, um processo Mestre deve dividir a tarefa em diversas partições, uma para cada processo Trabalhador. Chamamos este passo de Map.

Em seguida, cada processo retorna seu resultado parcial para o processo Mestre, que aglutina e retorna todas as respostas a quam requisitou o processamento. Chamamos este passo de Reduce.

Nada como um bom exemplo (neste caso, retirado do livro The Art of Concurrency): Imagine o processo de contar quantas letras ‘e’ existem numa frase. Ao passar para o computador mestre a frase “The quick brown fox jumps over the lazy dog.” ele deverá criar e enviar à cada Trabalhador um conjunto de pares com uma parte da frase e a letra a ser procurada (uma ordem para computação). Veja a figura abaixo.

 image

Feito isto, cada Trabalhador faz sua procura em paralelo retornando seus resultados. Ao Mestre cabe juntar os resultados parciais e calcular o resultado final.

image

Pronto! Simples, elegante, podendo alcançar um altíssimo grau de paralelismo.

Realizar isto no Azure é simples também. Mostrei no TechEd um pouco do código exemplo que consegui com o Simon Guest e vale a pena falar dele aqui também.

O exemplo trata de achar todos os números primos dentro de uma série (range) de inteiros.

Para isto são utilizados vários processos: um processo com o papel Web e vários com o papel Worker.

image

Para a comunicação entre estes, são criadas duas filas e duas tabelas.

A primeira tabela guarda as ordens de serviço de acordo com o nó Trabalhador. Caberá a cada trabalhador fazer uma query para pegar a sua ordem e processá-la.

A segunda tabela é utilizada para que os nós Trabalhadores coloquem seus resultados. Caberá ao processo mestre fazer a query para consolidar os resultados processados pelos Workers.

Quanto às filas, elas são usadas para dar a cada nodo um número (node assignment), dar a ordem de início do processamento em paralelo aos Trabalhadores e, por fim, avisar ao Mestre que o processamento terminou.

Deixo aqui como exercício para vocês implementarem este processo. É simples e é um bom exercício.

Para facilitar, aqui vai uma função que indica se um número é primo ou não.

public bool IsPrime(int number)
{
    if (number == 0||number==1)
        return false;
    else
    {
        for (int i = 2; i * i <= number; i++)
        {
            if (number % i == 0)
            {
                return false;
            }
        }
        return true;
    }
}

Bom trabalho e abraços

Divagando sobre modismos e computação

Lembro-me sempre de uma história real relatada num livro do Carl Sagan sobre como a população em volta de um lago no Japão gerou o desenho de um samurai nas costas dos sapos que habitavam este lago. Segundo o livro, uma lenda de reencarnação de samurais como sapos fez com que a população não se alimentasse daqueles sapos cujas listas nas costas lembrassem os desenhos de um rosto de um samurai. Com o tempo, os sapos que sobraram eram justamente os que tinham estes traços e estes, por sua vez, levaram esta característica genética para seus filhos. Lembro-me até hoje da foto de um sapo em que algumas linhas na sua costa realizavam uma reprodução perfeita de um desenho japonês de um samurai.

Os produtos e software de informática parecem sofrer deste mesmo processo. Ao longo do tempo vamos acreditando em certas qualidades, copiando e “melhorando” processos ou linhas de produto de tal forma que, ao final, todos se comportam de uma forma semelhante.

Hoje temos a Arquitetura Orientada a Objetos, a Web 2.0, a computação na nuvem (ou em nuvem), a orientação a objetos, etc. Alguns chamam isto de paradigmas. Gosto de chamar de teleologias: lógicas que criamos par explicar ou determinar o propósito final de um design, natural ou não.

Querem um exemplo clássico de teleologia? A idéia de que as espécies visam a sua perpetuação. Está é uma possibilidade, mas existem outras. Por exemplo, podemos pensar que as espécies vão sofrendo modificações aleatórias e que acasos, como mudança de clima, etc., façam com que os animais com algumas características particulares sobrevivam enquanto os outros não. Assim, a espécie não se modificou visando a sua perpetuação, mas se perpetuou porque tinha casualmente a modificação correta para o novo contexto.

Arquitetos são mestres evangelizadores de teleologias. Eles criam as regras que definem as boas qualidades de um projeto de software e tentam fazer com que os projetos sigam estas regras. É um poder e tanto – para o bem ou para o mal!

Outro dia, em conversa com MVP’s da Microsoft, contei uma estória antiga sobre teleologias que podem ser úteis neste contexto. Aqui vai...

“No Egito Antigo, havia uma crença de que um único homem era Deus. Por causa desta idéia, milhares de homens/mulheres dedicaram a vida para construir as pirâmides fantásticas que temos até hoje.

Na Idade Média e Renascença, havia a crença de um Deus criador a que todos nós devíamos dedicação e obediência. Por causa desta idéia, a humanidade criou igrejas de arquiteturas magníficas mais todo um conjunto de artes, como a pintura, a escultura, etc., que nesta época floresceram. Temos estas igrejas como monumentos de visitação até hoje.

Na idade moderna um novo deus surgiu, chamado razão. E se há algum objeto que incorpore melhor a razão, este é o computador. Isto explica porque milhares de homens/mulheres se dedicam a construir monumentos de milhões de linhas como os Sistemas Operacionais e ERP’s, que a cada 5 anos jogamos fora para construir um mais moderno.”

Bom feriado!

Posted by Otavio Pecego Coelho | 0 Comments
Filed under:

Promessa do TechEd – código do exemplo do Oslo

Prometi na palestra sobre MDD e Oslo neste TechEd colocar neste blog o código do exemplo de como usar uma gramática Mgrammar no .Net. Pois aqui vai....

A gramática que mostrei na demo é simples. Ela lê textos da forma “X tem Y anos.”. Um exemplo seria:

“Otavio tem 50 anos.

Paula tem 29 anos.”

Para isto, preciso declarar a gramática de reconhecimento em uma variável da forma:

static private string contactGrammar = @"
            module lingContato {
                language Contatos {
                    syntax Main = c:Contato * => Contatos { valuesof(c) };
                    syntax Contato =
                         n:Nome ""tem"" i:Idade ""anos."" => { Nome = n, Idade=i };
                    token Nome = ('A'.. 'Z' | 'a'..'z')+;
                    token Idade = '0'..'9'+;
                    interleave espaco = ' ' | '\r' | '\n' | '\r\n';
                }
            }";

O passo 2 é compilar a gramática e criar o “parser”:

CompilationResults resultsContatos = Compiler.Compile(
    new CompilerOptions
    {
        Sources = {
                    new TextItem {
                        Reader = new StringReader(contactGrammar),
                        ContentType = TextItemType.MGrammar
                    }
                }
    }
);

DynamicParser parserContatos = resultsContatos.ParserFactories["lingContato.Contatos"].Create();

parserContatos.GraphBuilder = new NodeGraphBuilder();

O passo 3 é fazer o “parsing”do texto de entrada (na variável inText), gerando um grafo que representa a AST (Abstract Syntax Tree).

Node nContato = (Node)parserContatos.Parse(new StringTextStream(inText), null);

Por fim, estamos prontos para navegar o texto e preencher uma lista do tipo Contato:

Contato c = new Contato();

foreach (Node record in tree.ViewAllNodes() )
{
    foreach (var member in record.Edges )
    {
        string s = member.Label + ": " + member.Node;

        if (member.Label == "Idade")
        {
            c.Idade = member.Node.ToString();
            lc.Add(c);
            c = new Contato();
            saida += s + " \r\n";
        }
        else
        {
            c.Nome = member.Node.ToString();
            saida += s + " ";
        }

    }
}

onde Contato é dado pela classe:

public class Contato {

     public string Nome { get; set; }

     public string Idade { get; set; }

}

Você vai precisar destas declarações de namespaces do Oslo (no diretório bin da instalação do SDK dele):

using Microsoft.M;
using Microsoft.M.Parser;
using Microsoft.M.Grammar;
using System.Dataflow;

Aqui abaixo vocês podem ver como fica esta exemplo no Intellipad do Oslo.

clip_image002

Na esquerda desta figura temos exemplos de frases de entrada. No meio, a gramática que consegue analisar sintaticamente este texto de entrada. Na esquerda vocês vêem a saida depois da análise. É um texto com chaves que representa uma AST (ou grafo).

Para quem não viu, temos um webCast similar em: https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?culture=pt-BR&EventID=1032416175&CountryCode=BR

Vale a pena também ler os artigos:

Textual Domain Specific Languages for Developers - Part 1

Textual Domain Specific Languages for Developers - Part 2

Textual Domain Specific Languages for Developers - Part 3

Creio que com isto você consegue criar o seu parser, não consegue?

Promessa é divida. Com isto acabei de pagar a minha!

Abraços,

Otavio

Sobre Chefes, Algoritmos e Pérolas

Num blog antigo comentei sobre estilos de arquitetura e, em particular, sobre o estilo barroco, onde o excesso é uma virtude. Isto também é real para algoritmos.

Uma vez fui chamado pelo presidente da empresa em que trabalhava para otimizar o tempo de processamento de uma análise financeira. O produto dava ao usuário uma espécie de planilha, como o Excel, em que várias fórmulas do usuário eram interpretadas, chamando queries do banco de dados, potencialmente, a cada fórmula. Com isto em mãos, os usuários podiam construir o planejamento do custo financeiro de seus projetos com dados reais de um ERP e valores futuros de moedas, juros, etc. Nesta ocasião alguns clientes reclamavam que o cálculo estava levando mais de uma hora e o pedido era simples: diminuir este tempo.

Como arquiteto, já estava com idéias de uso de cachê, queries feitas em batch e outras otimizações comuns neste cenário, quando dei início à engenharia reversa no código.

O código não estava ruim de todo. Engenharia reversa feita, comecei a planejar as mudanças quando o bom senso me fez rodar um profile antes de começar as modificações.

Com o relatório na mão, algo me assustou: a maior parte do tempo de execução estava numa única função: a concatenação de strings, uma função feita em casa.

Curioso, abri o código da função e o que eu vi foi algo interessante. Era uma função que chamava a si mesmo, recursivamente, em pelo menos em 3 ocasiões. Levei algum tempo analisando se havia algum truque. Por que não, simplesmente, criar um array com o tamanho da soma dos dois strings para depois copiá-los?

Desistindo de entender o porquê, criei a função correta e testei com uma planilha real. Resultado: cerca de 4 minutos para algo que antes levava 1 hora.

Com o resultado na mão fui ao presidente da empresa e anunciei o bom resultado. Quando me perguntou o que havia de errado, mostrei a ele a listagem da função e anunciei: o culpado era esta “pérola do barroco”!

Lembro que ele riu, mas um pouco sem graça. Desconfiado, fui investigar e descobri que ele tinha sido o autor desta pérola!

Moral 1) Pratique o profile antes de iniciar uma re-arquitetura;

Moral 2) Investigue quem fez a besteira antes de anunciá-la.

Abraços

Arquitetura no Kernel do Windows 7

Talvez eu tenha sido um dos últimos a instalar o Windows 7 na Microsoft. Tenho dezenas de softwares e SDKs instalados na minha máquina e costumo demorar dias para uma instalação completa – algo muito arriscado quando estamos tão perto de apresentações com demonstrações que irei fazer no TechEd. Nestas horas, “não mecha!” é meu mantra. Além disto, há muito esperava a chegada do versão final do Windows 7. Não gostaria de multiplicar o trabalho da instalação mais vezes enter Betas, RTCs e RTMs.

Nesta semana que passou tive a grata surpresa de receber uma máquina nova – menos poderosa que a atual, porém, mas leve e com os mesmos 4G que tenho hoje. Animado, instalei o Windows 7 Enterprise e muitos dos outros softwares do meu dia a dia enquanto continuava trabalhando na antiga. Estou hoje há 4 dias operando 100% só com ela e aqui vão algumas impressões:

  • A instalação foi suave. Coloquei a versão 64 bits e migrei os dados com o Easy Transfer via disco externo. Muito bom ver depois de ½ hora todos meus muito gigas de docs espalhados serem migrados sem falha.
  • A nova interface é ótima. Senti falta do “Show Desktop” no início, mas fiquei em casa depois que me contaram que está no botão mais à direita do taskbar depois do relógio.
  • O W7 parece ser mais rápido que o Vista que tenho no hardware antigo, mais poderoso. Quando rodei algoritmos de teste em C# verifiquei que a máquina antiga é mesmo mais poderosa. Porém, o reajuste no kernel e a resolução de dependências que geravam lock e enfileiramentos são responsáveis por esta melhor responsividade real (alguém conhece um bom adjetivo para que eu não use este anglicismo?).

E aqui vem meu ponto sobre arquitetura. Vale a pena ver a conversa com Arun Kushan no channel 9. Ele é o arquiteto que mudou o esquema de lock do Windows 7 e Windows Server 2008 R2, liberando-os para lidar com 256 processadores (a limitação antes era de 64).

Estamos muito acostumados em pensar sobre arquitetos de software como aqueles responsáveis pela escolha de tecnologias de middleware, sistemas de identidade, estruturas de camadas, etc. Arquitetos como o Arun e o Anders Hejlsberg trabalham com estruturas lingüísticas ou com estruturas granulares que têm um impacto imenso no desempenho e capacidade dos softwares envolvidos. São partes fundamentais de um projeto que, se não forem bem estruturadas antes, causam desastres.

Muitos arquitetos, devido aos diagramas de bloco, se vêem construindo a fachada da solução de software. Gosto de lembrar que a consistência interna, muitas vezes não representável por diagramas de blocos, são fundamentos essências de um bom arquiteto.

Abraços

PS: Para quem quer melhorar seu aplicativo usando o que tem de novo no Windows 7, vale a pena baixar o XP2Win7 – uma aplicação exemplo que mostra como usar o Taskbar, sensores, multitouch, Windows Search, Transaction File System e muitos outros. Basta dar uma olhada em http://code.msdn.microsoft.com/XP2Win7 e ver como é simples usar o que a infraestrutura te dá de graça.

More Posts Next page »
 
Page view tracker