En Couchbase Connect 2015 demostramos una aplicación de ejemplo que utiliza N1QL para consultar datos de un bucket de Couchbase de ejemplo.
Si te perdiste la conferencia, no hay problema. Vamos a repasar cómo reproducir esta aplicación y revisar algunos de los aspectos más destacados de Couchbase 4.0.
Requisitos previos
- Apache Maven 3
- Kit de desarrollo de Java (JDK) 1.7
- Servidor Couchbase 4.0
- IntelliJ IDEA 14.1+, Eclipse o NetBeans. En este ejemplo se utilizará IntelliJ IDEA.
Crear un nuevo proyecto
Abra IntelliJ IDEA y elija crear un nuevo proyecto Java, asegurándose de utilizar JDK 1.7 si se le pide. Para los propósitos de esta guía, vamos a llamar al proyecto try-cb-java.
Ahora haz clic con el botón derecho try-cb-java en el árbol de proyectos y, a continuación, seleccione Añadir compatibilidad con marcos y seleccione Maven. Esto añadirá un pom.xml a su proyecto.
Configuración de Maven
Dentro de la pom.xml comience por dar al proyecto un nombre de grupo más atractivo:
|
1 2 3 |
<groupId>com.couchbase.example</groupId> |
A continuación, procedemos a añadir el resto de nuestras dependencias al archivo, que incluyen Spring Boot, el cliente Couchbase y el framework de seguridad de 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 |
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.3.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> </dependency> <dependency> <groupId>com.couchbase.client</groupId> <artifactId>java-client</artifactId> <version>2.2.0-dp</version> </dependency> </dependencies> <repositories> <repository> <id>couchbase</id> <name>couchbase repo</name> <url>https://files.couchbase.com/maven2</url> <snapshots><enabled>false</enabled></snapshots> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> |
Creación de un perfil de carrera
En este momento, si intenta ejecutar la aplicación, se producirá un error o no ocurrirá nada porque no hay ningún perfil configurado actualmente.
En la barra de herramientas, seleccione Ejecutar -> Editar configuraciones y elija añadir una nueva configuración de Maven. Puedes ponerle el nombre que quieras, pero es importante tener lo siguiente en el campo de línea de comandos:
|
1 2 3 |
spring-boot:run |
Para este artículo vamos a nombrar la configuración Spring Boot.
IntelliJ IDEA debería estar ahora listo para el desarrollo.
Creación de índices en el cubo Couchbase
Como este tutorial utiliza consultas N1QL, primero debemos añadir índices a nuestro bucket de Couchbase Server. Ahora bien, esto se puede hacer fácilmente a través de código, pero para este ejemplo vamos a atajarlo y añadirlos a través del cliente Couchbase Query (CBQ) que se instala automáticamente con una instalación de Couchbase 4+ en Mac OS y Windows.
En Mac OS, inicie CBQ que se encuentra en /Applications/Couchbase Server.app/Contents/Resources/couchbase-core/bin/cbq y ejecuta lo siguiente:
|
1 2 3 |
CREATE PRIMARY INDEX def_primary ON `travel-sample` USING gsi; |
En Windows, inicie CBQ que se encuentra en C:/Archivos de programa/Couchbase/Server/bin/cbq.exe y ejecute el mismo comando N1QL que en Mac OS.
Creación de una clase principal de aplicación
La clase principal de este proyecto será Aplicación.java y pueden crearse haciendo clic con el botón derecho del ratón en el icono trycb del árbol de proyectos y seleccionando Nuevo -> Clase Java.
Añade lo siguiente para poner la clase en su estado ejecutable más básico:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package trycb; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController @RequestMapping("/api") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } |
Asegúrese de que el proyecto se ejecuta sin errores seleccionando Ejecutar -> Ejecutar 'Spring Boot de la barra de herramientas de IntelliJ IDEA.
Gestión de recursos compartidos entre orígenes (CORS)
Dado que la mayor parte de nuestras pruebas se harán localmente, tenemos que asegurarnos de que CORS está habilitado, de lo contrario el navegador web se va a quejar cuando se trata de golpear a nuestros puntos finales de la API con JavaScript.
Asegúrate de que la clase Aplicación implementa la clase Filtro y añade el siguiente código:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); chain.doFilter(req, res); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} |
Configurar las opciones de cluster y bucket de Couchbase
A partir de ahora tenemos esencialmente una aplicación Spring Boot sin interación con Couchbase. Hemos incluido el cliente Java a través de Maven, así que es hora de empezar a usarlo.
Añade lo siguiente a la clase Aplicación:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Value("${hostname}") private String hostname; @Value("${bucket}") private String bucket; @Value("${password}") private String password; public @Bean Cluster cluster() { return CouchbaseCluster.create(hostname); } public @Bean Bucket bucket() { return cluster().openBucket(bucket, password); } |
No hemos configurado el nombre de host, cuboy contraseña pero se utilizarán para conectarse a un cluster de Couchbase y a un bucket en particular.
Añadir variables de recursos
Viste que estábamos usando nombre de host, cuboy contraseñaAsí que ahora es el momento de fijarlos.
En el árbol de proyectos de IntelliJ IDEA, haga clic con el botón derecho del ratón en src/main/recursos y elija Nuevo -> Archivo. Nombre del nuevo archivo aplicación.propiedades y añade las siguientes líneas:
|
1 2 3 |
hostname=127.0.0.1 bucket=travel-sample password= |
Spring Boot recogerá este aplicación.propiedades para usted. Encontrará más información sobre las propiedades relacionadas con la aplicación en la sección documentación oficial de Spring.
Creación de puntos finales RESTful
Esta aplicación va a estar basada en API por lo que es necesario crear ciertos endpoints:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@RequestMapping(value="/airport/findAll", method=RequestMethod.GET) public List<Map<String, Object>> airports(@RequestParam String search) { } @RequestMapping(value="/flightPath/findAll", method=RequestMethod.GET) public List<Map<String, Object>> all(@RequestParam String from, @RequestParam String to, @RequestParam String leave) throws Exception { } @RequestMapping(value="/user/login", method=RequestMethod.GET) public Object login(@RequestParam String user, @RequestParam String password) { } @RequestMapping(value="/user/login", method=RequestMethod.POST) public Object createLogin(@RequestBody String json) { } @RequestMapping(value="/user/flights", method=RequestMethod.POST) public Object book(@RequestBody String json) { } @RequestMapping(value="/user/flights", method=RequestMethod.GET) public Object booked(@RequestParam String username) { } |
Esencialmente, tendremos puntos finales para el registro e inicio de sesión de los usuarios, la reserva y búsqueda de vuelos, así como la búsqueda de información sobre vuelos.
La lógica detrás de estos puntos finales aparecerá en otra clase para la limpieza.
Creación de una clase de base de datos
Acabamos de configurar los puntos finales de nuestra aplicación Spring Boot, pero ahora es el momento de echar un vistazo a la lógica detrás de la interacción con la base de datos.
La clase de base de datos para este proyecto será Base de datos.java y pueden crearse haciendo clic con el botón derecho del ratón en el icono trycb del árbol de proyectos de IntelliJ IDEA y seleccionando Nuevo -> Clase Java.
Añade lo siguiente para obtener la clase para un bonito esqueleto de a dónde vamos:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package trycb; public class Database { private Database() { } public static List<Map<String, Object>> findAllAirports(final Bucket bucket, final String params) { } public static List<Map<String, Object>> findAllFlightPaths(final Bucket bucket, String from, String to, Calendar leave) { } public static ResponseEntity<String> login(final Bucket bucket, final String username, final String password) { } public static ResponseEntity<String> createLogin(final Bucket bucket, final String username, final String password) { } private static List<Map<String, Object>> extractResultOrThrow(QueryResult result) { } } |
A partir de aquí, vamos a completar cada uno de estos métodos en el orden en que es probable que el usuario interactúe con ellos.
Crear un nuevo usuario
Cuando el usuario emite un POST al /api/usuario/inicio de sesiónse debe llamar a la siguiente función de la base de datos:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public static ResponseEntity<String> createLogin(final Bucket bucket, final String username, final String password) { JsonObject data = JsonObject.create() .put("type", "user") .put("name", username) .put("password", BCrypt.hashpw(password, BCrypt.gensalt())); JsonDocument doc = JsonDocument.create("user::" + username, data); try { bucket.insert(doc); JsonObject responseData = JsonObject.create() .put("success", true) .put("data", data); return new ResponseEntity<String>(responseData.toString(), HttpStatus.OK); } catch (Exception e) { JsonObject responseData = JsonObject.empty() .put("success", false) .put("failure", "There was an error creating account") .put("exception", e.getMessage()); return new ResponseEntity<String>(responseData.toString(), HttpStatus.OK); } } |
El nombre de usuario y la contraseña incluidos en la solicitud se añadirán a un objeto JSON y, a continuación, la contraseña se cifrará con la biblioteca Spring BCrypt. Para realizar un seguimiento de los datos de usuario, los nuevos usuarios terminarán en documentos titulados usuario::{USERNAME_HERE}. Utilización de bucket.insert(doc)se intenta insertar los datos en el bucket. Si no se lanzan excepciones, entonces tuvo éxito y se devuelve una respuesta. Si hay una excepción, entonces la inserción falló y se devolverá el error.
Iniciar sesión como usuario existente
Cuando el usuario emite un GET al mismo /api/usuario/inicio de sesión se debe llamar a la siguiente función de base de datos:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
public static ResponseEntity<String> login(final Bucket bucket, final String username, final String password) { JsonDocument doc = bucket.get("user::" + username); JsonObject responseContent; if(BCrypt.checkpw(password, doc.content().getString("password"))) { responseContent = JsonObject.create().put("success", true).put("data", doc.content()); } else { responseContent = JsonObject.empty().put("success", false).put("failure", "Bad Username or Password"); } return new ResponseEntity<String>(responseContent.toString(), HttpStatus.OK); } |
Utilizando bucket.get("usuario::" + nombredeusuario) con el nombre de usuario proporcionado, la aplicación Java obtiene el documento del bucket si existe. La librería Spring BCrypt tiene una gran función para comprobar si la contraseña proporcionada coincide o no con la contraseña encriptada que está almacenada. Si lo hace, entonces devuelve un objeto success, en caso contrario devuelve un objeto login failed.
Cómo extraer el resultado de N1QL y hacerlo legible en Java
N1QL devuelve un Resultado de la consulta que puede ser menos deseable si se devuelven datos a un front-end solicitante. Lo que realmente queremos hacer es convertirlo en un objeto Lista objeto.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
private static List<Map<String, Object>> extractResultOrThrow(QueryResult result) { if (!result.finalSuccess()) { throw new DataRetrievalFailureException("Query error: " + result.errors()); } List<Map<String, Object>> content = new ArrayList<Map<String, Object>>(); for (QueryRow row : result) { content.add(row.value().toMap()); } return content; } |
Esta función será llamada cada vez que se devuelvan datos N1QL.
Encontrar todos los aeropuertos
Ahora vamos a ver algo de la magia detrás de N1QL cuando se trata de buscar aeropuertos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static List<Map<String, Object>> findAllAirports(final Bucket bucket, final String params) { Statement query; AsPath prefix = select("airportname").from(i(bucket.name())); if (params.length() == 3) { query = prefix.where(x("faa").eq(s(params.toUpperCase()))); } else if (params.length() == 4 && (params.equals(params.toUpperCase()) || params.equals(params.toLowerCase()))) { query = prefix.where(x("icao").eq(s(params.toUpperCase()))); } else { query = prefix.where(i("airportname").like(s(params + "%"))); } QueryResult result = bucket.query(Query.simple(query)); return extractResultOrThrow(result); } |
Puede ver en el código anterior que estamos utilizando la API Fluent con IntelliJ IDEA para crear nuestra consulta N1QL. Esencialmente, si usted fuera a mirar SQL crudo, se vería así:
|
1 2 3 |
SELECT airportname FROM `travel-sample` WHERE faa = {{PARAMS}} |
En el ejemplo anterior, {{PARAMS}} representa un aeropuerto como LAX o similar. Por supuesto, siempre que la longitud de los parámetros sea tres.
Encontrar todas las rutas de vuelo
Por último, nos queda el método responsable de encontrar rutas de vuelo:
|
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 |
public static List<Map<String, Object>> findAllFlightPaths(final Bucket bucket, String from, String to, Calendar leave) { Statement query = select(x("faa").as("fromAirport")) .from(i(bucket.name())) .where(x("airportname").eq(s(from))) .union() .select(x("faa").as("toAirport")) .from(i(bucket.name())) .where(x("airportname").eq(s(to))); QueryResult result = bucket.query(Query.simple(query)); if (!result.finalSuccess()) { throw new DataRetrievalFailureException("Query error: " + result.errors()); } String fromAirport = null; String toAirport = null; for (QueryRow row : result) { if (row.value().containsKey("fromAirport")) { fromAirport = row.value().getString("fromAirport"); } if (row.value().containsKey("toAirport")) { toAirport = row.value().getString("toAirport"); } } Statement joinQuery = select("a.name", "s.flight", "s.utc", "r.sourceairport", "r.destinationairport", "r.equipment") .from(i(bucket.name()).as("r")) .unnest("r.schedule AS s") .join(i(bucket.name()).as("a") + " ON KEYS r.airlineid") .where(x("r.sourceairport").eq(s(fromAirport)).and(x("r.destinationairport").eq(s(toAirport))).and(x("s.day").eq(leave.get(Calendar.DAY_OF_MONTH)))) .orderBy(Sort.asc("a.name")); QueryResult otherResult = bucket.query(joinQuery); return extractResultOrThrow(otherResult); } |
Estamos haciendo dos consultas N1QL en este método. La primera se puede traducir fácilmente a lo siguiente:
|
1 2 3 |
SELECT faa AS fromAirport FROM `travel-sample` WHERE airportname = {{PARAMS.FROM}} UNION SELECT faa AS toAirport FROM `travel-sample` WHERE airportname = {{PARAMS.TO}} |
Por supuesto {{PARAMS}} es lo que se pasó a su punto final. En la declaración, estamos combinando los conjuntos de resultados de todos los de aeropuertos y todos los a aeropuertos.
Tras obtener los dos conjuntos de resultados, los recorremos en bucle para asegurarnos de que el a y de ya que, de lo contrario, los asignaremos por defecto a NULL, lo que impedirá que la siguiente consulta se realice correctamente.
La segunda consulta puede traducirse en la siguiente consulta sin procesar:
|
1 2 3 |
SELECT a.name, s.flight, s.utc, r.sourceairport, r.destinationairport, r.equipment FROM `travel-sample` AS r UNNEST r.schedule AS s JOIN `travel-sample` AS a ON KEYS r.airlineid WHERE r.sourceairport = {{TO}} AND r.destinationairport = {{TO}} AND s.day = 3 ORDER BY a.name ASC |
Obtenemos la información de los horarios de los vuelos desanidándola del documento JSON y uniéndola a la clave ahora aplanada.
Finalización de las clases de aplicación y base de datos
Ahora tenemos nuestros endpoints y métodos de base de datos, pero no están conectados entre sí. Ha llegado el momento de volver a examinar el Aplicación.java y añadir algo de código a las funciones que hemos creado anteriormente:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
@RequestMapping(value="/user/login", method= RequestMethod.GET) public Object login(@RequestParam String user, @RequestParam String password) { return Database.login(bucket(), user, password); } @RequestMapping(value="/user/login", method=RequestMethod.POST) public Object createLogin(@RequestBody String json) { JsonObject jsonData = JsonObject.fromJson(json); return Database.createLogin(bucket(), jsonData.getString("user"), jsonData.getString("password")); } |
Se puede ver que los dos estáticos Base de datos desde cada uno de los endpoints relacionados con las cuentas de usuario. El mismo proceso se puede hacer para los otros endpoints que hemos creado previamente:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@RequestMapping("/airport/findAll") public List<Map<String, Object>> airports(@RequestParam String search) { return Database.findAllAirports(bucket(), search); } @RequestMapping("/flightPath/findAll") public List<Map<String, Object>> all(@RequestParam String from, @RequestParam String to, @RequestParam String leave) throws Exception { Calendar calendar = Calendar.getInstance(Locale.US); calendar.setTime(DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse(leave)); return Database.findAllFlightPaths(bucket(), from, to, calendar); } |
Comprobación de los puntos finales de la muestra
Hay algunas maneras de probar los endpoints de la aplicación. En este ejemplo vamos a utilizar cURL, pero sin duda puede utilizar Postman para Google Chrome o algo similar.
Con cURL instalado, abra un Terminal o Símbolo del sistema e introduzca lo siguiente:
|
1 2 3 |
curl -X GET 'https://localhost:8080/api/airport/findAll?search=LAX' |
El comando cURL anterior golpeará el api/aeropuerto/findAll y pasar un parámetro de search=LAX. Si tiene éxito, usted debe obtener una respuesta de:
|
1 |
[{"airportname":"Los Angeles Intl"}] |
El mismo tipo de prueba puede hacerse para cualquier otro punto final.
Conclusión
Acabamos de ver cómo configurar una aplicación de viajes de ejemplo que utiliza Couchbase Server y Spring Boot para Java. Aunque no configuramos un front-end, es muy posible añadir uno usando lenguajes como AngularJS, jQuery o ReactJS.
Este proyecto completo, junto con un front-end AngularJS, puede obtenerse en la página Couchbase Labs GitHub canal.