No Couchbase Connect 2015, demonstramos um aplicativo de exemplo que usa o N1QL para consultar dados de um bucket de amostra do Couchbase.
Se você perdeu a conferência, não há problema. Vamos explicar como reproduzir esse aplicativo e conferir alguns dos destaques do Couchbase 4.0.
Pré-requisitos
- Apache Maven 3
- Kit de desenvolvimento Java (JDK) 1.7
- Servidor Couchbase 4.0
- IntelliJ IDEA 14.1+, Eclipse ou NetBeans. O IntelliJ IDEA será usado neste exemplo.
Criação de um novo projeto
Abra o IntelliJ IDEA e escolha criar um novo projeto Java, certificando-se de usar o JDK 1.7, se solicitado. Para fins deste guia, vamos chamar o projeto de try-cb-java.
Agora, clique com o botão direito do mouse try-cb-java em sua árvore de projetos e, em seguida, selecione Adicionar suporte a estruturas e selecione Maven. Isso adicionará um pom.xml em seu projeto.
Configuração do Maven
Dentro do pom.xml comece dando ao projeto um nome de grupo mais atraente:
|
1 2 3 |
<groupId>com.couchbase.exemplo</groupId> |
Em seguida, adicione o restante de nossas dependências ao arquivo, que inclui o Spring Boot, o cliente Couchbase e a estrutura de segurança do Spring.
|
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<pai> <groupId>org.estrutura de mola.inicialização</groupId> <artifactId>mola-inicialização-inicial-pai</artifactId> <versão>1.2.3.RELEASE</versão> </pai> <dependências> <dependência> <groupId>org.estrutura de mola.inicialização</groupId> <artifactId>mola-inicialização-inicial-web</artifactId> </dependência> <dependência> <groupId>org.estrutura de mola</groupId> <artifactId>mola-tx</artifactId> </dependência> <dependência> <groupId>org.estrutura de mola.segurança</groupId> <artifactId>mola-segurança-núcleo</artifactId> </dependência> <dependência> <groupId>com.couchbase.cliente</groupId> <artifactId>java-cliente</artifactId> <versão>2.2.0-dp</versão> </dependência> </dependências> <repositórios> <repositório> <id>couchbase</id> <nome>couchbase repo</nome> <url>http://files.couchbase.com/maven2 <instantâneos><habilitado>falso</habilitado></instantâneos> </repositório> </repositórios> <construir> <plugins> <plug-in> <groupId>org.estrutura de mola.inicialização</groupId> <artifactId>mola-inicialização-mentor-plug-in</artifactId> </plug-in> </plugins> </construir> |
Criação de um perfil de execução
No momento, se você tentar executar o aplicativo, ocorrerá um erro ou nada acontecerá porque não há nenhum perfil configurado no momento.
Na barra de ferramentas, selecione Run -> Edit Configurations (Executar -> Editar configurações) e escolha adicionar uma nova configuração do Maven. Você pode dar a ela o nome que quiser, mas é importante ter o seguinte no campo da linha de comando:
|
1 2 3 |
mola-inicialização:executar |
Para este artigo, nomearemos a configuração como Inicialização do Spring.
O IntelliJ IDEA agora deve estar pronto para o desenvolvimento.
Criação de índices no Bucket do Couchbase
Como este tutorial usa consultas N1QL, devemos primeiro adicionar índices ao nosso bucket do Couchbase Server. Isso pode ser feito facilmente por meio de código, mas, para este exemplo, vamos fazer um atalho e adicioná-los por meio do cliente Couchbase Query (CBQ) que é instalado automaticamente com uma instalação do Couchbase 4+ no Mac OS e no Windows.
No Mac OS, inicie o CBQ encontrado em /Applications/Couchbase Server.app/Contents/Resources/couchbase-core/bin/cbq e execute o seguinte:
|
1 2 3 |
CRIAR PRIMÁRIO ÍNDICE def_primary ON `viagens-amostra` USO gsi; |
No Windows, inicie o CBQ encontrado em C:/Arquivos de Programas/Couchbase/Server/bin/cbq.exe e execute o mesmo comando N1QL que é feito no Mac OS.
Criação de uma classe de aplicativo principal
A classe principal deste projeto será Application.java e pode ser criado clicando com o botão direito do mouse no ícone trycb da árvore do projeto e escolhendo Novo -> Classe Java.
Adicione o seguinte para colocar a classe em seu estado executável mais básico:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
pacote trycb; importação org.estrutura de mola.inicialização.SpringApplication; importação org.estrutura de mola.inicialização.autoconfigurar.SpringBootApplication; importação org.estrutura de mola.web.vincular.anotação.RequestMapping; importação org.estrutura de mola.web.vincular.anotação.RestController; @SpringBootApplication @RestController @RequestMapping("/api") público classe Aplicativo { público estático vazio principal(Cordas[] argumentos) { SpringApplication.executar(Aplicativo.classe, argumentos); } } |
Certifique-se de que o projeto seja executado sem erros, selecionando Executar -> Executar 'Spring Boot' na barra de ferramentas do IntelliJ IDEA.
Manipulação do compartilhamento de recursos entre origens (CORS)
Como a maior parte dos nossos testes será feita localmente, precisamos garantir que o CORS esteja ativado; caso contrário, o navegador da Web reclamará ao tentar acessar os pontos de extremidade da API com JavaScript.
Certifique-se de que a classe Application implemente a classe Filter e adicione o seguinte código:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Substituir público vazio doFiltro(ServletRequest req, ServletResponse res, Cadeia de filtros cadeia) lançamentos IOException, ServletException { HttpServletResponse resposta = (HttpServletResponse) res; resposta.setHeader("Access-Control-Allow-Origin", "*"); resposta.setHeader("Access-Control-Allow-Headers", "Origem, X-Requested-With, Content-Type, Accept"); cadeia.doFiltro(req, res); } @Substituir público vazio inicial(FilterConfig filterConfig) lançamentos ServletException {} @Substituir público vazio destruir() {} |
Configurar opções de cluster e de bucket do Couchbase
No momento, temos basicamente um aplicativo Spring Boot sem interação com o Couchbase. Incluímos o cliente Java via Maven, portanto, é hora de começar a usá-lo.
Adicione o seguinte à classe Application:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Valor("${hostname}") privado Cordas nome do host; @Valor("${bucket}") privado Cordas balde; @Valor("${senha}") privado Cordas senha; público @Feijão Aglomerado agrupamento() { retorno CouchbaseCluster.criar(nome do host); } público @Feijão Balde balde() { retorno agrupamento().openBucket(balde, senha); } |
Não configuramos o nome do host, baldee senha mas elas serão usadas para se conectar a um cluster do Couchbase e a um determinado bucket.
Adicionar variáveis de recursos
Você viu que estávamos usando nome do host, baldee senhaPortanto, agora é hora de defini-los.
Na árvore de projetos do IntelliJ IDEA, clique com o botão direito do mouse em src/main/resources e escolha Novo -> Arquivo. Nomeie o novo arquivo application.properties e adicione as seguintes linhas:
|
1 2 3 |
nome do host=127.0.0.1 balde=viagens-amostra senha= |
O Spring Boot vai captar isso application.properties para você. Mais informações sobre as propriedades relacionadas ao aplicativo podem ser vistas na seção Documentação oficial do Spring.
Criação de pontos de extremidade RESTful
Esse aplicativo será baseado em API, portanto, determinados pontos de extremidade precisam ser criados:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@RequestMapping(valor="/airport/findAll", método=RequestMethod.OBTER) público Lista<Mapa<Cordas, Objeto>> aeroportos(@RequestParam Cordas pesquisa) { } @RequestMapping(valor="/flightPath/findAll", método=RequestMethod.OBTER) público Lista<Mapa<Cordas, Objeto>> todos(@RequestParam Cordas de, @RequestParam Cordas para, @RequestParam Cordas sair) lançamentos Exceção { } @RequestMapping(valor="/user/login", método=RequestMethod.OBTER) público Objeto login(@RequestParam Cordas usuário, @RequestParam Cordas senha) { } @RequestMapping(valor="/user/login", método=RequestMethod.POST) público Objeto createLogin(@Corpo da solicitação Cordas json) { } @RequestMapping(valor="/user/flights", método=RequestMethod.POST) público Objeto livro(@Corpo da solicitação Cordas json) { } @RequestMapping(valor="/user/flights", método=RequestMethod.OBTER) público Objeto reservado(@RequestParam Cordas nome de usuário) { } |
Essencialmente, teremos pontos de extremidade para registro e login de usuários, reserva e localização de voos, bem como pesquisa de informações de voos.
A lógica por trás desses pontos de extremidade aparecerá em outra classe para fins de limpeza.
Criação de uma classe de banco de dados
Acabamos de configurar os pontos de extremidade de condução do nosso aplicativo Spring Boot, mas agora é hora de dar uma olhada na lógica por trás da interação com o banco de dados.
A classe de banco de dados para este projeto será Banco de dados.java e pode ser criado clicando com o botão direito do mouse no ícone trycb na árvore de projetos do IntelliJ IDEA e escolhendo Novo -> Classe Java.
Adicione o seguinte para obter a classe para um bom esqueleto de onde estamos indo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
pacote trycb; público classe Banco de dados { privado Banco de dados() { } público estático Lista<Mapa<Cordas, Objeto>> findAllAirports(final Balde balde, final Cordas parâmetros) { } público estático Lista<Mapa<Cordas, Objeto>> findAllFlightPaths(final Balde balde, Cordas de, Cordas para, Calendário sair) { } público estático ResponseEntity<Cordas> login(final Balde balde, final Cordas nome de usuário, final Cordas senha) { } público estático ResponseEntity<Cordas> createLogin(final Balde balde, final Cordas nome de usuário, final Cordas senha) { } privado estático Lista<Mapa<Cordas, Objeto>> extractResultOrThrow(Resultado da consulta resultado) { } } |
A partir daqui, completaremos cada um desses métodos na ordem em que provavelmente serão interagidos pelo usuário.
Criação de um novo usuário
Quando o usuário emite um POST para o /api/user/logina seguinte função de banco de dados deve ser chamada:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
público estático ResponseEntity<Cordas> createLogin(final Balde balde, final Cordas nome de usuário, final Cordas senha) { JsonObject dados = JsonObject.criar() .colocar("tipo", "usuário") .colocar("name" (nome), nome de usuário) .colocar("senha", BCrypt.hashpw(senha, BCrypt.gensalt())); JsonDocument doc = JsonDocument.criar("user::" + nome de usuário, dados); tentar { balde.inserir(doc); JsonObject responseData = JsonObject.criar() .colocar("sucesso", verdadeiro) .colocar("dados", dados); retorno novo ResponseEntity<Cordas>(responseData.toString(), HttpStatus.OK); } captura (Exceção e) { JsonObject responseData = JsonObject.vazio() .colocar("sucesso", falso) .colocar("falha", "Ocorreu um erro ao criar a conta") .colocar("exceção", e.getMessage()); retorno novo ResponseEntity<Cordas>(responseData.toString(), HttpStatus.OK); } } |
O nome de usuário e a senha incluídos na solicitação serão adicionados a um objeto JSON e, em seguida, a senha será criptografada com a biblioteca Spring BCrypt. Para manter o controle dos dados do usuário, os novos usuários serão colocados em documentos intitulados usuário::{USERNAME_HERE}. Usando bucket.insert(doc)Se o bucket for inserido, será feita uma tentativa de inserir os dados no bucket. Se não houver exceções lançadas, a tentativa foi bem-sucedida e uma resposta é retornada. Se houver uma exceção, a inserção falhou e o erro será retornado.
Como fazer login como um usuário existente
Quando o usuário emite um GET para o mesmo /api/user/login a seguinte função de banco de dados deve ser chamada:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
público estático ResponseEntity<Cordas> login(final Balde balde, final Cordas nome de usuário, final Cordas senha) { JsonDocument doc = balde.obter("user::" + nome de usuário); JsonObject responseContent; se(BCrypt.checkpw(senha, doc.conteúdo().getString("senha"))) { responseContent = JsonObject.criar().colocar("sucesso", verdadeiro).colocar("dados", doc.conteúdo()); } mais { responseContent = JsonObject.vazio().colocar("sucesso", falso).colocar("falha", "Nome de usuário ou senha incorreta"); } retorno novo ResponseEntity<Cordas>(responseContent.toString(), HttpStatus.OK); } |
Usando bucket.get("user::" + nome de usuário) com o nome de usuário fornecido, o aplicativo Java obtém o documento do bucket, se ele existir. A biblioteca Spring BCrypt tem uma função excelente para verificar se a senha fornecida corresponde ou não à senha criptografada que está armazenada. Se corresponder, retorna um objeto de sucesso; caso contrário, retorna um objeto de falha de login.
Extraindo o resultado do N1QL e tornando-o legível em Java
O N1QL retorna um Resultado da consulta que pode ser menos desejável se estiver retornando dados para um front-end solicitante. O que realmente queremos fazer é convertê-lo em um objeto Lista objeto.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
privado estático Lista<Mapa<Cordas, Objeto>> extractResultOrThrow(Resultado da consulta resultado) { se (!resultado.finalSuccess()) { lançar novo DataRetrievalFailureException("Erro de consulta: " + resultado.erros()); } Lista<Mapa<Cordas, Objeto>> conteúdo = novo ArrayList<Mapa<Cordas, Objeto>>(); para (QueryRow fila : resultado) { conteúdo.adicionar(fila.valor().toMap()); } retorno conteúdo; } |
Essa função será chamada toda vez que os dados N1QL forem retornados.
Como encontrar todos os aeroportos
Agora veremos um pouco da mágica por trás do N1QL quando se trata de pesquisar aeroportos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
público estático Lista<Mapa<Cordas, Objeto>> findAllAirports(final Balde balde, final Cordas parâmetros) { Declaração consulta; Como caminho prefixo = selecionar("nome do aeroporto").de(i(balde.nome())); se (parâmetros.comprimento() == 3) { consulta = prefixo.onde(x("faa").eq(s(parâmetros.toUpperCase()))); } mais se (parâmetros.comprimento() == 4 && (parâmetros.iguais(parâmetros.toUpperCase()) || parâmetros.iguais(parâmetros.toLowerCase()))) { consulta = prefixo.onde(x("icao").eq(s(parâmetros.toUpperCase()))); } mais { consulta = prefixo.onde(i("nome do aeroporto").como(s(parâmetros + "%"))); } Resultado da consulta resultado = balde.consulta(Consulta.simples(consulta)); retorno extractResultOrThrow(resultado); } |
Você pode ver no código acima que estamos usando a API do Fluent com o IntelliJ IDEA para criar nossa consulta N1QL. Essencialmente, se você fosse olhar para o SQL bruto, ele teria a seguinte aparência:
|
1 2 3 |
SELECIONAR nome do aeroporto DE `viagens-amostra` ONDE faa = {{PARÂMETROS}} |
No exemplo acima, {{PARAMS}} representa um aeroporto como LAX ou similar. É claro que isso ocorre desde que o comprimento dos params seja três.
Como encontrar todas as rotas de voo
Por fim, ficamos com o método responsável por encontrar rotas de voo:
|
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 26 27 28 29 30 31 32 33 34 35 36 37 38 |
público estático Lista<Mapa<Cordas, Objeto>> findAllFlightPaths(final Balde balde, Cordas de, Cordas para, Calendário sair) { Declaração consulta = selecionar(x("faa").como("fromAirport" (do aeroporto))) .de(i(balde.nome())) .onde(x("nome do aeroporto").eq(s(de))) .sindicato() .selecionar(x("faa").como("toAirport" (para o aeroporto))) .de(i(balde.nome())) .onde(x("nome do aeroporto").eq(s(para))); Resultado da consulta resultado = balde.consulta(Consulta.simples(consulta)); se (!resultado.finalSuccess()) { lançar novo DataRetrievalFailureException("Erro de consulta: " + resultado.erros()); } Cordas doAeroporto = nulo; Cordas paraAeroporto = nulo; para (QueryRow fila : resultado) { se (fila.valor().containsKey("fromAirport" (do aeroporto))) { doAeroporto = fila.valor().getString("fromAirport" (do aeroporto)); } se (fila.valor().containsKey("toAirport" (para o aeroporto))) { paraAeroporto = fila.valor().getString("toAirport" (para o aeroporto)); } } Declaração joinQuery = selecionar("a.name", "s.flight", "s.utc", "r.sourceairport", "r.destinationairport", "r.equipment") .de(i(balde.nome()).como("r")) .não registrado("r.schedule AS s") .unir-se(i(balde.nome()).como("a") + " ON KEYS r.airlineid") .onde(x("r.sourceairport").eq(s(doAeroporto)).e(x("r.destinationairport").eq(s(paraAeroporto))).e(x("s.day").eq(sair.obter(Calendário.DIA_DO_MÊS)))) .orderBy(Classificar.asc("a.name")); Resultado da consulta outroResultado = balde.consulta(joinQuery); retorno extractResultOrThrow(outroResultado); } |
Estamos fazendo duas consultas N1QL nesse método. A primeira pode ser facilmente traduzida para o seguinte:
|
1 2 3 |
SELECIONAR faa AS doAeroporto DE `viagens-amostra` ONDE nome do aeroporto = {{PARÂMETROS.DE}} UNIÃO SELECIONAR faa AS paraAeroporto DE `viagens-amostra` ONDE nome do aeroporto = {{PARÂMETROS.PARA}} |
É claro que {{PARAMS}} é o que foi passado para o seu endpoint. Na declaração, estamos combinando os conjuntos de resultados de todos os de aeroportos e todos os para aeroportos.
Depois de obter os dois conjuntos de resultados, estamos percorrendo-os para garantir que o para e de existem, caso contrário, o padrão será NULL, o que impedirá que a próxima consulta seja bem-sucedida.
A segunda consulta pode ser traduzida na seguinte consulta bruta:
|
1 2 3 |
SELECIONAR a.nome, s.voo, s.utc, r.aeroporto de origem, r.aeroporto de destino, r.equipamentos DE `viagens-amostra` AS r INÚTIL r.cronograma AS s JUNTAR `viagens-amostra` AS a ON CHAVES r.companhia aérea ONDE r.aeroporto de origem = {{PARA}} E r.aeroporto de destino = {{PARA}} E s.dia = 3 ORDEM BY a.nome ASC |
Estamos obtendo informações de programação sobre os voos, aninhando-as no documento JSON e, em seguida, unindo-as à chave agora achatada.
Encerramento das classes de aplicativo e banco de dados
Agora temos nossos endpoints e métodos de banco de dados, mas eles não estão conectados uns aos outros. É hora de revisitar o Application.java e adicione algum código às funções que criamos anteriormente:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
@RequestMapping(valor="/user/login", método= RequestMethod.OBTER) público Objeto login(@RequestParam Cordas usuário, @RequestParam Cordas senha) { retorno Banco de dados.login(balde(), usuário, senha); } @RequestMapping(valor="/user/login", método=RequestMethod.POST) público Objeto createLogin(@Corpo da solicitação Cordas json) { JsonObject jsonData = JsonObject.fromJson(json); retorno Banco de dados.createLogin(balde(), jsonData.getString("usuário"), jsonData.getString("senha")); } |
Você pode ver que as duas imagens estáticas Banco de dados são chamados de cada um dos pontos de extremidade relacionados às contas de usuário. O mesmo processo pode ser feito para os outros pontos de extremidade que criamos anteriormente:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@RequestMapping("/airport/findAll") público Lista<Mapa<Cordas, Objeto>> aeroportos(@RequestParam Cordas pesquisa) { retorno Banco de dados.findAllAirports(balde(), pesquisa); } @RequestMapping("/flightPath/findAll") público Lista<Mapa<Cordas, Objeto>> todos(@RequestParam Cordas de, @RequestParam Cordas para, @RequestParam Cordas sair) lançamentos Exceção { Calendário calendário = Calendário.getInstance(Local.EUA); calendário.setTime(Formato de data.getDateInstance(Formato de data.CURTO, Local.EUA).analisar(sair)); retorno Banco de dados.findAllFlightPaths(balde(), de, para, calendário); } |
Teste dos pontos finais da amostra
Há algumas maneiras de testar os endpoints do aplicativo. Neste exemplo, usaremos o cURL, mas você certamente pode usar o Postman para o Google Chrome ou algo semelhante.
Com o cURL instalado, abra um Terminal ou Prompt de Comando e digite o seguinte:
|
1 2 3 |
enrolar -X OBTER 'http://localhost:8080/api/airport/findAll?search=LAX' |
O comando cURL acima atingirá o api/airport/findAll e passar um parâmetro de search=LAX. Se for bem-sucedido, você deverá receber uma resposta de:
|
1 |
[{"nome do aeroporto":"Los Angeles Intl"}] |
O mesmo tipo de teste pode ser feito para todos os outros endpoints.
Conclusão
Acabamos de ver como configurar um aplicativo de viagem de amostra que usa o Couchbase Server e o Spring Boot para Java. Embora não tenhamos configurado um front-end, é muito possível adicionar um usando linguagens como AngularJS, jQuery ou ReactJS.
Esse projeto completo, juntamente com um front-end AngularJS, pode ser obtido no site GitHub do Couchbase Labs canal.