Mar
16

Sobre acoplamento e injeção de dependências em Ruby

Quem me conhece sabe que eu gosto bastante de linguagens com tipagem estática, mas vou parar um pouco para falar sobre acoplamento em linguagens com tipagem dinâmica e como isso é diferente do que eu estou acostumado a fazer normalmente.

Vou começar por uma execução de um método qualquer em ruby:

class MinhaClasse
  def faz_alguma_coisa(outro_cara)
    outro_cara.algum_metodo
  end
end

Qual é o nível de acoplamento da MinhaClasse com o outro_cara? O mais baixo possível! Só existe o acoplamento com a interface, no sentido mais puro da palavra, ou seja, só precisamos que o outro_cara tenha um método chamado algum_metodo, que não recebe nenhum argumento. Ou seja, o baixo acoplamento vem de graça! Não existe o acoplamento de tipos, que existe nas linguagens estáticas.

Se existirem duas classes que tem esse método, qualquer uma delas pode ser usada:

class CaraLegal
  def algum_metodo
    puts "yey!"
  end
 
  def outro_metodo
    puts "=)"
  end
end
 
class CaraChato
  def algum_metodo
    puts "Zzzzz"
  end
end

Agora se mudar um pouco a MinhaClasse, por exemplo:

class MinhaClasse
  def faz_alguma_coisa(outro_cara)
    outro_cara.algum_metodo
 
    outro_cara.outro_metodo
  end
end

Quebra todos os lugares que usam o CaraChato ou qualquer outra classe do sistema como argumento nesse método (inclusive os mocks que não esperavam a mensagem :‍outro_method ¬¬). Quanto mais métodos eu chamar no outro_cara, mais eu estou me acoplando à interface dele, e maior é a chance de quebrar algum código que usa a MinhaClasse. Ou seja, ao mesmo tempo em que eu estou me acoplando pouco a um objeto, estou me acoplando a todas as classes que possuem os métodos que chamei em faz_alguma_coisa.

Agora, suponha que a MinhaClasse precisa enviar um email, e eu resolvi implementar da seguinte maneira:

class MinhaClasse
  #...
  def processo_complicado
     email = gera_email 
     enviador = EnviadorDeEmail.new
     enviador.envia email
  end
end

Agora além de estar se acoplando a uma interface, estaria também se acoplando a uma implementação! A vida me ensinou que nesse caso, para remover esse acoplamento posso usar injeção de dependências e receber esse enviador no construtor da MinhaClasse, por exemplo. Mas peraí, isso é Ruby, será que esse código está acoplado à implementação do EnviadorDeEmail mesmo? Se eu quiser trocar a implementação padrão por uma que não fica esperando o email ser enviado, por exemplo, vou ter que varrer o sistema inteiro pra fazer isso? Não. Só preciso fazer isso em algum lugar:

EnviadorDeEmail = EnviadorDeEmailNaoBlocante

Se eu tenho várias classes que oferecem serviços e são usadas como dependências no meu sistema, basta eu escolher um nome para o serviço, e dar new na hora que eu precisar usar (ou coisa parecida). E no meu ponto de entrada do sistema, simplesmente faço o ‘bind’ desses serviços para as implementações:

EnviadorDeEmail = EnviadorDeEmailNaoBlocante
Cache = MemCache
GeradorDeNotaFiscal = NotaFiscal::GeradorViaSoap
#...

Sem contar que conseguimos implementar o new pra retornar a mesma instância, já que ele é um simples método da classe. Os motivos que tínhamos para não dar new nas classes não valem aqui (pelo menos não a maioria deles). O problema é: qual é o perigo disso?

Oct
04

Deixando seu código mais funcional com Scala for comprehensions

Há alguns meses estava pareando com o Alberto Souza para melhorar o plugin do VRaptor para scala fazendo o VRaptor navegar pelos getters (properties) do Scala.

O componente a ser sobrescrito no VRaptor é o TypeFinder, que é responsável por descobrir os tipos dos caminhos que colocamos nas URI templates do VRaptor. Ex:

@Get("/livros/{livro.id}") //==> o parâmetro livro.id é um Long, por exemplo
public void visualiza(Livro livro) {...}

A implementação padrão é a seguinte (não precisa tentar entender o que acontece ainda, é só pra mostrar o estilo do código):

public Map<String, Class<?>> getParameterTypes(Method method, String[] parameterPaths) {
  Map<String,Class<?>> result = new HashMap<String, Class<?>>();
  String[] parameterNamesFor = provider.parameterNamesFor(method);
  for (String path : parameterPaths) {
    for (int i = 0; i < parameterNamesFor.length; i++) {
      String name = parameterNamesFor[i];
      if (path.startsWith(name + ".") || path.equals(name)) {
        String[] items = path.split("\\.");
        Class<?> type = method.getParameterTypes()[i];
        for (int j = 1; j < items.length; j++) {
          String item = items[j];
          try {
            type = new Mirror().on(type).reflect().method("get" + upperFirst(item)).withoutArgs().getReturnType();
          } catch (Exception e) {
             throw new IllegalArgumentException("Parameters paths are invalid: " + Arrays.toString(parameterPaths) + " for method " + method, e);
          }
        }
        result.put(path, type);
      }
    }
  }
  return result;
}

A idéia do código é, basicamente, pegar os caminhos ({livro.id}), partir dos nomes dos parâmetros ([livro]) e procurar o tipo (livro.getId() retorna Long). Mas o principal é perceber que isso é um típico código imperativo em java: pegar todos os dados necessários (parameterPaths, parameterNames, etc) percorrê-los e popular um mapa (result) que é o resultado esperado do método.

Para transformar esse código em funcional precisamos mudar o nosso pensamento. Para deixar o código mais funcional o mais natural é, a partir dos dados, aplicar funções e transformá-los em outros dados. Algo como criar uma função que tem como entrada a lista de paths e a lista de nomes e, como saída, o mapa de caminhos para tipos. Até aí, nada muito diferente da programação imperativa. A grande sacada é que não vamos chegar ao nosso objetivo executando comandos um atrás do outro, mas sim fazer composição de funções (lembra do f ∘ g do colégio?) transformando, aos poucos, a entrada na saída esperada.

A tradução direta do código acima para Scala, feita automaticamente com o menu ‘Convert Java file to Scala’ do IntelliJ e levemente modificada retirando elementos desnecessários, fica assim:

def getParameterTypes(method: Method, parameterPaths: Array[String]) = {
  val result = new HashMap[String, Class[_]]
  val parameterNamesFor = provider.parameterNamesFor(method)
  for (path <- parameterPaths) {
    for (i <- 0 to (parameterNamesFor.length - 1)) {
      val name = parameterNamesFor(i)
      if (path.startsWith(name + ".") || (path == name)) {
        val items = path.split("\\.")
        var clazz = method.getParameterTypes(i)
        for(j <- 1 to (items.length - 1)) {
          val item = items(j)
          try {
            clazz = new Mirror().on(clazz).reflect.method("get" + upperFirst(item)).withoutArgs.getReturnType
          } catch {
            case e => {
              throw new IllegalArgumentException("Parameters paths are invalid: " + Arrays.toString(parameterPaths) + " for method " + method, e)
            }
          }
        }
        result.put(path, clazz)
      }
    }
  }
  result
}

Esse código não tem quase nada de funcional ainda, a idéia é deixá-lo mais funcional com alguns recursos do Scala.

O segundo for está passeando pelos índices, já que precisamos usar o mesmo índice para pegar o nome e o tipo dos parâmetros, que estão em listas (arrays) diferentes. Em Scala, podemos trocar:

for (i <- 0 to (parameterNamesFor.length - 1)) {

por:

for (i <- parameterNamesFor.indices) { // indices é o plural de index ;)

No terceiro for precisamos ignorar o primeiro elemento do caminho: já sabemos o seu tipo e vamos navegar a partir dele. O código com o jeitão java abaixo tem uma tradução bem mais legal em Scala (e em várias outras linguagens).

val items = path.split("\\.")
for(j <- 1 to (items.length - 1)) {
   val item = items(j)

O que queremos é percorrer a lista de items jogando fora o primeiro elemento:

val items = path.split("\\.")
for (item <- items.drop(1))

ou ainda:

for (item <- path.split("\\.").drop(1))

Só com os dois detalhes acima (além de renomear variáveis e remover o try..catch, que nunca é obrigatório em Scala) já temos um código bem mais limpo, mas ainda não bom (ou funcional) o suficiente:

def getParameterTypes(method: Method, paths: Array[String]) = {
  val result = new HashMap[String, Class[_]]
  val names = provider.parameterNamesFor(method)
  for (path <- paths) {
    for (i <- names.indices) {
      val name = names(i)
      if (path.startsWith(name + ".") || (path == name)) {
        var clazz = method.getParameterTypes(i)
        for(item <- path.split("\\.").drop(1)) {
          clazz = new Mirror().on(clazz).reflect.method("get" + upperFirst(item)).withoutArgs.getReturnType
        }
        result.put(path, clazz)
      }
    }
  }
  result
}

O próximo passo é eliminar os dois primeiros for‘s aninhados usando um recurso do Scala chamado for comprehensions. Os dois for‘s são equivalentes a:

for (path <- paths; i <- names.indices) {

Isso é bem mais do que só economizar código, mas a explicação completa sobre o que é isso precisaria de um post inteiro. Para entender melhor o poder de for comprehensions, vamos fazer o inline da variável name:

for (path <- paths; i <- names.indices) {
      if (path.startsWith(names(i) + ".") || (path == names(i))) {
          //resto do código
      }
}

Tudo que temos dentro do for agora é o if. Nesse caso, podemos transferir a responsabilidade de filtrar os elementos para o próprio for:

for (path <- paths; i <- names.indices; if path.startsWith(names(i) + ".") || (path == names(i))) {
   //resto do código
}

De novo, isso é bem mais do que só economizar código. Transformamos um código procedural (for -> for -> if) em um código funcional. É como se tivéssemos descrito o domínio da nossa função (voltando ao colégio):

D = { path ∈ paths, i ∈ indices tais que path começa com names(i)}

E o que está dentro do for será executado para cada elemento do domínio D – é uma função do domínio D numa imagem Im. Louco, não?

Código até agora:

def getParameterTypes(method: Method, paths: Array[String]) = {
  val result = new HashMap[String, Class[_]]
  val names = provider.parameterNamesFor(method)
  for (path <- paths; i <- names.indices; if path.startsWith(names(i) + ".") || (path == names(i))) {
    var clazz = method.getParameterTypes(i)
    for(item <- path.split("\\.").drop(1)) {
      clazz = new Mirror().on(clazz).reflect.method("get" + upperFirst(item)).withoutArgs.getReturnType
    }
    result.put(path, clazz)
  }
  result
}

================
BÔNUS:

Ainda dá pra deixar o código acima mais funcional (não necessariamente mais legível, mas isso é outra história).

Veja esse código:

var clazz = method.getParameterTypes(i)
for(item <- path.split("\\.").drop(1)) {
  clazz = new Mirror().on(clazz).reflect.method("get" + upperFirst(item)).withoutArgs.getReturnType
}

O que estamos fazendo, no fim das contas, é transformar a lista de itens (path.split("\\.").drop(1)) em uma clazz a partir de um valor inicial (method.getParameterTypes(i)). Isso é uma operação bem comum em programação funcional chamada fold. Então podemos usar o foldLeft e evitar o uso da variável mutável (var) clazz:

val items = path.split("\\.").drop(1)
val clazz = items.foldLeft(method.getParameterTypes(i)) { (clazz, item) =>
   new Mirror().on(clazz).reflect.method("get" + upperFirst(item)).withoutArgs.getReturnType
}

================
BÔNUS 2:
Outra coisa não muito funcional que ainda estamos fazendo é a geração do mapa de resultado:

val result = new HashMap[String, Class[_]]
for (path <- paths ....) {
   val clazz = .....
   result.put(path, clazz)
}
result

Em programação funcional não deveríamos usar variáveis mutáveis (o valor do objeto result muda durante a execução do código). Em algumas linguagens como Erlang e Haskell isso é até uma regra da linguagem. A idéia é gerar esse mapa através apenas de aplicação e composição de funções.

Para fazer isso, precisaremos de outro recurso das for comprehensions: o yield. O que ele faz é pegar o retorno de cada iteração do for e gerar uma nova lista. Por exemplo:

val novaLista = for (i <- List(1,2,3)) yield i + 2
println(novaLista) // ==> List(3, 4, 5)

O que vamos fazer aqui é transformar cada item do nosso domínio D (o que está dentro do for) em uma tupla (path, clazz) que representa uma entrada (chave,valor) do nosso mapa:

val entries =
   for (path <- paths; i <- names.indices; 
         if path.startsWith(names(i) + ".") || (path == names(i))) yield {
     val items = path.split("\\.").drop(1)
     val clazz = items.foldLeft(...) { (clazz, item) =>
        new Mirror().on(clazz)....getReturnType
     }
     path -> clazz // jeito de criar a tupla (path, clazz) em scala
   }

Agora a variável entries é a lista de tuplas produzida pela aplicação da função que está dentro do for nos elementos do domínio D, definido pela for comprehension.

E como eu transformo isso num Map agora? O legal é que o Map do Scala já tem um construtor que recebe uma lista de tuplas!

val result = Map(entries:_*) // na verdade recebe um varargs, por isso o :_*, que explode a lista

Código final:

def getParameterTypes(method: Method, paths: Array[String]) = {
  val names = provider.parameterNamesFor(method)
  val entries =
     for (path <- paths; i <- names.indices; 
           if path.startsWith(names(i) + ".") || (path == names(i))) yield {
        val items = path.split("\\.").drop(1)
        val clazz = items.foldLeft(method.getParameterTypes(i)) { (clazz, item) =>
          new Mirror().on(clazz).reflect.method("get" + upperFirst(item)).withoutArgs.getReturnType
        }
        path -> clazz
     }
  Map(entries:_*)
}

E aí, melhor ou pior que o código java correspondente?

Aug
12

Java puzzle: protected method no construtor

Enquanto estava refatorando um pull request do VRaptor feito pelo acdesouza, cai numa situação estranha do java.

A idéia era criar um ponto de extensão para a classe DefaultRepresentationResult:

public DefaultRepresentationResult(...List<Serialization> serializations) {//construtor
    //...
    this.serializations = serializations;
    sortSerializations();
}
 /**
   * Override this method if you want another ordering strategy.
   *
   * @since 3.4.0
   */
protected void sortSerializations() {
       Collections.sort(this.serializations, new PackageComparator());
}

Daí se a pessoa quiser mudar a ordenação é só sobrescrever o método sortSerializations(). Até aí tudo bem, mas quão perigoso é isso?

Daí vem o seguinte puzzle:

public class Mae {
    public Mae() {
         metodo();
    }
    protected void metodo() {
        System.out.println("Mãe");
    }
}
public class Filha extends Mae {
   private final String x;
   public Filha() {
        this.x = "Testando";
   }
   @Override
   protected void metodo() {
       System.out.println(x);
   }
}

Se eu executar:

new Filha();

o que será impresso?

Jul
06

Anatomia de uma solução: VRaptor – linkTo para jsp – Final

Nas partes 1 e 2 conseguimos fazer com que isso funcionasse manualmente:

${linkTo[ProdutoController].adiciona} => /produtos

Mas se a gente precisasse colocar todas as URIs do sistema no mapa linkTo, não ia adiantar muita coisa. O legal é conseguir gerar esse mapa com todas as lógicas que existem no sistema de uma vez só. Para isso precisamos do componente do VRaptor que é responsável pelas rotas (URIs) das lógicas: o Router.
Com ele você consegue a URI de uma lógica a partir da classe e do método:

String uri = router.uriFor(Controller.class, metodo, argumentos);

com esse método conseguiríamos gerar o nosso mapa linkTo:

Map<String, Map<String,String>> linkTo = Maps.newHashMap();
List<Class<?>> controllers = //todos os controllers do sistema
for (Class<?> controller : controllers) {
    //*
    List<Method> metodos = new Mirror().on(controller).reflectAll().methods();
 
    Map<String, String> mapaDoMetodo = Maps.newHashMap();
    for(Method metodo : metodos) {
         String nome = metodo.getName();
 
         //aridade é o número de parâmetros de um método. Precisamos passar um array 
         //com o tamanho da aridade do método para o uriFor funcionar
         String uri = router.uriFor(controller, metodo, new Object[aridade(metodo)]);
 
         mapaDoMetodo.put(nome, uri);
    }
 
    linkTo.put(controller.getSimpleName(), mapaDoMetodo);
} 
request.setAttribute("linkTo", linkTo);
 
//...
private int aridade(Method method) {
    return method.getParameterTypes().length;
}

* ajuda do Mirror, uma biblioteca bem legal feita pelo Jonas Abreu para fazer reflection. Já vem como dependência do VRaptor.

Temos dois problemas com esse código. O primeiro deles é: Como conseguir uma lista de todos os controllers do sistema? O VRaptor não tem um método específico pra isso na sua API mas, como sempre, dá pra improvisar se a gente conhecer um pouco da sua API interna.

Um dos jeitos é pegar todas as rotas do sistema (router.allRoutes()) e, a partir delas construir a lista de controllers. O problema é que as rotas (interface Route) não têm um método direto pra isso (o mais perto seria: rota.resourceMethod(requestMockada, "qqer uri").getResource().getType(), não mto legal, ou reflection acessando os fields da rota).

Outro jeito seria interceptar o registro dos controllers e guardá-los em uma lista. Com o VRaptor é possível interceptar qualquer uma das classes anotadas com uma das anotações do VRaptor (ou até com alguma anotação sua anotada com @Stereotype). Esse interceptador é uma classe que implementa StereotypeHandler. Então podemos fazer:

@Component
@ApplicationScoped //nesse escopo pois isso vai rodar na inicialização do VRaptor
public class LinkToHandler implements StereotypeHandler { 
 
     public Class<? extends Annotation> stereotype() {
          return Resource.class; // intercepta todos os controllers
     }
 
     private List<Class<?>> controllers = Lists.newArrayList(); //guava ajudando aqui também =)
     public void handle(Class<?> controller) {
          controllers.add(controller);
     }
     public List<Class<?>> getControllers() {
         return this.controllers;
     }
}

Cada vez que o VRaptor acha um controller, ele vai chamar esse método handle, e adicioná-lo na lista. Assim conseguimos rodar o código que popula o mapa linkTo que eu postei acima. O código funciona muito bem, se nenhuma das rotas tem parâmetro (ou seja, se tivermos @Path("/produtos/{produto.id}") como vamos passar o id do produto?). O que nos leva ao segundo problema do código: como tratar URIs que têm parâmetros?

Se uma URI tem parâmetro, não podemos usar a nossa estratégia de gerar o mapa inteiro já com as URIs, pois ela vai ser diferente dependendo de qual parâmetro passamos para ela. Mas como passar parâmetros usando EL padrão?

${linkTo[ProdutosController].visualiza(produto)} => seria o ideal, mas não funciona =/
 
relaxando um pouco:
 
${linkTo[ProdutosController].visualiza[produto]} => isso sim, funciona! mas como?

Para poder fazer isso, o “método” visualiza precisa ser um mapa, que contém uma chave que é o produto que a gente passou. Poxa, será que precisamos criar um mapa com todos os produtos possíveis? Seria só loucura. Precisamos então de uma gambiarra solução mais criativa.

Para que o visualiza[produto] funcione, precisamos que visualiza seja um mapa, mas ninguém falou que precisa ser um HashMap ou um TreeMap. A única coisa necessária é implementar Map! Então vamos criar um mapa hackeado que retorne a uri dependo do objeto passado. O único método que a gente precisa implementar de verdade é o get, que é o método chamado quando a gente faz o vizualiza[produto].

O chato é que a interface Map tem vários métodos, e precisamos implementar todos. Mas o nosso amigo guava tem uma classe chamada ForwardingMap que é bem útil nesse caso (existem várias classes Forwarding que podem ser bem úteis também):

class Linker extends ForwardingMap<Object, String> {
 
    public Linker(Class<?> controller, Method method) {...} //guarda em fields
 
    @Override
    protected Map<Object, Linker> delegate() {
        return Maps.newHashMap(); //precisamos delegar para um mapa qualquer
    }
 
    @Override
    public String get(Object key) {
        return router.urlFor(controller, method, new Object[] { key });
    }
 
}
 
//código da solução anterior
for(Class<?> controller : controllers) {
    Map<String, Linker> mapaDosMetodos = Maps.newHashMap();
    for(Method metodo : metodos) {
         mapaDosMetodos.put(metodo.getName(), new Linker(controller, metodo));
    }
}

Agora se chamarmos no jsp:

${linkTo[ProdutoController].visualiza[produtoComId3]} ==> /produtos/3, legal =)
mas
${linkTo[ProdutoController].adiciona} ==> br.com.caelum...Linker@1def231 =S

Droga, criamos outro problema. Tudo bem, só sobrescrever o toString do Linker para também retornar a uri. Antes de mostrar o código completo, como fazer para passar mais de um parâmetro?

public class ProdutoController {
   @Get("/produtos/vendas/{dia}/{mes}/{ano}")
   public void vendasDe(int dia, int mes, int ano) {...}
}
${linkTo[ProdutoController].vendasDe(6,6,2011)} ==> err.. não
 
${linkTo[ProdutoController].vendasDe[6,6,2011]} ==> hum.. seria bom, mas também não
 
${linkTo[ProdutoController].vendasDe[6][6][2011]} ==> aí sim =)

ou seja, cada vez que recebermos um parâmetro precisamos retornar um outro mapa. Tudo bem, só retornar sempre um linker, só que a cada vez com um parametro a mais preenchido:

class Linker extends ForwardingMap<Object, Linker> {
 
    public Linker(Class<?> type, Method method) { // para não mudar o código anterior
        this(type, method, new Object[aridade(method)], 0);
    }
    private Linker(Class<?> type, Method method, Object[] args, int index) {//guarda em fields}
 
    @Override
    protected Map<Object, Linker> delegate() {
        return Maps.newHashMap();
    }
 
    @Override
    public Linker get(Object key) {
        Object[] newArgs = args.clone(); //para evitar memorização de argumentos
        newArgs[index] = key;
        return new Linker(type, method, newArgs, index + 1); //aumentando o índice para preencher o próximo parâmetro
    }
 
    @Override
    public String toString() {
        return router.urlFor(type, method, args); //só mostra a uri no último momento, 
                                                               //qdo o mapa for mostrado na jsp
    }
 
}

E pronto, dessa forma conseguimos suportar a geração dos links de qualquer lógica de um controller. Lembrando que se um parâmetro não é usado na URI, não precisa ser passado.

O legal dessa solução é que ela passa por várias coisas úteis (um pouco roubadas às vezes ;) ), que são legais saber quando um problema cabeludo aparece na sua frente. Espero que tenham gostado =).

Código da solução final: https://gist.github.com/1064176. Em breve estará integrado ao código do VRaptor, com os devidos testes e documentação.

Jun
19

Anatomia de uma solução: VRaptor – linkTo para jsp – parte 2

Antes de começar a solução, vamos usar uma rota mais fácil:

@Resource
public class ProdutoController {
    @Post("/produtos")
    public void adiciona(Produto produto) {...}
}

A idéia é conseguir fazer com que:

${linkTo[ProdutoController].adiciona}

retorne a URI /produtos.

Vamos, primeiro, olhar para a primeira parte e relaxar um pouquinho a sintaxe:

${linkTo['ProdutoController']}

Qual é a única forma disso funcionar? Na EL padrão você geralmente só pode navegar pelos getters, com três exceções: Lists, Arrays e Maps. Nas Lists e Arrays você pode acessar os índices (lista[0], array[1], etc), e nos mapas você pode acessar as chaves (mapa['chave']). Então pro código acima funcionar, podemos fazer o linkTo ser um mapa e adicioná-lo no request:

Map<String, ?> linkTo = Maps.newHashMap(); // do guava, para evitar declarar os generics de novo
linkTo.put("ProdutoController", objetoMagico);
 
request.setAttribute("linkTo", linkTo);

Agora só precisamos criar um objetoMagico em que eu possa chamar o .adiciona. Isso significa que precisamos de um objeto que tenha o método getAdiciona(). Até é possível gerar uma classe assim em tempo de compilação (com o APT) ou em runtime (com ASM ou javassist), uma classe para cada controller do sistema com getters para o nome de cada método. Mas isso é um pouco complicado demais (bem mais difícil que implementar a Tag ou o ELResolver que eu estava evitando), então vamos relaxar um pouco mais a sintaxe. Como fazer isso funcionar?

${linkTo['ProdutoController']['adiciona']}

Outro mapa!

Map<String, Map<String,String>> linkTo = Maps.newHashMap(); // viu como o guava é útil aqui? ;)
 
linkTo.put("ProdutoController", ImmutableMap.of("adiciona", "/produtos"));
 
request.setAttribute("linkTo", linkTo);

Usei aqui a classe ImmutableMap do guava, pro código ficar mais conciso. Isso cria um mapa imutável com uma única entrada (adiciona -> /produtos).

Agora o código ${linkTo['ProdutoController']['adiciona']} retorna o que a gente queria: /produtos.
O legal é que os colchetes são apenas um dos jeitos de acessar chaves de um mapa, não o único. No caso em que a chave é uma String, podemos simplificar a sintaxe:

${linkTo['ProdutoController'].adiciona} => /produtos

Já chegamos bem próximos da sintaxe proposta no começo do post, só falta retirar as aspas. Mas será que eu posso simplificar a sintaxe para ficar assim?

${linkTo[ProdutoController].adiciona} => /produtos (?)

Infelizmente não. Quando fazemos isso, o JSP procura por uma variável chamada ProdutoController. Bom, não seja por isso, basta fazer uma pequena gambiarra adaptação:

request.setAttribute("ProdutoController", "ProdutoController");

E pronto, o código que queríamos funciona!

${linkTo[ProdutoController].adiciona} => /produtos !

Agora é só gerar esses mapas automaticamente, um para cada controller do VRaptor. Mas antes de fazer isso, existe um problema: a solução até aqui não suporta todas as rotas possíveis no VRaptor.

E se tivéssemos:

@Resource
public class ProdutoController {
    @Get("/produtos/{id}")
    public Produto visualiza(Long id) {...}
 
    @Delete("/produtos/{produto.id}")
    public void remove(Produto produto) {...}
}

Como fazer para gerar as URIs /produtos/4, /produtos/15, etc ainda sem Tags ou ELResolvers?
Como fazer esse tipo de código funcionar?

<c:forEach items="${produtos}" var="produto">
   <a href="${linkTo[ProdutoController].visualiza ??? produto.id ???}">Visualiza</a>
   <a href="${linkTo[ProdutoController].remove ??? produto ???}">Remove</a>
</c:forEach>

E agora, como vocês fariam isso? No próximo post eu explico o resto da solução, com um pouco mais de magia negra e detalhes internos do VRaptor que podem te ajudar a customizar várias coisas de forma bem fácil.

Jun
17

Anatomia de uma solução: VRaptor – linkTo para jsp – parte 1

Uma das desvantagens de usar VRaptor é que sempre temos que digitar as URIs duas vezes – no controller e na view:

@Resource
public class ProdutoController {
    @Post("/produtos/{id}")
    public void visualiza(Long id) {...}
}
<a href="<c:url value="/produtos/${produto.id}"/>">${produto.nome}</a>

Assim, durante o desenvolvimento você precisa lembrar qual é a URI correta do método, sem garantia nenhuma de que ela é a certa – se por algum motivo mudarmos o path do método visualiza todos os links para ele quebram silenciosamente.

Para quem programa em Rails, existe uma solução para isso, um helper que gera os links dado um controller e um método:

<% link_to "Um produto", :controller => "produtos", :action => "visualiza", :id => 4 %>

Num projeto que eu estou desenvolvendo em VRaptor + Scala, com o Scalate como template engine, conseguimos chegar nisso:

${linkTo[ProdutoController](_.visualiza(3))}  => /produtos/3

E ainda ganhando checagem estática: o template não vai compilar se não existir o método visualiza em ProdutoController, que recebe um número como parâmetro. Isso é possível porque no ssp conseguimos executar qualquer método scala, com a sintaxe padrão do scala.

Mas será que algo parecido com isso é possível com JSP?

O primeiro problema é que usando a EL padrão do JSP você não pode executar qualquer método de um objeto, somente getters. Executar métodos só criando uma Tag, ou sobrescrevendo o ELResolver – ambas soluções bastante trabalhosas.

Semana passada eu estava pareando com o Otávio Garcia, que é um grande contribuidor do VRaptor e estava visitando a Caelum, e a gente resolveu tentar encontrar uma solução para isso.

Chegamos nessa solução, sem criar tags nem sobrescrever o resolver:

${linkTo[ProdutoController].visualiza[3]}

Será que isso funciona? Será que é possível fazer isso retornar a URI “/produtos/3″?

Tem idéia de como fazer isso funcionar? Como seria? No próximo post eu explico qual foi a nossa solução, com pitadas de magia negra e técnicas interessantes. ;)

Jun
10

Hello world!

Finalmente criando meu blog =)

Pretendo falar sobre soluções legais que surgiram a partir de problemas que eu resolvi, e sobre discussões que aconteceram.

Let’s get started!