Caelum | Ensino e Inovação - Cursos de Java, Scrum, Ruby on Rails


Lançado Rails 3 – e apostila atualizada para download

Por David Paniz em 02/09/10

Depois de dois anos de trabalho da junção do Merb com o Rails, saiu nesse fim de semana a versão final do Rails 3. E, para comemorar, a Caelum acaba de liberar a apostila atualizada do curso RR-71 Ruby On Rails 3 para download.

Rails 3

Entre as principais novidades, temos a nova API de query do ActiveRecord (ARel), a nova sintaxe para definição de rotas, o unobstrusive JavaScript para desacoplamento do prototype como biblioteca padrão, além de melhorias no ActionController e ActionMailer. Mas a principal mudança nessa nova versão é seu agnosticismo, agora é possível substituir partes do Rails por outros frameworks de sua preferencia sem precisar sofrer como antigamente e quem escreve essas outras opções não precisa mais fazer hacks e código de difícil manutenção, tornando o Rails um verdadeiro ecossistema.

Por exemplo, o ARel nos provê uma nova maneira de montarmos as queries através de uma DSL mais elegante que os finders do Rails 2, e também possibilita adicionarmos filtros extras a uma query já existente como no exemplo abaixo:

juridicas = Cliente.where(:tipo => "PJ").order("nome")
inativos = juridicas.where(:ativo => false)

Para a nova definição de rotas, o Rails 3 traz diversas melhorias de sintaxe, entre elas, na declaração das rotas nomeadas:

match 'cadastro', :controller => 'usuarios', :action => 'new'
# se acessar /cadastro vai para a action 'new' no usuarios_controller, mas não cria os helpers

match 'cadastro', :controller => 'usuarios', :action => 'new', :as => 'rota_cadastro'
# se acessar /cadastro vai para a action 'new' no usuarios_controller, mas agora ganhamos os
# helper_methods rota_cadastro_path e rota_cadastro_url

Se você ainda não conhece Rails, e quer começar já por essa nova versão, baixe agora a apostila do curso RR-71 de Ruby on Rails, que foi reformulada durante esses últimos meses acompanhando todas as mudanças no framework. Está liberada a versão da apostila beta. Envie seus comentários e sugestões!

  • Share/Bookmark

Um produto para muitos clientes: implementando multitenancy

Por Guilherme Silveira em 23/08/10

São diversos as aplicações web disponíveis, como quadros eletrônicos, sistemas de tracking, email e aplicações para empresas, ou até mesmo controle de clientes e vendas. Eles até ganharam um pomposo nome dentro do cloud computing: Software as a Service (SAAS).

O que essas aplicações possuem em comum? Todas elas atendem diversos clientes sem que um tenha conhecimento da existência dos outros.

Em um post recente no Tectura.com.br foram discutidas vantagens e desvantagens de diversas abordagens para produtos com a necessidade de suportar mais de um tenant.

A Microsoft categoriza três tipos de abordagens dependendo do nível de compartilhamento de recursos entre os clientes e apresente um relatório onde analisa os custos contra a segurança.

Em um extremo, nada é compartilhado entre cada cliente: para cada nova conta criada dentro de seu serviço, é criada uma nova máquina na cloud e uma instalação limpa é executada, com seu próprio banco.

Dois servidores, dois bancos

Nessa abordagem p processo de autorização é automático, um cliente é criado através da instalação automática de uma nova máquina virtual e os recursos são compartilhados se o ambiente for uma cloud.

Na outra ponta, tudo é compartilhado: novos clientes são inseridos no mesmo conjunto de máquinas e a instalação é feita através de um simples insert no banco, adicionando um novo cliente. Qual abordagem escolher?

Um servidor, um banco

A autorização é feita programaticamente na aplicação, os recursos são compartilhados entre todas elas, escala-se tipicamente através do uso de load balancers e replicação master/slave e a cada novo cliente basta executar um insert no banco.

Em qualquer abordagem onde o banco seja compartilhado por diversas empresas, precisamos garantir a segurança dos dados de cada uma, para que nenhum acesso indevido ocorra, trazendo preocupações de autorização para dentro do código de nossa aplicação. Caso seja criado para cada cliente um banco em uma máquina no cloud, essa questão fica concentrada em um único ponto da arquitetura, a segurança está implícita.

Dois servidores, um banco

Diversos servidores e um banco implica em clientes não afetarem uns aos outros, um controle programático de autorização e novos clientes são criados a partir da instalação de um novo contexto web.

Ao mesmo tempo, o processo de customização de seu serviço por cliente também é afetada de acordo com o nível de compartilhamento de dados entre eles. Na abordagem onde os clientes compartilham o mesmo servidor web, a customização é uma preocupação de nossa aplicação, enquanto ao utilizarmos aplicações web distintas para cada cliente, podemos facilmente separar customizações por instância, por deploy efetuado.

A escalabilidade é afetada pois compartilhar recursos em memória na camada web, entre eles dados cacheados do banco ou filas, permitem diminuir o tempo de processamento ou de latência, aumentando o número de requisições suportados.

O processo de escalar também está diretamente ligado: quando uma máquina não aguentar mais as requisições, será levantada uma outra máquina que funcionará em cluster para um, n ou todos os clientes?

Outro fator importante é como limitar o uso do serviço de acordo com as regras contratadas pelo cliente e controlar o dano que um pode causar a outros. Em um pico de uso por parte dos usuários de um cliente, ativamos o chargeback, dependendo do tipo de serviço que é prestado. Implementações comuns de chargeback em cloud (baseados em virtualização) permitem um pico de consumo temporário ou até mesmo mover a aplicação de uma máquina para outra sem que cliente algum perçeba o que está acontecendo.

Dois servidores, um banco

Diversos servidores e um banco implica em clientes não afetarem uns aos outros, um controle programático de autorização e novos clientes são criados a partir da instalação de um novo contexto web.

Ao invés daqueles usuários atrapalharem a performance de outros clientes, é alocado memória e processador distintos dos atuais para ele.

A simplicidade do código é um dos fatores mais afetados pela escolha feita: um código que sabe da existência de múltiplos clientes está diretamente ligado a uma chave que identifica o cliente atual. E essa ligação se reflete por todos os lados do código, uma vez que o comportamento da aplicação em geral depende dele: uma chave estrangeira, um relacionamento, que vai permear toda a aplicação.

Caso a aplicação implemente o suporte a multi tenant através de instalções web distintas, com configurações para cada cliente apontando para um bancos distintos, o código fica mais simples e fácil de manter, uma vez que não é necessário se preocupar com a existência de outros clientes.

Por fim, uma preocupação que surge para a empresa provedora dos serviços é de como agrupar os dados existentes em todos os clientes e gerar relatórios de administração e estatísticas que permitam a melhora do serviço prestado. Quando possuimos um único banco, escalado através de master slave ou múltiplos nós, basta executarmos queries longas (sql ou não), mas se o sistema estiver distribuido entre diversos bancos sem ligação entre si, um processo de batch deve rodar para agrupar os dados e permitir o consumo posterior pelas ferramentas de BI.

É importante analisar todos esses pontos antes de tomar a decisão se o controle de espaço de aplicação por cliente será feito no nosso código, na camada web, no banco ou em algum outro ponto. Como discutimos bastante no curso de arquitetura, toda e qualquer decisão implica em um tradeoff: o importante é saber o que está sendo trocado e qual é o impacto disso.

  • Share/Bookmark

ConcurrentModificationException e os fail-fast iterators

Por Paulo Silveira em 18/08/10

A java.util.ConcurrentModificationException costuma surpreender a muitos: como uma exception com esse nome pode aparecer mesmo em uma aplicação single threaded, que não envolve concorrência alguma no acesso dessa coleção?

Para entender melhor, vale relembrar que as coleções muito antigas, como Vector e Hashtable, são thread safe, implementado através do uso do synchronized em seus métodos e iteradores (Enumeration na época). No Java 1.2, com a entrada das interfaces Collection, List, Set e Iterator, as novas implementações optaram por não ser thread safe, dado o custo de performance que o synchronized apresentava (hoje em dia é muito, muito menor), e de que a grande maioria dos casos de uso dessas estruturas não necessitavam de thread safety.

Essa novidade, das novas coleções não serem preparadas para o uso em um ambiente multi thread, poderia causar surpresas para quem não a estivesse esperando. Em vez de deixar alguém percorrer uma coleção através de um Iterator enquanto ela é modificada concorrentemente (acarretando em dados incorretos, nulls, etc) optou-se por evitar esses casos. Para isso tenta-se “adivinhar” quando houver mais de uma thread trabalhando com a mesma coleção concorrentemente (uma modificando a coleção, e outra iterando-a).

Como fizeram isso sem usar mecanismos de lock? Usando um contador!

Imagine que a cada modificação na estrutura de uma ArrayList, nós incrementamos um contador (modCount). Quando um Iterator é requisitado pelo método iterator(), esse contador é guardado como atributo (expectedModCount). Cada vez que você invocar os métodos next() e remove() (lembrando que temos apenas 3 métodos na interface Iterator), esse iterador vai checar se o contador da ArrayList é exatamente igual ao número que era esperado, isso é, tem o mesmo valor desde quando começamos a percorrer seus elementos. Caso os valores sejam diferentes, a java.util.ConcurrentModificationException é lançada, pois foi detectada uma modificação concorrente (comodification).

Mas como essa exceção pode ocorrer mesmo quando não temos outras threads acessando a mesma coleção? O problema é exatamente essa tentativa de detectar um acesso concorrente:

List<String> nomesDosAlunos = new ArrayList<String>();
// ...  popula lista com strings
for (String nome : nomesDosAlunos) {
	if (nome.equals("nome procurado")) {
		nomesDosAlunos.remove(nome);
	}
}

Esse código vai lançar ConcurrentModificationException caso encontre a String procurada:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
	at java.util.AbstractList$Itr.next(AbstractList.java:343)
	...

Isso ocorre pois o Iterator utilizado internamente nesse laço vai detectar, na próxima chamada ao seu método next(), que o número de modificações desta ArrayList é diferente de quando ele foi instanciado. Essa stacktrace pode confundir um pouco, já que o uso do enhanced for esconde a utilização do iterator, e não conseguimos ver explicitamente a invocação do método next na linha do for.

Repare que a detecção nesse caso single threaded é arbitrária para evitar casos estranhos (se removessemos o objeto da lista, o iterator deveria ainda percorre-lo?), e coleções sem fail fast iterators vão possibilitar esse tipo de remoção e outras modificações sem lançar excessões. Como então evitar essa exception nas coleções com fail fast iterators? Utilizando o iterator.remove() em vez do enhanced for:

List<String> nomesDosAlunos = new ArrayList<String>();
// ...  popula lista com strings
for (Iterator<String> i = nomesDosAlunos.iterator(); i.hasNext();) {
	String nome = i.next();
	if (nome.equals("nome procurado")) {
		i.remove();
	}
}

Esses iteradores são chamados de fail-fast por possuirem essa característica de falhar quando uma modificação concorrente é detectada. Essa é apenas uma tentativa do Iterator em encontrar possíveis bugs, e existem casos e combinações em que a modificação concorrente pode passar desapercebida por esse mecanismo, e você não deve se basear nessa exception para garantir que sua aplicação é thread safe.

E se precisarmos usar uma lista em ambiente multi thread e percorrer seu iterator enquanto a modificamos? Podemos usar a CopyOnWriteArrayList (nos casos de muita leitura e pouca escrita) ou ainda, se não for precisar de acesso aleatório aos elementos, utilizar uma Queue como a ConcurrentLinkedQueue. Utilizar o antigo Vector ou a própria ArrayList em conjunto com Collections.synchronizedList é thread safe e não vai lançar ConcurrentModificationException desde que você sincronize o uso de seus iterators:

synchronized(nomesDosAlunos) {
	for (String nome : nomesDosAlunos)
		System.out.println(nome);
}

A performance dessas diferentes escolhas pode mudar drasticamente dependendo do contexto de sua aplicação. Pode também não ser necessário utilizar uma coleção que se preocupa com thread safety: algumas vezes basta diminuir o escopo da coleção (pode não ser necessário deixá-la na session, por exemplo) ou ainda trabalhar com cópias defensivas.

  • Share/Bookmark

As 7 práticas para um site otimizado

Por Sérgio Lopes em 29/07/10

Todo mundo gosta de sites rápidos. Seus primos não sabem dizer se você tem um arquitetura escalável, se seu banco NoSQL é mais robusto ou se é melhor usar Web Services SOAP ou REST. Mas eles sabem dizer duas informações com precisão: se seu site é bonito e se ele é rápido. Performance, medida pelo usuário, é o que te diz se a velocidade de resposta da sua aplicação é aceitável. E, assim como usuários não se cansam de bordas redondas e sombrinhas bonitas, sempre haverá otimizações possíveis para tornar seu site mais rápido.

Inspirado pela recente edição 2010 da Velocity Conference organizada pela O’Reilly e por uma palestra recente que dei com o Alberto Souza aqui na Caelum, além de discussões que aparecem no nosso curso que trata de HTML, CSS e Javascript, selecionei um punhado de dicas para deixar qualquer site ou aplicação Web mais rápido em instantes. Meu TOP 7 não pretende ser uma lista completa e nem detalhada de boas práticas. É mais um guia rápido sobre tudo o que você deveria fazer em qualquer site antes de perguntar a opinião dos seus primos – ou do seu chefe, ou de qualquer usuário. Eis então:

TOP 7 das Otimizações na Web:

0. Habilite GZIP em todas as suas páginas. Se você ainda não fez isso, pare imediatamente de ler esse post e faça. É o passo zero, nem vou contá-lo. Dicas de como fazer no Tomcat, Jetty, Apache, IIS.

1. Agrupe arquivos JavaScript/CSS. 90% do trabalho de otimização consiste em diminuir o número de requests feito na página e o peso de cada um deles. Você usa JQuery (ou qualquer outro framework) com JQuery UI e mais meia dúzia de plugins, sem contar o código JS da sua aplicação? Junte tudo no menor número de arquivos possíveis para evitar muitos requests. Mesma coisa com CSS, um arquivo com tudo basta. Mas gosta de manter as coisas organizadas ao invés de escrever um arquivo com milhares de linhas? Então junte essas coisas dinamicamente como faz o pessoal do Digg ou faça o serviço no build da aplicação como preferimos aqui na Caelum.

2. Comprima o JavaScript/CSS. Você escreve JS/CSS elegante, bem documentado, com código organizado e variáveis de nomes legíveis. Mas seus primos não se importa com isso, eles querem um site rápido e isso significa não trafegar no request bytes e mais bytes de documentação ou de código legível. Comprima todo o seu código JavaScript e CSS usando o YUI Compressor ou o Google Closure Compiler. Faça isso em build time ou dinamicamente.

3. Otimize suas imagens com Smush-it. Nem todos os KB de suas imagens são necessários para o cliente. Arquivos JPEG possuem uma série de metadados e PNGs possuem palhetas de cores. Remova vários KBs desnecessários de suas imagens usando o Yahoo Smush-it em todas elas.

4. Coloque CSS no topo e JavaScript embaixo. Simples assim. Referencie os seus CSS no <head> com <link> para evitar o flash effect, e coloque seus scripts logo antes do fechamento do <body> para não atrasar a renderização da tela.

5. Não redimensione imagens no HTML. Não use os atributos width e height das imagens para redimensionar seu tamanho. Sirva as imagens já com tamanho certo e otimizadas. Mas coloque sempre o width e height de todas as imagens para ajudar o browser nos cálculos da renderização da página.

6. Configure o Expires corretamente. Use o cache do navegador para prover uma melhor experiência ao usuário no segundo request – seja no retorno ao site ou navegando para a próxima página. Permita também que Proxies entre seu servidor e o cliente cacheiem elementos do seu site para evitar um download demorado. Configure o header de Expires de todo seu conteúdo estático (JS, CSS, imagens) com alguns dias de duração.

7. Use YSlow e PageSpeed. Depois que você fez todas as otimizações anteriores, instale o YSlow e o PageSpeed para um diagnóstico detalhado de seu site. Monitore sua performance constantemente e vá implementando sempre novas otimizações. Seus usuários agradecem.

Esse TOP 7 são todas as otimizações que você deveria ter feito ontem em qualquer site seu antes de começar a falar em performance. Há muitas outras práticas, algumas mais complicadas, como CSS Sprites, usar CDNs, otimizar seus cookies, fazer preload e postload de conteúdo, ou comprimir seu HTML. Dependendo da tecnologia que você usa no seu sistema, você terá maior ou menor facilidade de aplicar todas essas práticas: no JSF e ASP.NET você terá benefícios para desenhar sua interface, mas muito mais trabalho de fazer esse ajuste fino; com Struts, Rails, ASP.NET MVC, VRaptor e frameworks action based, será o contrário.

Bônus: Onde ir depois?

E você, que outras práticas aplica no seu website?

  • Share/Bookmark

Possibilidades de design no uso do seu Generic DAO

Por Lucas Cavalcanti em 26/07/10

Muitas vezes, quando estamos criando nosso sistema temos a tentação de criar o GenericDAO para não ter que ficar repetindo as operações CRUD e listagens.

O maior problema com o GenericDAO é que não necessariamente todas as operações fazem sentido para uma determinada classe. Daí o que fazer se, por exemplo, não faz sentido excluir um pagamento?

public class PagamentoDAO
          extends GenericDAO<Pagamento> {

     @Override
     public void excluir(Pagamento pagamento) {
         throw new UnsupportedOperationException();
     }
}

Não parece uma solução muito elegante, mas é um dos únicos jeitos de proibir uma operação declarada na classe mãe, e ainda assim, só funciona em tempo de execução. Esse é um dos principais motivos para muitos não gostarem de usar o GenericDAO e preferirem usar composição ao invés de herança. Mas como fazer para não repetir o código trivial das operações do CRUD?

Um dos jeitos é usar uma outra abstração de persistência de objetos: o Repository. Com o Repository, temos um lugar onde podemos guardar e buscar por objetos, não importando como fazemos isso. O DAO já está muito ligado com armazenamento em banco de dados, e foi criado quando as operações do BD eram muito trabalhosas (em especial no JDBC).

E como juntar o Repository com o GenericDAO? O Repository pode ser definido, por exemplo, como uma interface, e aí podemos fazer o seguinte: se, para um pagamento, faz sentido apenas salvar e listar, mas não excluir, então criamos a interface:

public interface PagamentoRepository {
    void salva(Pagamento pagamento);
    Pagamento busca(Long id);
    List<Pagamento> lista();
}

E usamos o GenericDAO como implementação dessa interface:

class PagamentoDAO
          extends GenericDAO<Pagamento>
          implements PagamentoRepository {
   // implementacao extra
}

E no nosso código de domínio “nunca” referenciaremos o PagamentoDAO, apenas o PagamentoRepository, assim mesmo que a implementação saiba fazer mais coisas, a interface só expõe as operações suportadas.

Se você usa Injeção de Dependências e algum framework que a suporta (como o VRaptor, Spring ou Java EE6), você pode deixar os DAOs apenas como infraestrutura, e usar os Repositories como interfaces públicas da sua aplicação:

public class PagamentoController {
    public PagamentoController(PagamentoRepository repository) {
         this.repository = repository;
    }

    public void salva(Pagamento pagamento) {
         // validações e outras regras
         repository.salva(pagamento);
    }
}

Ainda poderíamos melhorar o nome do nosso repositório para BaseDePagamentos, ContasAPagar ou ainda Pagamentos, evitando usar sufixos nas classes. Usando abstrações e padrões simples conseguimos evitar repetição de código sem perder a semântica e restrições das nossas classes de modelo, além de esconder detalhes de implementação.

  • Share/Bookmark



Caelum | Ensino e Inovação
São Paulo: Rua Vergueiro, 3185, cj. 87, próximo ao Metrô Vila Mariana   |   Tel. (11) 5571-2751
Rio de Janeiro: Rua Senador Dantas, 80, cj. 307/308 - Centro   |   Tel. (21) 2220-4156 ou 2297-0033
Brasília: SCS Qd. 8 Bl. B-50, Sala 521 - Ed. Venâncio 2000   |   Tel. (61) 3039-4222