Refatorando para Fluent Interface

Ultimamente tenho passado boa parte do meu tempo trabalhando numa aplicação chamada WebMediaAPI. Trata-se de uma… ahm… API de uso interno que tem como objetivo padronizar, centralizar e facilitar o acesso à mídias e seu consumo em sites da Globo.com.

Por exemplo, quando o pessoal do G1 quiser colocar vídeos no seu site, ao invés de dar vários SELECTs em tabelas que eles não entendem e não conhecem, a idéia é que eles possam usar um JAR na aplicação deles que encapsula várias funcionalidades oferecidas pela nossa infraestrutura de WebMedia, simplificando o trabalho deles (pois não terão que descobrir como inventar a roda) e o nosso (centralizando e organizando o consumo de mídias na empresa).

No início do projeto um dos maiores desafios foi estabelecer como seria a fachada desta API. Nós tentamos alguns formatos e como precisávamos lançar logo a primeira versão acabamos optando por uma interface “tradicional” e simplificada. Para ter uma idéia melhor, veja o código para selecionar os últimos vídeos publicados do programa “Altas Horas”:

int quantidade = 5;
long programaId = 456;
Set midias = new HashSet();
 
WebMediaServices webMediaServices = WebMediaFactory.getServices();
List idsMidias = webMediaServices
	.getIdsUltimasMidiasPublicadasPorPrograma(quantidade, programaId);
 
for (Iterator iter = idsMidias.iterator(); iter.hasNext();) {
   Long midiaId = (Long) iter.next();
   Midia midia = webMediaServices.getMidia(midiaId.longValue());
   midias.add(midia);
}

Não é muito difícil entender o funcionamento deste código… Só que ele poderia ser muito melhor!

Esta interface pode até funcionar mas não é nem um pouco intuitiva. A classe WebMediaServices como pode-se imaginar ficou com dezenas de métodos e virou basicamente um grande saco de funcionalidades. Qualquer método simplesmente ficava nesta classe – o que não é nem um pouco elegante.

Depois de algum tempo tive a idéia de refatorar a API para uma Fluent Interface. A idéia é tentar fazer algo que se assemelha a uma DSL interna, que não é nada mais do que uma API com nomes interessantes. Ao invés de um saco de métodos a WebMediaAPI agora é acessada através de uma interface semânticamente organizada e seu design é pensado para ser legível e… fluente!

Veja como ficou o novo código para selecionar os últimos vídeos publicados do programa “Altas Horas” (exatamente a mesma coisa que o código anterior faz):

Long altasHoras = new Long(456);
Set videos = WebMediaAPI.videos().recentes().doPrograma(altasHoras);

Bem mais elegante!

O lado negativo é que quanto mais fácil a API torna-se para o cliente mais difícil torna-se sua implementação. Construir uma fluent interface muitas vezes me fez perder algumas horas pensando como certas coisas seriam feitas, mas eu gostei do resultado final e acho que para esse tipo de aplicação valeu a pena.

Para mostrar a diversidade e simplicidade da nova API, veja mais alguns exemplos (repare que eu não tenho que dizer nada sobre o que eles fazem para você entendê-los):

// exemplo 1
Set programas = WebMediaAPI.programas().comTitulo("Fantastico");
 
// exemplo 2
Long multishow = new Long(123);
Set videos = WebMediaAPI.videos().favoritos().doCanal(multishow);
 
// exemplo 3
Long destaquePrincipalGloboVideos = new Long(123);
Integer quantidadeMaxima = new Integer(10);
Set videos = WebMediaAPI.videos().relacionados().aoCanal()
	.doVideo(destaquePrincipalGloboVideos, quantidadeMaxima);

Ainda temos um longo caminho pela frente e muita coisa ainda será melhorada, mas já dá para perceber uma difirença significativa entre as duas versões.

Tags: , , , , , , ,

19 Responses to “Refatorando para Fluent Interface”

  1. Bruno Andrade says:

    parabéns!!

    ficou bastante claro e objetivo(lembra mesmo uma dsl),
    excelente ideia.

    só uma duvida, “programas” poderia ser um objeto? em vez de um numero apenas?
    ex:

    Programa altasHoras = factoryPrograma.getprograma(new Long(123));
    Set videos = WebMediaAPI.videos().recentes().doPrograma(altasHoras);

    ou eu estou viajando??

  2. Não acho que você tá viajando não. Poderia ser sim um objeto Programa mas por alguns motivos decedí fazer com um Long. Aliás, esse é o assunto do meu próximo post que já está 50% escrito. Devo publicar na próxima segunda feira porque essa semana tá complicada. :)

    [ ]s, gc

  3. Frederico says:

    Realmente muito boa a forma como ficou o resultado final. Ultimamente tenho pensado bastante neste aspecto semântico tb nos códigos que escrevo. Afinal, dessa forma fica muito mais fácil entender o que o sistema tá fazendo e consequentemente fazer a manutenção.

    Parabéns!

  4. Eu gosto muito de interfaces fluentes, mesmo. Então vai aqui uma pergunta de advogado do diabo: Guilherme, e que tal

    Set videos = api.videosRecentesDoPrograma(altasHoras);

    ???

  5. Marcos,

    Dessa forma que você propôs você vai ter o mesmo saco de métodos de antes.

    Se você fosse implementar todas as variações da minha Fluent Interface dessa sua forma eu chuto que sua classe teria uns 150 métodos (enquanto eu não tenho nenhuma classe com mais de 5 métodos).

    Além disso os nomes ficam super toscos. Você pegou um exemplo fácil mas olha esse aqui:

    Set videos = api.videosRelacionadosAoCanalDoVideoOrdenadoPorFavoritos(destaquePrincipalGloboVideos, quantidadeMaxima);

    Péssimo!

    Mas no fim das contas não existe jeito certo ou errado aqui nesse caso. As duas formas são válidas e fazem exatamente a mesma coisa. O caso é que a fluent interface é muito mais elegante.

    [ ]s, Guilherme

  6. Marcos,

    Dá uma lida no artigo do Martin Fowler (http://martinfowler.com/bliki/FluentInterface.html) pra ver o exemplo que tem lá.

    O que é melhor, isso:

    private void makeNormal(Customer customer) {
    Order o1 = new Order();
    customer.addOrder(o1);
    OrderLine line1 = new OrderLine(6, Product.find(”TAL”));
    o1.addLine(line1);
    OrderLine line2 = new OrderLine(5, Product.find(”HPK”));
    o1.addLine(line2);
    OrderLine line3 = new OrderLine(3, Product.find(”LGV”));
    o1.addLine(line3);
    line2.setSkippable(true);
    o1.setRush(true);
    }

    ou isso:

    private void makeFluent(Customer customer) {
    customer.newOrder()
    .with(6, “TAL”)
    .with(5, “HPK”).skippable()
    .with(3, “LGV”)
    .priorityRush();
    }

    Como já falei antes, os dois fazem a mesma coisa mas um é muito mais semântico que o outro.

    [ ]s, Guilherme

  7. Marcos Silva Pereira says:

    Guilherme,

    Já conhecia o texto do Fowler. Na verdade eu vivo implementando interfaces fluentes o tempo todo, há até uma classe aqui usada assim:
    List numeros = sorteador.de(1).ate(20).sortear(1000).maisRepetidos(6);

    Que é parte de um testes para candidatos a vagas aqui. Só que, claro, há casos em que Interfaces Fluentes não são necessárias. Como o seu caso possui uma variação muito grande combinações, parece perfeito.

    valeuz…

  8. [...] esse link há algum tempo e eu havia olhado superficialmente. Até que nessa história de fazer Fluent Interfaces e Domain-Driven Design acabei me lembrando que isso poderia ser [...]

  9. [...] de serem implementadas mas o impacto é muito positivo. Um exemplo real pode ser visto em um post que fiz há algumas semanas sobre uma Fluent Interface que implementei em um projeto, que trouxe vários benefícios [...]

  10. Andrik Albuquerque says:

    Olá Guilherme, estava lembrando deste post e me surgiu uma dúvida

    No exemplo você apresentou primeiro como era feito antes “getIdsUltimasMidiasPublicadasPorPrograma” e mostrou como ficou depois
    “WebMediaAPI.videos().recentes().doPrograma(altasHoras)”. Sei que o post é sobre interfaces fluentes, mas estou com uma dúvida sobre a recuperação dos dados.
    Gostaria de saber como foi implementado a recuperação dos dados em ambas as abordagens, por exemplo, imagino que no primeiro método tenha uma consulta que faz vários joins entre as tabelas e devolve o resultado, já no segundo você teria consultas pequenas para cada um dos métodos, foi essa a abordagem adotada?

    []’s

    Andrik Albuquerque

  11. Oi Andrik.

    Pense nessa DSL/Fluent Interface como uma camada POR CIMA da API antiga. Ou seja, o funcionamento interno é exatamente o mesmo, só o que muda é a forma que a aplicação é chamada. Você pode considerar essa minha Fluent Interface como uma “Façade” para o resto do sistema.

    Dê uma olhada nos posts recentes no meu blog que estou postando algumas coisas sobre DSLs. Dá uma olhada no material e se voce continuar com dúvida me manda uma mensagem e eu tento ajudar mais.

    Araços,
    Guilherme

  12. Edson Watanabe says:

    Respondendo ao Andrik, você pode implementar isso como sendo uma forma de preparar a consulta (ou seja, cada método adiciona um critério, mas não faz uma consulta em si), em vez de uma consulta simples que é sucessivamente filtrada pelas outras consultas.
    Por exemplo, você poderia ter algo que é menos fluente mas é mais fácil de implementar como

    Set programas = WebMediaAPI.programas().comTitulo(”Fantastico”).listar();

    onde o método “listar” executa a consulta que “programas”, “comTitulo” etc. preparam.

  13. Edson Watanabe says:

    Outra forma que também não é muito fluente pode ser:

    Set programas = WebMediaAPI.listar (new Programas(), new ComTitulo (”Fantastico”));

    onde os tais construtores adicionam critérios. Mas é sempre melhor que ter 200 métodos com nomes imensos e cada um deles tendo 40 parâmetros.

  14. Leandro says:

    Parabéns pelo post!
    O seu exemplo de fluent interface faz pessoas se interessarem mais pelo assunto.

  15. [...] alguns meses escreví um post sobre Fluent Interfaces, mostrando um trabalho que fizemos aqui na empresa para tornar nossa API interna mais fácil de se [...]

  16. [...] } O Guilherme explicou muito bem no blog dele sobre o assunto nesses posts. Vale a pena dar uma espiadinha. Posted in Domain-Driven Design. Etiquetas HTML:ddd, [...]

  17. [...] por falar em fluent interfaces, o Guilherme Chapiewski fez dois ótimos posts a alguns [...]

  18. [...] Post de Guilherme Chapiewski que clareou minhas idéias sobre Fluent Interface http://gc.blog.br/2007/09/25/refatorando-para-fluent-interface/ [...]

  19. Claudio Rosse says:

    Gulherme .. estou estudando alguns casos de construção de API, e achei muito interessante refatorar para Fluent Interface.

    Minha questão é se existe algumas outras tecnicas para o desenvolvimento de API e refatoração?

    Pergunto isso para analisar os casos e verificar a viabilidade na utilização em meus projetos.

Leave a Reply