Em meus dois anterior blog Nas postagens, apresentei o Ratpack e a camada ODM leve disponível em nosso Java SDK. Esta postagem se baseará neles e apresentará uma API REST para gerenciar usuários por meio do repositório do Couchbase.
Quero que minha API seja compatível com os quatro verbos HTTP básicos e com o tipo de conteúdo. Tudo foi pensado para isso no Ratpack. O Cadeia é como um construtor para compor manipuladores. Então, escrevi um novo objeto Ação para vincular uma cadeia ao prefixo de URL 'user'. Isso simplificará bastante meu método principal:
|
1 2 3 4 5 6 |
público estático vazio principal(Cordas... argumentos) lançamentos Exceção { RxRatpack.inicializar(); RatpackServer.iniciar(servidor -> servidor.registro(Guice.registro(b -> b.módulo(Configuração.classe))) .manipuladores(cadeia -> cadeia.prefixo("usuário", UserHandler.classe))); } |
Para garantir que minha instância UserHandler possa ser encontrada, também preciso adicioná-la ao meu módulo Config Guice:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
público classe Configuração se estende AbstractModule { protegida vazio configurar() { CouchbaseCluster cc = CouchbaseCluster.criar(); Balde balde = cc.openBucket(); vincular(AsyncBucket.classe).toInstance(balde.assíncrono()); vincular(AsyncRepository.classe).toInstance(balde.repositório().assíncrono()); vincular(UserHandler.classe); vincular(UserRenderer.classe); } } |
Também adiciono a classe UserRenderer que será usada para lidar com os diferentes tipos de conteúdo.
Renderizador personalizado
No Ratpack, você pode registrar novos RendererSupport classes. Seu objetivo é especificar uma maneira de renderizar seu objeto T com base na classe Contexto. No meu caso, quero renderizar um objeto User com base no tipo de conteúdo do contexto. O objeto Context fornece a você um porConteúdo que permite compor a renderização com base no tipo de conteúdo definido na solicitação. Os tipos usuais já estão predefinidos. No meu caso, quero apenas oferecer suporte a json e texto:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
público classe UserRenderer se estende RendererSupport<Usuário> { @Substituir público vazio renderizar(Contexto contexto, Usuário usuário) lançamentos Exceção { contexto.porConteúdo(porContentSpec -> porContentSpec .json(() -> contexto.renderizar(Jackson.json(usuário))) .plainText(() -> contexto.renderizar(usuário.toString())) ); } } |
A renderização de texto é apenas um simples toString(). A versão JSON usa o objeto Jackson disponível por padrão no Ratpack. Ele lhe dá acesso a um atalho para conversões JSON/Objeto. Uma chamada para o método json fornecendo o objeto User como argumento é suficiente. O Jackson também é usado internamente pelo nosso Java SDK. Mas há um truque. A anotação @Field que usei no meu ODM do Couchabse não é captada pelo mapeador Jackson padrão usado no Ratpack. Portanto, preciso adicionar a anotação Jackson @JsonProperty para compensar. Eu não teria que adicionar nada se não tivesse usado a anotação @Field em primeiro lugar. Tentarei encontrar uma maneira melhor de fazer isso funcionar, mas, enquanto isso, está funcionando muito bem.
|
1 2 3 4 5 6 7 8 |
@Campo("fName") @JsonProperty("fName") privado Cordas firstName; @Campo("lName") @JsonProperty("lName") privado Cordas lastName; |
Composição da API com manipuladores
Agora estou em uma boa posição para começar a trabalhar na API. GET, PUT e DELETE precisam de um ID de usuário para funcionar. Portanto, a primeira coisa que estou fazendo é verificar se há algo após o URL /user/. Os manipuladores são todos encadeados e executados na ordem em que você os declara. Quando você entra em um manipulador, a cadeia é interrompida. Portanto, você deve certificar-se de declarar /user/:userId antes de /user/. A vinculação de caminho usa regex, e você encontrará exemplos na seção Cadeia documentação.
Chamando o caminho permite que eu forneça o regex do caminho e um manipulador como argumento. No manipulador, começo obtendo o repositório do Couchbase e o userId dos tokens de caminho. Em seguida, chamo o método porMétodo para definir uma função para cada verbo HTTP que preciso suportar. Aqui, retornarei um usuário para GET, atualizarei ou criarei um usuário para PUT e removerei o usuário para REMOVE.
O verbo mais interessante aqui é o PUT, pois ele requer conteúdo da solicitação. O contexto analisar recebe um Analisar como argumento e retorna uma Promise. Aqui eu quero analisar o JSON da solicitação e mapeá-lo para um objeto User. Portanto, ele retorna uma Promise. Como sou um usuário do RxJava, mapearei essa Promise para um Observable e, em seguida, mapearei o objeto User para um EntityDocument para finalmente salvá-lo no repositório do Couchbase. Em seguida, converto esse observável novamente em uma promessa do ratpack e envio de volta uma string OK simples. Talvez você queira fazer algo mais inteligente em casos reais :)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
cadeia.caminho(":userId", ctx -> { AsyncRepository repo = ctx.obter(AsyncRepository.classe); Cordas userId = ctx.getPathTokens().obter("userId"); ctx.porMétodo(methodSpec -> { methodSpec.obter(() -> { Observável<EntidadeDocumento<Usuário>> usuário = repo.obter(userId, Usuário.classe); RxRatpack.promessa(usuário).então(u -> ctx.renderizar(u.obter(0).conteúdo())); }).colocar(() -> { Promessa<Usuário> promessa = ctx.analisar(Jackson.fromJson(Usuário.classe)); Observável<EntidadeDocumento<Usuário>> observável = RxRatpack.observar(promessa) .mapa(usuário -> EntidadeDocumento.criar(usuário)).flatMap(doc -> repo.upsert(doc)); RxRatpack.promessa(observável).então(doc -> ctx.renderizar("OK")); }).excluir(() -> { Observável<EntidadeDocumento<Usuário>> usuário = repo.remover(userId, Usuário.classe); RxRatpack.promessa(usuário).então(u -> ctx.renderizar("OK")); }); }); }); |
Quando os verbos HTTP que precisam de um userId forem implementados, poderei concluir todos os outros. E posso fazer isso facilmente com o método todos. Aqui eu obtenho o repositório e o bucket do registro. Em seguida, para o método GET, como ele não tem identificação de usuário, retorno a lista completa de usuários. Para isso, executo uma consulta N1QL básica. Por fim, o método POST é idêntico ao método PUT.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
cadeia.todos(ctx -> { AsyncBucket balde = ctx.obter(AsyncBucket.classe); AsyncRepository repo = ctx.obter(AsyncRepository.classe); ctx.porMétodo(methodSpec -> { methodSpec.obter(() -> { N1qlQuery consulta = N1qlQuery.simples("SELECT Meta().id as username,age,fName,lName FROM default WHERE type = 'user'"); Observável<Usuário> observável = balde.consulta(consulta) .flatMap(qRes -> qRes.linhas()).mapa(fila -> { JsonObject jo = fila.valor(); Cordas nome de usuário = jo.getString("nome de usuário"); Inteiro idade = jo.getInt("idade"); Cordas fName = jo.getString("fName"); Cordas lNome = jo.getString("lName"); retorno novo Usuário(nome de usuário, idade, fName, lNome); }); RxRatpack.promessa(observável).então(usuários -> ctx.renderizar(Jackson.json(usuários))); }).postagem(() -> { Promessa<Usuário> promessa = ctx.analisar(Jackson.fromJson(Usuário.classe)); Observável<EntidadeDocumento<Usuário>> observável = RxRatpack.observar(promessa) .mapa(usuário -> EntidadeDocumento.criar(usuário)).flatMap(doc -> repo.upsert(doc)); RxRatpack.promessa(observável).então(doc -> ctx.renderizar("OK")); }); }); }); |
Essa pode não ser uma API totalmente RESTFUL, mas deve lhe dar uma boa ideia de como é fácil criar uma com o Ratpack, o Couchbase e o RxJava. Se você quiser ver mais coisas acontecendo com o Ratpack, como um módulo do Couchbase, entre em contato comigo.