El año pasado empecé a aprender Kotlin y me sorprendió lo fácil que era convertir una aplicación Java. IntelliJ y algunos otros IDEs ofrecen buenas herramientas para la conversión automática, y con unos pocos ajustes puedes terminar con un código mucho más conciso y menos propenso a errores.

Así que decidí crear una aplicación de ejemplo para mostrar mi nueva combinación favorita: Kotlin, Spring Boot, Spring Data y Couchbase:
Creación de un servicio de perfil de usuario
Puede clonar el proyecto completo aquí:
https://github.com/couchbaselabs/try-cb-kotlin
Empecemos por crear nuestra clase principal:
|
1 2 3 4 5 6 |
@SpringBootApplication abra clase KotlinDemoApplication divertido principal(args: Matriz<Cadena>) { SpringApplication.ejecute(KotlinDemoApplication::clase.java, *args) } |
Nota: Su clase debe ser abra de lo contrario se producirá el siguiente error:
|
1 2 3 4 |
org.springframework.judías.fábrica.análisis sintáctico.BeanDefinitionParsingException: Configuración problema: @Configuración clase KotlinDemoApplication mayo no sea final. Eliminar el final modificador a continuar. Delincuente recurso: com.couchbase.KotlinDemoApplication en org.springframework.judías.fábrica.análisis sintáctico.FailFastProblemReporter.error(FailFastProblemReporter.java:70) ~[primavera-judías-4.3.13.RELEASE.jar:4.3.13.RELEASE] en org.springframework.contexto.anotación.ClaseConfiguración.valide(ClaseConfiguración.java:214) ~[primavera-contexto-4.3.13.RELEASE.jar:4.3.13.RELEASE] |
Aquí está nuestra entidad de Usuario, es muy similar a el de Java:
|
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 |
@Documento clase Usuario(): Entidad básica() { constructor(id: Cadena, nombre: Cadena, dirección: Dirección, preferencias: Lista<Preferencia>, securityRoles: Lista<Cadena>): este(){ este.id = id; este.nombre = nombre; este.dirección = dirección; este.preferencias = preferencias; este.securityRoles = securityRoles; } @Id var id: Cadena? = null @NotNull var nombre: Cadena? = null @Campo var dirección: Dirección? = null @Campo var preferencias: Lista<Preferencia> = emptyList() @Campo var securityRoles: Lista<Cadena> = emptyList() } |
- @Documento: Anotación de Couchbase que define una entidad, similar a @Entidad en JPA. Couchbase añadirá automáticamente una propiedad llamada Clase en el documento para utilizarlo como tipo de documento.
- @Id: La clave del documento
- @Campo: Las anotaciones de Couchbase, similares a las de JPA @Columna. No es obligatorio, pero recomendamos su uso.
Mapear atributos en Couchbase es realmente sencillo. Serán mapeados directamente a la estructura correspondiente en JSON:
- Propiedades simples: Asignación directa a JSON:
|
1 2 3 4 |
{ "id": "usuario::1", "nombre": "Denis Rosa" } |
- Matrices: Como era de esperar, las matrices como securityRoles se convertirán en matrices JSON:
|
1 2 3 |
{ "securityRoles": ["admin", "usuario"] } |
- Entidades anidadas: ¿Odias mapear @ManyToOne ¿relaciones? A mí también. Como estamos utilizando una base de datos de documentos, ya no es necesario escribir estas relaciones, las entidades anidadas también se traducen directamente a JSON.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
{ "id":"usuario::1", "nombre":"Denis Rosa", "dirección":{ "streetName":"Una calle en algún lugar", "houseNumber":"42", "código postal":"81234", "ciudad":"Munich", "país":"DE" }, "preferencias":[ { "nombre":"lang", "valor":"ES" } ], "securityRoles":[ "admin", "usuario" ] } |
Repositorios
Ahora, echemos un vistazo al aspecto que tendrá nuestro repositorio:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
@N1qlPrimaryIndexed @VerIndexado(designDoc = "usuario") interfaz UsuarioRepositorio : CouchbasePagingAndSortingRepository<Usuario, Cadena> { divertido findByName(nombre: Cadena): Lista<Usuario> @Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} and ANY preference IN " + " preferences SATISFIES preference.name = $1 END") divertido findUsersByPreferenceName(nombre: Cadena): Lista<Usuario> @Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} and meta().id = $1 and ARRAY_CONTAINS(securityRoles, $2)") divertido hasRole(userId: Cadena, papel: Cadena): Usuario } |
- @N1qlPrimaryIndexed: Este anotación asegura que el bucket asociado al repositorio actual tendrá un índice primario N1QL
- @ViewIndexed: Este anotación le permite definir el nombre del documento de diseño y el nombre de la vista, así como una función de mapa y reducción personalizada.
Como puede ver a continuación, puede aprovechar todos los Palabras clave de Spring Data para consultar la base de datos, como BuscarEn, Entre, EsMayorQue, Como, Existeetc.
|
1 |
divertido findByName(nombre: Cadena): Lista<Usuario> |
El repositorio está ampliando CouchbasePagingAndSortingRepositoryque le permite paginar sus consultas simplemente añadiendo un icono Pageable al final de la definición del método. Si necesitas escribir consultas más potentes, también puedes utilizar N1QL:
|
1 2 3 4 5 |
@Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} and ANY preference IN " + " preferences SATISFIES preference.name = $1 END") divertido findUsersByPreferenceName(nombre: Cadena): Lista<Usuario> @Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} and meta().id = $1 and ARRAY_CONTAINS(securityRoles, $2)") divertido hasRole(userId: Cadena, papel: Cadena): Usuario |
Las consultas anteriores tienen algunos azúcares sintácticos para hacerlas más pequeñas:
- #(#n1ql.bucket): Utilice esta sintaxis para evitar introducir el nombre del cubo en la consulta
- #{#n1ql.selectEntity}: sintaxis-azúcar a SELECT * FROM #(#n1ql.bucket):
- #{#n1ql.filter}: sintaxis-azúcar para filtrar el documento por tipo, técnicamente significa class = 'miPaquete.MiNombreDeClase' (Clase es el atributo que se añade automáticamente al documento para definir su tipo cuando se trabaja con Couchbase en Spring Data )
- #{#n1ql.fields} se sustituirá por la lista de campos (por ejemplo, para una cláusula SELECT) necesarios para reconstruir la entidad.
- #{#n1ql.delete} se sustituirá por la sentencia delete from.
- #{#n1ql.returning} se sustituirá por la cláusula de retorno necesaria para reconstruir la entidad.
Servicios
Nuestro servicio básicamente reenvía peticiones a nuestro repositorio, pero si necesitas escribir consultas ad-hoc, este es el lugar adecuado:
|
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 |
@Servicio clase ServicioUsuario { @Autowired lateinit var usuarioRepositorio: UsuarioRepositorio; divertido findByName(nombre: Cadena): Lista<Usuario> = usuarioRepositorio.findByName(nombre) divertido findById(userId: Cadena) = usuarioRepositorio.findOne(userId) divertido guardar(@Válido usuario: Usuario) = usuarioRepositorio.guardar(usuario) divertido findUsersByPreferenceName(nombre: Cadena): Lista<Usuario> = usuarioRepositorio.findUsersByPreferenceName(nombre) divertido hasRole(userId: Cadena, papel: Cadena): Booleano { devolver usuarioRepositorio.hasRole(userId, papel) != null } /** * Ejemplo de consultas ad hoc */ divertido findUserByAddress(streetName: Cadena = "", número: Cadena = "", código postal: Cadena = "", ciudad: Cadena = "", país: Cadena = ""): Lista<Usuario> { var consulta = "SELECT meta(b).id as id, b.* FROM" + getBucketName() + " b WHERE b._class = '" + Usuario::clase.java.getName() + "' " si (!streetName.isNullOrBlank()) consulta += " and b.address.streetName = '$streetName' " si (!número.isNullOrBlank()) consulta += " and b.address.houseNumber = '$number' " si (!código postal.isNullOrBlank()) consulta += " y b.address.postalCode = '$postalCode' " si (!ciudad.isNullOrBlank()) consulta += " and b.address.city = '$city' " si (!país.isNullOrBlank()) consulta += " y b.address.country = '$country' " val parámetros = N1qlParámetros.construya().coherencia(Consistencia de escaneado.SOLICITUD_PLUS).adhoc(verdadero) val paramQuery = N1qlQuery.parametrizado(consulta, JsonObject.crear(), parámetros) devolver usuarioRepositorio.getCouchbaseOperations().findByN1QLProyección(paramQuery, Usuario::clase.java) } divertido getBucketName() = usuarioRepositorio.getCouchbaseOperations().getCouchbaseBucket().bucketManager().información().nombre() } |
Controladores
Por último, vamos a añadir también un controlador para probar nuestros servicios a través de rest:
|
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 |
@RestController @RequestMapping("/api/usuario") clase UserController { @Autowired lateinit var userService: ServicioUsuario @GetMapping(valor = "/{id}") divertido findById(@PathParam("id") id: Cadena) = userService.findById(id) @GetMapping(valor = "/preferencia") divertido findPreference(@RequestParam("nombre") nombre: Cadena): Lista<Usuario> { devolver userService.findUsersByPreferenceName(nombre) } @GetMapping(valor = "/find") divertido findUserByName(@RequestParam("nombre") nombre: Cadena): Lista<Usuario> { devolver userService.findByName(nombre) } @PostMapping(valor = "/guardar") divertido findUserByName(@RequestBody usuario: Usuario) = userService.guardar(usuario) @GetMapping(valor = "/encontrarpordirección") divertido findByAddress(@RequestParam("streetName", defaultValue = "") streetName: Cadena, @RequestParam("número", defaultValue = "") número: Cadena, @RequestParam("código postal", defaultValue = "") código postal: Cadena, @RequestParam("ciudad", defaultValue = "") ciudad: Cadena, @RequestParam("país", defaultValue = "") país: Cadena): Lista<Usuario> { devolver userService.findUserByAddress(streetName, número, código postal, ciudad, país); } } |
Escribir pruebas de integración con Kotlin
Para ejecutar correctamente las pruebas de integración, no olvide configurar las credenciales de su base de datos en el campo aplicación.propiedades file:
|
1 2 3 4 |
primavera.couchbase.arranque-alberga=localhost primavera.couchbase.cubo.nombre=prueba primavera.couchbase.cubo.contraseña=algunaContraseña primavera.datos.couchbase.auto-índice=verdadero |
Aquí puede ver el aspecto de nuestras pruebas:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Prueba divertido testComposedAddress() { val dirección1 = Dirección("calle1", "1", "0000", "santo andre", "br") val dirección2 = Dirección("calle1", "2", "0000", "santo andre", "br") val dirección3 = Dirección("calle2", "12", "1111", "munich", "de") userService.guardar(Usuario(USUARIO_1, "usuario1", dirección1, emptyList(), emptyList())) userService.guardar(Usuario("usuario::2", "usuario2", dirección2, emptyList(), emptyList())) userService.guardar(Usuario("usuario::3", "usuario3", dirección3, emptyList(), emptyList())) var usuarios = userService.findUserByAddress(streetName = "calle1") assertThat(usuarios, hasSize<Cualquier>(2)) usuarios = userService.findUserByAddress(streetName = "calle1", número= "1") assertThat(usuarios, hasSize<Cualquier>(1)) usuarios = userService.findUserByAddress(país = "de") assertThat(usuarios, hasSize<Cualquier>(1)) } |
Dependencias de Kotlin y Maven
Kotlin se mueve rápidamente, así que ten en cuenta utilizar las versiones más recientes de cada dependencia:
|
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 46 47 48 49 50 51 52 53 54 55 |
<dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> <version>1.2.41</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> <version>1.2.41</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-kotlin</artifactId> <version>2.9.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-couchbase</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>pruebaámbito> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-biblioteca</artifactId> <version>1.3</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-couchbase</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-test</artifactId> <version>${kotlin.version}</version> <scope>pruebaámbito> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-allopen</artifactId> <version>1.2.41</version> </dependency> </dependencies> |
Puede ver toda la pom.xml aquí.