ElasticSearch con SpringBoot
ElasticSearch es probablemente uno de los motores de almacenamiento de datos orientados a documentos JSON más populares de la actualidad. Basado en Lucene, es un sistema altamente escalable y de alto rendimiento, accesible para su gestión y uso desde un API REST que tiene incorporado.
Una de las características particulares de ElasticSearch es el lenguaje de dominio específico (DSL) que nos facilita realizar todo tipo de consultas. ElasticSearch es una pieza fundamental del stack ELK (ElasticSearch, Logstash, Kibana), tres excelentes herramientas que combinadas ofrece una solución para la creación de gráficas estadísticas, métricas, análisis de logs, etc.
Me gustaría compartirles por cierto una conferencia sobre ElasticSearch que impartía en la universidad donde me formé.
Creando un proyecto con ElasticSearch y Spring Boot
Cómo es de esperar una tecnología con tanto grado de aplicabilidad a diferentes dominios posee integración para trabajar con Spring Boot de forma sencilla, todo posible a spring-starter-data-elasticsearch.
Vamos a desarrollar una aplicación de ejemplo, especie un API de localización, aprovechando las características de datos georeferenciados de ElasticSearch.
En este ejemplo no explicaremos las operaciones sobre los tipos georeferenciados, pero seguiremos en otro artículo tratando el tema y en nuestro modelo pondremos un tipo GeoPoint para continuar más adelante.
Arquitectura de nuestra aplicación
Siguiendo la arquitectura estándar que se sigue comúnmente en las aplicaciones Spring Boot tendremos para nuestra aplicación de ejemplo:
- Modelo: Contendrá nuestra entidad del negocio (Location).
- Repository: Patrón repostorio usando ElastiSearchRepository.
- Service: Patrón Service Layer.
- Controller: El controlador rest que expondrá un API REST
Empezando por el modelo, resultando interesante destacar algunos elementos
@Document(indexName = "locations", type="location")
public class Location {
@Id
private String id;
private String name;
@GeoPointField
private GeoPoint geoPoint;
private String phisicalAddress;
Primero la anotación @Document y el indexName, para indicar que es un documento y que se se guardará en el índice location (Ver la presentación al inicio del artículo para entender la relación entre un índice y una tabla SQL).
El elemento marcado como @Id será el identificador primario del elemento en el índice, es importante saber que si este elemento se llama “id” y es de tipo String gracias a la magia de Spring Boot Starter ElasticSearch la llave que asigna ES se devolverá en ese atributo de la entidad, por lo que posterior a una operación “save” de la entidad tendremos en ese atributo el identificador único del documento guardado.
El otro elemento interesante es el tipo GeoPoint, es un tipo propio de ES para almacenar datos de longitud y latitud.
En el Repository, simplemente extendemos ElasticsearchRepository.
@Repository
public interface LocationRepository extends ElasticsearchRepository<Location, String> {
Optional<Location> findByName(String name);
}
Nuestro servicio no implementa mucha lógica por lo tanto no lo expondré acá.
El controlador REST se muestra a continuación:
@Controller
@RequestMapping("/api/v1")
public class LocationController {
@Autowired
private LocationService locationService;
@PostMapping("/locations")
public ResponseEntity<Location> save(@RequestBody Location location) throws URISyntaxException {
Location res = this.locationService.save(location);
return ResponseEntity.created(new URI("/locations/" + res.getId())).body(res);
}
@GetMapping("/locations")
public ResponseEntity<Page<Location>> findAll(@RequestParam("page") int page, @RequestParam("size") int size){
return ResponseEntity.ok(this.locationService.findAll(size, page));
}
@GetMapping("/locations/{id}")
public ResponseEntity<Location> findById(@PathVariable("id") String id) {
Optional<Location> locationOptional = this.locationService.findById(id);
if (locationOptional.isPresent())
return ResponseEntity.ok(locationOptional.get());
return ResponseEntity.notFound().build();
}
@GetMapping("/locations/byname/{name}")
public ResponseEntity<Location> findByName(@PathVariable("name") String id) {
Optional<Location> locationOptional = this.locationService.findByName(id);
if (locationOptional.isPresent())
return ResponseEntity.ok(locationOptional.get());
return ResponseEntity.notFound().build();
}
}
Cómo se muestra un controlador normal sin nada especial.
Es importante resaltar que nuestra aplicación cuenta con un paquete común donde se guardan los elementos transversales, como por ejemplo la lectura de configuraciones necesarias para conectar con nuestro cluster de ElasticSearch.
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.sacavix.yoandypv.locationapi")
public class EsConfig {
@Value("${elasticsearch.host}")
private String EsHost;
@Value("${elasticsearch.port}")
private int EsPort;
@Value("${elasticsearch.clustername}")
private String EsClusterName;
@Bean
public Client client() throws Exception {
Settings settings = Settings.builder().put("cluster.name", EsClusterName).build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName(EsHost), EsPort));
return client;
}
@Bean
public ElasticsearchOperations elasticsearchTemplate() throws Exception {
return new ElasticsearchTemplate(client());
}
}
La configuración se lee del archivo de configuración YML de nuestro proyecto:
server:
port: 5004
spring:
application:
name: location-api
elasticsearch:
clustername: sacavix
host: localhost
port: 9300
El clustername es el nombre del cluster ES al que nos estamos conectando, en caso de instalar ES en Linux en /etc/elasticsearch/elasticsearch.yml está el archivo de configuración principal en el podemos definir el nombre del cluster, estableciéndolo en la llave cluster.name: sacavix de este fichero.
El código del proyecto está disponible en GitHub.
Si tienes alguna duda, puedes dejar tus comentarios.
1 thought on “ElasticSearch con SpringBoot”