Darter: RESTful APIs em Dart

Lembram da última publicação que escrevi falando sobre as ferramentas disponíveis em Dart para criar APIs RESTful? Eu reclamei de algumas características que essas ferramentas não tinham, certo? E como resolver isso? Uma forma seria contribuir para o Redstone, por exemplo. Só que, infelizmente, não dá para modificar o Redstone para permitir o controle de versionamento de APIs, conforme você pode ver nessa issue que abri lá. Outra forma seria trabalhar em cima do RPC da Google. Só que nesse eu não abri issues e nada. hehe. Resolvi logo partir para a violência: criar uma biblioteca própria, considerando não cometer as mesmas falhas que encontrei nessas duas.

O meu guia de referência para essa biblioteca foi o Grape, para Ruby. Em minha opinião, é o framework, para desenvolver APIs RESTful, mais completo e simples de ser usado, independente de linguagem. Uso ele para todas as minhas APIs. Entretanto, é óbvio que não pude criar uma DSL em Dart, já que essa linguagem não dá facilidades para fazer isso da mesma forma como Ruby. Precisei usar a maneira Dart de codificar. Acho que o resultado ficou bom!

Darter

Apresento-lhes o Darter! Meu primeiro projeto open-source para Dart. Ele está servindo para muitos propósitos: estou aprendendo a fundo a linguagem, estou me divertindo pacas e estou contribuindo! O Darter ainda não está em versão final. Precisa de melhorias e correções. Como ainda estou na fase de codificação para provar meu conceito, ainda não escrevi os testes. E, por favor, deixa de #mimimi se isso é certo ou errado, preferi primeiro validar minha ideia para depois partir para tornar a ferramenta mais madura, com testes, exemplos e etc.

Mas, já é possível usá-lo? É sim, companheiro. O Darter já está no Github mas ainda não publicado no Pub. Inclusive, estou escrevendo todo um projeto em cima dele e que também está no Github aqui. Trata-se do meu “projeto-prova-de-conceito”. Ele está totalmente funcional! Agora, vamos ver como funciona o Darter?

 

Exemplo de Uso

O primeiro passo é criar uma classe e anotá-la com @API, assim como fizemos no exemplo abaixo. Observe que esta anotação precisa do argumento path. No caso abaixo, teremos uma rota da seguinte forma: http://localhost/categories.

import 'package:darter/darter.dart';
@API(path: 'categories') 
class MyDarterAPI { }

Depois disso, você já pode iniciar a criação de seus métodos que irão tratar as diversas requisições. Para isso, precisamos usar as anotações @GET@POST@PUT@DELETE e @PATCH. Vamos incrementar a classe acima para refletir isso!

import 'package:darter/darter.dart';

@API(path: 'categories')
class MyDarterAPI {

    @GET()
    List<MyModel> list() {
    }

    @GET(path: ':id')
    MyModel get(Map pathParams) {
    }

    @PUT(path: ':id')
    MyModel put(PathParams params, MyModel myModel) {
    }

    @POST()
    MyModel post(MyModel myModel) {
    }

    @DELETE(path: 'id')
    void delete(Map pathParams) {
    }
}

Observe que omiti o corpo dos métodos, pois meu objetivo é só exemplificar o Darter. Como era de se esperar, com essa classe aí, teremos as rotas:

  • GET http://localhost/categories
  • POST http://localhost/categories
  • PUT http://localhost/categories/<id>
  • GET http://localhost/categories/<id>
  • DELETE http://localhost/categories/<id>

Parâmetros

No exemplo que vimos na seção anterior, note que recebemos como parâmetro um objeto MyModel em alguns casos, enquanto em outros, recebemos um Map. No primeiro caso, o Darter tenta transformar os parâmetros encontrados em uma classe de negócio da sua aplicação. Por exemplo, suponha que você envia uma String JSON para o Darter e seu método tem como parâmetro um objeto de negócio. O Darter vai criar esse objeto e preencher seus atributos com os valores obtidos do JSON.

Mas, é claro, isso não abrange todos os cenários, certo? Claro que não. Existem os Path parameters e Query parameters. E como ter acesso a eles? Através de uma convenção bem simples: crie um parâmetro do tipo Map e dê o nome de pathParams ou queryParams. Você pode me perguntar porque não fiz igual ao JAX-RS (Java), que tem anotações @PathParam e @QueryParam. Simplesmente porque eu acho essa solução feia, medonha. Polui seu código.

Em minha humilde opinião, é muito mais simples e clean você receber um Map contendo os dados. Rápido, caceteiro, simples e eficiente.

Versionamento

Opa! Você não reclamou tanto de versionamento nas outras ferramentas? Sim! E fiz isso de forma mais flexível no Darter! Vamos melhorar a classe anterior (vou omitir agora os métodos só para exemplificar o versionamento).

@API(path: 'categories')
@Version(version: 'v1', vendor: 'company', format: Format.JSON, using: Using.HEADER)
class MyDarterAPI { }

O Darter permite usar dois tipos de estratégias de versionamento: por path e por header. No caso do header, exemplificado acima, você precisa usar a anotação @Version e informar quatro argumentos obrigatórios. Por que? Porque o Darter vai procurar por isso no cabeçalho Accept. Como? Ele espera que esteja no formato application/vnd.<vendor>.<version>+<format>. Entendeu agora o motivo dos argumentos extra? No caso acima, ele espera o cabeçalho formatado assim application/vnd.company.v1+json.

E caso você queira o versionamento no path? Eu não aconselho essa estratégia, mas ela existe e eu não poderia deixar ela de fora. Neste caso, basta modificar a anotação e esquecer os parâmetros extras.

@API(path: 'categories')
@Version(version: 'v1', using: Using.PATH)
class MyDarterAPI { }

Nesta estratégia, você terá a URL http://localhost/v1/categories.

Error Handlers

Suponha que seu método recebe um ID e você vai buscar esse ID na base de dados. Seu mecanismo de busca não encontra e lança uma exceção chamada ObjectNotFoundException. O que sua API deve fazer com essa exceção? O correto é pegar essa exceção e retornar na API um status 404 e uma mensagem de erro! O Darter facilita isso? Claro!

@API(path: 'categories')
@Version(version: 'v1', using: Using.PATH)
class MyDarterAPI { 
    
    @ErrorHandler
    Response handleObjectNotFound(ObjectNotFoundException ex) {
    }

}

A partir deste método, você pode retornar um objeto Response ou qualquer outro objeto, inclusive seus models. Observe que o parâmetro do método deve ser exatamente o tipo da exceção que você quer tratar. E caso você tenha mais de um handler com o mesmo parâmetro? O Darter pega o primeiro e ignora os outros, então, tome cuidado.

Interceptors

Agora, imagine outra situação: você precisa validar antes se o usuário tem permissão de acesso à sua API! Como fazer isso? Com interceptors, meu caro. Um interceptor, como o próprio nome já diz, intercepta uma requisição para que você possa tratar ela. Essa interceptação pode ocorrer antes ou depois que seu método seja chamado. Ah! E você também pode definir a ordem de execução dos interceptors! Vamos deixar de blá blá blá e ver um exemplo?

@Interceptor(when:Interceptor.BEFORE, priority: 1)
class Authentication {

    void intercept(Chain chain) {
        String token = chain.request.headers["X-Token"];
        if (token != "Test") {
          chain.abort(new Response()
            ..body = "{\"error\":\"Permission Denied\"}"
            ..statusCode = 401);
        }
    }
}

Você deve primeiro criar uma classe e anotá-la com @Interceptor, fornecendo dois parâmetros: quando ele será executado e sua prioridade. Em seguida, e isso é uma convenção, crie um método chamado intercept que recebe o parâmetro Chain. O objeto chain lhe dá acesso apenas ao Request, quando é um interceptor BEFORE, e acesso ao Request e Response quando é um interceptor AFTER.

Iniciando

Para fechar o exemplo, só precisamos iniciar o servidor, né? E isso é fácil! Basta criar uma instância de DarterServer, adicionar suas APIs e interceptors e mandar bala com start().

main() {
  new DarterServer()
    ..addApi(new MyDarterAPI())
    ..addInterceptor(new Authentication())
    ..start();
}

Vai me perguntar porque não fiz reflexão para encontrar as classes anotadas? Achei o mecanismo de mirrors do Dart muito massa, mas um pouco complicado de escanear o classpath de sua própria aplicação em busca das classes. Achei oneroso e propenso a erros. Até mesmo o RPC, do próprio Google, não faz isso. Então, resolvi ir na mesma onda deles! 🙂

Conclusão

Gostou? Achou a ideia interessante? Agora vem a parte mais legal: o projeto ainda está em estágios iniciais e, portanto, sua ajuda não é só permitida, é altamente desejada. Não precisa ser necessariamente com código, mas com sugestões de melhorias, testes e etc. Vamos lá? Manda suas dicas nos comentários!

Ah! Por último, porque o nome Darter? Em primeiro lugar, é óbvio, é pelo trocadilho com o nome da linguagem. Mas só isso não bastaria, eu também quis salientar a leveza e liberdade de um pássaro para voar da forma como quiser. Dois princípios que pretendo manter na biblioteca! O pássaro é esse da foto do post!

Dart: Syntax Sugar!

Que Dart é bacana, isso todo mundo sabe, certo? Mas, por que Dart é bacana mesmo? Um dos motivos é o que chamamos de syntax sugar. O que é isso? São facilitadores que a linguagem traz para que você faça tarefas muito comuns de forma mais fácil, com menos linhas de código. Algumas pessoas acham que syntax sugar é para os fracos. Outros dizem que é pura frescura. Ah vai, para! Se uma linguagem me dá uma forma de fazer uma coisa com menos trabalho, é claro, óbvio, cristalino, inteligente e outros mil bons adjetivos que eu quero usar isso!

Então, vamos começar falando sobre os syntax sugar que o Dart fornece para…

Construtores

Imagine, por exemplo, que você queira criar um construtor que recebe dois parâmetros e que atribui esses dois parâmetros para dois atributos que tem o mesmo nome. Trata-se de uma tarefa bem comum, certo? Então, porque não facilitar? Dart facilita. Vejamos como:

class Pessoa { 
   String nome; 
   String endereco; 

   Pessoa(this.nome, this.endereco); 
}

Pessoa marlon = new Pessoa("Marlon", "Rua dos Dartisans");

Lindo, não é? Observe que sua classe em Dart tem dois atributos e apenas um construtor, que recebe como parâmetro o nome e o endereço e já atribui o valor destes dois parâmetros para os atributos. Outro detalhe importante: essa atribuição ocorre antes de o corpo do construtor ser executado! Mas, peraí, e se eu precisar colocar um código no corpo desse construtor? Sem problemas.

class Pessoa { 
   String nome; 
   String endereco; 

   Pessoa(this.nome, this.endereco) {
      if(this.nome == "Marlon") {
         this.nome = "Marlon S. Carvalho";
      }
   }
}

Pessoa marlon = new Pessoa("Marlon", "Rua dos Dartisans");

Agora, e se sua classe herda de uma classe-pai e você precisa chamar o construtor do pai? Muito simples, meu caro, pois basta colocar um logo depois do construtor e chamar o construtor do pai. Observe também que você pode continuar usando a atribuição que vimos no exemplo anterior. Olha esse exemplo:

class Pessoa { 
   String nome; 
   String endereco; 

   Pessoa(this.nome, this.endereco);
}

class PessoaInteligente extends Pessoa {
    double qi;

    PessoaInteligente(nome, endereco, this.qi): super(nome,endereco) {
    }
}

Operador em Cascata

O operador em cascata é outro syntax sugar bem bacana. Quem nunca se deparou com a situação em que você precisa chamar diversos métodos de um objeto em sequência? Isso sempre acontece, né não? Principalmente quando você está inicializando um model ou entity e atribuindo aos seus atributos valores oriundos de um formulário HTML.

Pessoa pessoa = new Pessoa();
pessoa
    ..nome = "Marlon"
    ..endereco = "Rua dos Dartisans"
    ..save();

Métodos Assíncronos

Esta é bem recente e eu o considero como um syntax sugar também. Para você entender bem esse syntax sugar, lembre-se de como você trata requisições assíncronas com Javascript. Lembrou? Horrível, certo? Em Javascript temos o conhecido callback hell. Você precisa encadear um monte de funções de callback para atender a duas ou três chamadas assíncronas. Algo assim:

funcao_assincrona("param1", function(resultado) {
    funcao_assincrona2(resultado, function(resultado2) {
        funcao_assincrona3(resultado2, function(resultado3) {
            // Enfim, podemos fazer algo!
        });
    });
});

Meu amigo, esse código aí é feio demais! Nas primeiras versões do Dart, você também precisava fazer algo parecido com isso. A única diferença é que nós tínhamos a classe Future, mas a ideia era a mesma. E como o Dart resolveu isso? Com a biblioteca dart:async. Olha como as coisas agora ficaram lindas!

var resultado1 = await funcao_assincrona("param1");
var resultado2 = await funcao_assincrona2(resultado1);
var resultado3 = await funcao_assincrona3(resultado2);

Muito melhor, não? Com await, você programa quase como se estivesse programando um código síncrono. Quer saber mais sobre isso? Clica aqui.

Finalizando

Você acha que esqueci de citar alguma outra syntax sugar bacana do Dart? Então deixa um comentário! Eu adicionarei sua sugestão no artigo e com a devida citação para o autor.

RESTful APIs com Dart

Já ouviu falar de Dart? É uma linguagem bem bacana, embora ainda ache ela um pouco “verbose”, criada pelo Google. A linguagem lembra um pouco Java, mas adiciona features que são extremamente interessantes e que facilitam substancialmente nossa vida. Alguns desenvolvedores torcem o nariz para Dart por sua semelhança com Java e Javascript, outros a adoram, exatamente por ter essa semelhança mas incluir features que o Java carece.

Qual meu lado? Eu acho Dart massa! Principalmente por eu ter programado em Java por tantos anos. Entretanto, Ruby ainda é minha linguagem favorita! Imbatível! 🙂 Mas, eu não vim aqui para exaltar nem Java, nem Ruby e nem Dart. Eu estou escrevendo esse post para tratar sobre três frameworks escritos em Dart que podem facilitar a vida de quem deseja criar uma API RESTful usando essa linguagem.

Algumas das vantagens de criar uma API com Dart é que a nuvem do Google está preparada para o Dart e a linguagem inclui features bem interessantes para isso. Até o momento, encontrei três ferramentas para essa tarefa: Redstone.dart, RPC (do Google) e Shelf Rest. São três boas ferramentas, mas, como veremos a seguir, carecem de algumas funcionalidades interessantes que encontramos em frameworks como o Grape, para Ruby.

Alerto que eu não fiz um estudo exaustivo sobre essas ferramentas. Fiz pequenos exemplos e tentei implementar algumas features que sempre busco em ferramentas como essas. É capaz de existirem outros problemas mas com os quais eu não me deparei. Isto pode ter acontecido porque meus exemplos eram simples, bem básicos e quando eu encontrava dois ou três problemas que, na minha humilde opinião, são falhas graves para a construção de APIs, eu parava de fuçar.

Antes de começarmos a falar sobre essas ferramentas, eu sugiro assistir o vídeo acima. Eu descobri essas ferramentas através desse vídeo. É extremamente indicado para quem quer começar a desenvolver APIs em Dart!

Redstone.dart

O Redstone.dart foi escrito por um brasileiro chamado Luiz Mineo. O Luiz esteve à frente do Redstone até a versão 0.5 mas não pôde continuar seu desenvolvimento e agora o Redstone está nas mãos de abnegados desenvolvedores que já estão trabalhando em melhorias e reformulações profundas. A nova versão, 0.6, já está em Beta e aparentemente, próxima de ser lançada.

A principal questão é que o Redstone não foi feito especificamente para criar APIs RESTful. Com ele você pode criar rotas e tratar essas rotas usando os principais métodos HTTP de forma muito, mas muito fácil mesmo! Eu criei uma API simples nele em 10 minutos! Mas, então, o que falta nele? Vamos enumerar:

  1. Não permite alterar dinamicamente o status code. Desta forma, você não consegue mudar de 200 para 201 em um PUT que cria um recurso. Eu já relatei uma issue no Issue Tracking deles e um dos desenvolvedores considerou a feature necessária. Ou seja, é provável que eles implementem isso no futuro. Embora eu esteja avaliando a possibilidade de mandar um Pull Request! 😛
  2. O Redstone não tem uma forma fácil de tratar a versão da sua API. Para APIs, isto é importantíssimo, pois APIs sempre evoluem e não dá para jogar a versão anterior fora e colocar uma nova de supetão.
  3. Respostas Parciais. Também não tem nenhum mecanismo que facilite construir respostas parciais. Isto teria que ser implementado “na mão”.
  4. Não retorna os status “padrão” para cada método HTTP. Por exemplo, é esperado que o método POST retorne o status 201 CREATED. O Redstone não retorna automaticamente o 201, no lugar, ele retorna 200. 

RPC

RPC, diferente do Redstone, foi feito para criar APIs RESTful. Esse era o objetivo quando criaram ele. O RPC foi criado pelo Google e foi a escolhida pelo cara do vídeo que postei no início do post. Mas, infelizmente, tem uma coisa que me irritou nela. O RPC inclui a versão da API na URI. Não encontrei outra forma. Eu prefiro, e acho mais elegante, tratar a versão no cabeçalho ACCEPT. Eu uso a URI apenas para endereçar recursos. Somente!

Sendo uma biblioteca feita essencialmente para construir APIs RESTful, eu esperava encontrar maior flexibilidade com relação a isso. Outro problema que encontrei foi a impossibilidade de alterar o status code dinamicamente. Procurei na documentação como fazer isso, mas não encontrei. Dei uma leve fuçada no código, mas também não encontrei.

Shelf RPC

Como o nome sugere, o Shelf Rest é um adendo ao Shelf para criar serviços RESTful. Esta foi a única ferramenta que eu não testei profundamente. Só dei uma fuçada na documentação, então, peço desculpas caso eu esteja falando merda aqui! 😛 Do que pude observar, ele carece de todos os problemas que citei para o RPC e o Redstone adicionado de mais um: não encontrei como criar rotas usando o método PATCH. Estranho, hein?

Outra coisa que achei bem estranha: eles definem anotações para os métodos HTTP: @Get, @Post, @Put e @Delete. Só que inventaram uma anotação @AddAll que está na mesma categoria das outras. Oi? Semanticamente, não faz o mínimo sentido, embora a funcionalidade pareça interessante.

Conclusão

Você deve estar se perguntando: maluco, você indicaria algum desses frameworks? Eu sou meio xiita nesse negócio de APIs REST, então, tenderia a dizer não. Mas, eu acredito que é possível sim criar boas APIs RESTful usando o Redstone. Pareceu a biblioteca mais promissora e com maior flexibilidade. Eles tem um mecanismo de plugin que achei interessante, pois dá para, por exemplo, criar um mecanismo para retornar Partial Responses.

Ou seja, até aqui eu indicaria o Redstone. A outra pergunta que me fariam é: você acha que Dart está pronto para construir APIs RESTful? Minha resposta é que a linguagem está sim. Ainda não temos um framework tão interessante como é o Grape em Ruby. Uma coisa não invalida a outra. Dart é sim interessante para essa tarefa, mas ainda precisamos de ferramentas mais adaptadas para isto. Então, vamos construir uma? Vamos ajudar o pessoal do Redstone? Ou criar um plugin lá?

Eu vou! 😛