Servicios Angular: providedIn y providers

logo de angular

Estoy jugando con los Módulos en Angular. Y como siempre, me gusta hacer un ejemplo lo más sencillo posible para asentar los conceptos. Mi ejemplo: un módulo principal «tonto» que importe un módulo «Greeting» y lo use.

Como los módulos teóricamente sirven para agrupar componentes y exponer solo algunas de ellas, pues a «Greeting» le pongo un servicio GreetingService que devuelva un «Hola» y un componente GreetingComponent que lo muestre en pantalla. La idea es exportar el componente ocultando el servicio al módulo principal.

Sin problemas por el lado del componente. En el fichero greeting.module.ts basta poner en declarations y en exports el componente GreetingComponent.

@NgModule({
  declarations: [GreetingComponent],
  exports: [GreetingComponent],
  ...
})
export class GreetingModule { }

Pero con los servicios… ¡Menudo lío!. Todo esto me dio cierta guerra. Estaba empeñado en ocultar el servicio salvo que el módulo lo exportara, pero no es posible. Los servicios no tienen nada que ver con los módulos.

Cuando declaramos un servicio, podemos ponerle providedIn y decirle dónde se registra. Los valores pueden ser

  • root: Se instancia el servicio una sola vez y está disponible para cualquiera que quiera usarlo
  • Un nombre de módulo, componente, etc: Se instancia para ese módulo o componente y está disponible para él. Sin embargo, cualquiera puede usar el servicio y se crearán instancias nuevas según se vayan diciendo que se necesita, a nivel de módulo o componente.
  • Si no se pone nada, no se instancia. Se creará una instancia para cada uno que lo necesite.

Así que providedIn sirve básicamente para decir dónde está visible el servicio por defecto y si queremos o no instancias distintas cada vez que alguien lo pida. Y no tiene nada que ver con encapsulación. El servicio estará disponible para quien lo pida, sea o no del módulo, componente o lo que sea.

Y la forma de pedir un servicio es con providers. El componente o módulo que necesite ese servicio, sólo tiene que ponerlo en su array de providers. Se creará una instancia para él o no según hayamos indicado en providedIn.

Publicado en angular | Etiquetado , | Deja un comentario

Librería collections de Python

python

Pues sigo con ChatGPT de ayudante y he encontrado una forma que me ha gustado. En vez de preguntarle algo concreto simplemente le digo algo como «dame un tip de programación en python». Y te cuenta «cosas». Y está bien, porque te cuenta cosas de python de forma aleatoria (o no) y a veces son cosas que no conocías. No son cosas complejas, nada que no puedas mirar por google en un minuto, si las buscas. La gracia es que te dice cosas que quizás no sabías que existían y que por tanto no ibas a buscar salvo que te surgiera una necesidad concreta. Es una forma de aprender cosas nuevas.

Una de las cosas que me ha contado es sobre algunas de las clases de librería collections de Python. Me ha contado cuatro clases de ahí: Counter, defaultdict, namedtuple y deque.

  • Counter nos ayuda a contar elementos de una lista que cumplan determinadas condiciones.
  • defaultdict es como un dict pero nos devuelve un valor por defecto si accedemos a una clave que no existe, en vez de dar un error.
  • namedtuple es una tupla a la que podemos acceder a sus elementos por un nombre en vez de por índice.
  • deque es una cola eficiente para añadir y retirar elementos de los dos extremos.

Y el artículo con los detalles, escrito por ChatGPT pero con algunos arreglos míos está en Uso de la librería collections en python.

Los retoques míos son más que nada por dar un poco de claridad. He revisado los ejemplos y visto que funcionan, pero leyendo tal cual lo que me contaba ChatGPT no me quedaba lo suficientemente claro, así que decidír reescribir o ampliar algunas definiciones que daba.

Publicado en python | Etiquetado , , | Deja un comentario

Decoradores de TypeScript y ChatGPT

logo de angular

Con esto de Angular, me he puesto a escribir cosas de TypeScript. En concreto, sobre Decoradores de TypeScript. Y ya puestos, pues con ChatGPT le voy pidiendo ejemplos de código y explicaciones, para llevarlos a mis pruebas TypeScript en github.

Pues bien, todo más o menos «sobre ruedas», por decir algo. ChatGPT ayuda bastante y soy más rápido haciendo mis ejemplos y luego redactando en la wiki.

Pero no es oro todo lo que reluce. Hay varios tipos de decoradores, de clase, de atributo de clase, de método y de parámetro de método.

Quise hacer a modo de prueba un decorador de forma que cuando alguien haga un new de la clase decorada, saque un log de que se ha hecho el new. Buscando por google y el ejemplo de ChatGPT te lo ponen siempre más o menos igual: una función decoradora que saca el log, una clase decorada y se hace un new para ver que sale.

Pero ni la mayoría de los ejemplos/tutoriales de google ni ChatGPT te dicen dos cositas:

  • Al decorador se le llama una sola vez y es cuando se declara la clase. No cuando se instancia. En los ejemplos hacen un solo new y sale el log del decorador, todo aparentemente «guay». Pero si no haces ningún new también sale. El log del decorador sale independientemente de si haces new o no haces new y si haces un new o quince news. Sale una sola vez. Siempre.
  • La sintaxis que te ponen es experimental, así que ni siquiera compila. Tienes que habilitar la opción experimentalDecorators.

Así que se sigue investigando y se descubre el truco. Para nuestro ejemplo, el decorador debe devolver una clase hija de la original, con un constructor nuevo que saque el log que queremos por pantalla y llame al constructor de la clase padre original.

Y algo parecido pasa con los decoradores de método. Debes devolver una función alternativa que haga lo que tú quieres (el log en nuestro ejemplo) y llamar a la función original.

Y con los atributos, un poco más raro. Debes definir un método set() y get() para el atributo que haga lo que tu quieres y asignarlo al atributo en un PropertyDescriptor. Debes definir los dos, aunque sólo quieras hacer algo en el set() o en el get()

Y la liamos con los decoradores de parámetro de método. Le pido a ChatGPT el código para hacer log cada vez que llamen a un método que tenga un parámetro decorado, me lo hace. Y no funciona. Miro ejemplos por internet y todos se quedan en lo básico, sacar un log cuando llaman al decorador la primera y única vez que lo llaman. Media hora de charla con ChatGPT para intentar arreglar el código, no hay manera.

Así que me fuí a la documentación oficial: Parameter Decorators y veo un ejemplo rarísimo, además mezclado con un decorador de método. Y mirando con más calma, veo este aviso

NOTE  A parameter decorator can only be used to observe that a parameter has been declared on a method.

¡Aclarado!. El decorador de parámetro básicamente no sirve para nada :). Sólo te avisa que un parámetro ha sido decorado, pero no hay forma de que te avise cada vez que se llame a un método que tenga el parámetro decorador para hacer algo como sacar log o cambiar algo. De ahí que tengas que mezclarlo con un decorador de método:

  • El código que tienes que hacer en el decorador de parámetro es guaradarse en algún sito que el parámetro ha sido decorado con tal decoración. Una tabla o mapa donde pongas nombre del parámetro, de qué clase y método y qué decoración tiene.
  • Y luego tienes que hacer un decorador del método que devuelva un método alternativo que consulte eso que te has guardado para hacerle la lógica al parámetro.

Y ChatGPT, conmigo, emperrado en arreglar un código que no funciona. Y básicamente porque los montones de ejemplos que hay por internet tampoco han profundizado un poco en el tema, se quedan en que me avisa (una sola vez) cuando decoro el parámetro, independientemente de que lo use o no.

Publicado en TypeScript | Etiquetado , , , | Deja un comentario

EditorConfig

EditorConfig

Me he puesto a juguetear con Angular, una de mis asignaturas pendientes.

El primer paso, ¿cómo no?, crear un «Hola Mundo». Pero el comando ng new my-app crea demasiados ficheros y demasiadas cosas. Incluso con las opciones para hacer el «Hola Mundo», lo más reducido posible, como –minimal

Así que me pongo a revisar ficheros que crea para ver de cuáles puedo prescindir, que no sean estrictamente obligatorios para un «Hola Mundo». Y me encuentro con el fichero .editorconfig

Aparentemente es un fichero en el que se define el estilo que el editor que uses debe poner en tu código fuente. Pero ¿qué editor? ¿uno concreto? ¿todos?. Así que indago…. y resulta que la mayoría de los IDES lo soportan

En algunos viene incorporado «de fábrica», desde editores «simple» como vim o IDEs completos, como IntelliJ. Para otros hay que intalar un plugin como eclipse, netbeans o notepad++

El fichero tiene una sitaxis relativamente sencilla. Para cada extensión de fichero podemos decir el tipo de retorno de carro a usar, como hacer los sangrados (espacios, tabuladores, de qué tamaño), la codificación de caracteres a utilizar y poco más.

Puedes ver más detalles en EditorConfig.

Publicado en eclipse, IntelliJ | Etiquetado , , , , , | Deja un comentario

Map.computeIfAbsent()

Logo de Java

Aunque está ya desde Java 8, acabo de descubrir el método computeIfAbsent() de Map

Hay veces que en mi código tengo un Map cuya clave es cualquier cosa, un String por ejemplo, y que el valor es a su vez otra Collection, por ejemplo, una List

Map<String, List<Integer>> myMap = new HashMap<>();

Cuando llegaba el momento de meter un nuevo Integer en la lista valor, siempre había que comprobar primero si había o no lista para crearla en caso de que no. Luego ya se podía añadir el Integer

if (null == myMap.get("key1")) {
   myMap.put("key1", new ArrayList());
}
myMap.get("key1").put(anInteger);

Pues bien, el método computeIfAbsent() nos facilita esta tarea. El código quedaría

myMap.computeIfAbsent("key1", k->new ArrayList()).add(anInteger);

Básicamente, si el valor de «key1» existe, devuelve el valor List que corresponda. Si no existe, llama a lo de new ArrayList(), lo mete en el Map y lo devuelve

En cualquiera de los dos casos, devuelve la List. Ya solo nos queda llamar al método add() del List devuelto para meter el entero.

Una de las ventajas de tener en el IDE un analizador estático de código, estilo SonarLint y fijarse en las sugerencias que te hace.

Publicado en java | Etiquetado | Deja un comentario

Proguard con Maven y con Gradle

Una asignatura pendiente que tengo es ofuscar el código con Proguard. Dos «requisitos»

  • Que se haga automáticamente durante el proceso de desarrollo. Cada vez que compile y genere jars, que se generen los ofuscados.
  • Y con el requisito anterior, que mi entorno de desarrollo use los jar ofuscados directamente. De esta forma, sobre mi entorno de desarrollo, según desarrollo y pruebo, veo si el ofuscado es correcto y todo funciona sin problemas.

Así que me decido a hacer el experimiento tanto con Gradle como con Maven en mi IntelliJ IDEA.

Proguard con Maven

Tienes el resultado en Proguard con Maven. Me llevó un rato, pero sin demasiados problemas.

  • Poner el Plugin proguard-maven-plugin
  • Decir que se ejecute durante la fase package de Maven
  • Poner el Plugin build-helper-maven-plugin para indicar que el jar ofuscado generado, con apellido classifier small debe ser tratado como un segundo artifact del proyecto

Cree un proyecto maven padre con dos hijos, el de la librería que quiero ofuscar y del que he contado todo esto y luego un segundo hijo con un proyecto main que usa la librería ofuscada. Ahi puse la dependencia del jar ofuscado, básicamente, añadiendo <classifier>

IntelliJ me costó un poquito más configuarlo. Básicamente en la configuración de Run tuve que poner que ejecutara la tarea maven verify antes de la tarea build propia de IntelliJ. Eso me aseguraba que se construía el jar ofuscado cada vez.

Y todo bien. Toco los fuentes de mi código que se va a ofuscar, ejecuto desde IntelliJ el main y todo va bien. Se ejecuta Proguard y el main corre con el código ofuscado (lo veo en el log, por el nombre de las clases y métodos

09:32:41.606 [main] INFO com.chuidiang.examples.proguard_library.a.a – Toy sumando numeros

Fíjate que saca el log con el paquete »a», clase »a»

Proguard con Gradle

Esto si fue una odisea. Son las cosas de Gradle. Muy versátil, tanto, que cuesta hacerlo funcionar. Tienes el resultado en Gradle con Proguard.

Lo primero, que no me pasó con Maven, es que tras poner el plugin proguard-gradle y configurarlo, es que daba un error sin ninguna explicación. Poniendo la opción -i de gradle y verbose en la configuración de Proguard, ya si sale el error:

java.io.IOException: Please correct the above warnings first

y no hay más errores. La opción de gradle –stacktrace muestra la excepción pero tampoco da pista. Por un IOException esperaba que me diera el nombre de un fichero o qué IO le estaba dando el error.

Google y resulta que hay que poner en un libraryjars de Proguard las dependencias que hayas puesto en tu fichero build.gradle. Como no queremos ponerlas una a una, más google y encuentras un «gradle’s truco»

libraryjars configurations.findByName(‘runtimeClasspath’).getFiles()

Vale, ya soy capaz de crear el jar ofuscado con el comando gradle y lanzando la tarea de ofuscación que he creado. Ahora me queda hacer que se ejecute automáticamente al generar el jar. Sin mucho problema.

Ahora viene la parte de IntelliJ. El primer problema es la dependencia. Si en mi subproyecto de main pongo la depencia de esta forma

implementation project(‘:library’)

No soy capaz de decirle que use el jar ofuscado en vez de el normal. O al menos, no he encontrado la forma. Y si lo pongo como jar de terceros

implementation «com.chuidiang.proguard:library:${project.version}:small»

Pues gradle no lo encuentra. Para Gradle es neceario que este jar esté un un repositorio accesible (maven central, maven local, ….). Así que la solución pasa por poner el plugin maven-pulish , configurar los publishing y hacer que se publique ese jar en maven local al compilar.

Así que toca el lío de que la tareas proguard y publishMavenPublicationToMavenLocal se ejecuten en el orden adecuado de forma automática.

Con todo esto, al final quedó conseguido. En IntelliJ, en la configuración de Run, tuve que añadir la tarea previa a la ejecución de gradle jar. Esta tarea es estándar de gradle y tal cual hemos configurado en build.gradle es la que lanza todo lo demás (proguard y publish).

Resumiendo

Resumiendo, lo habitual. Tanto con Gradle como con Maven conseguí hacerlo. Con Maven me llevó una horita tenerlo todo montado y funcionando. Con Gradle un día entero.

Publicado en gradle, maven, proguard | Etiquetado , , | Deja un comentario

Java switch e instanceof Pattern Matching

Logo de Java

LLevo programando en Java lo menos desde el 2000. Recuerdo empezar con Java 1.2, que pasó a llamarse Java 2 y que tenía Swing como gran novedad, «reemplazando» AWT. Desde entonces y de forma más o menos contínua, en el curro programo en Java.

Todo esto hace que tenga ya mis formas de hacer las cosas, con las que me encuentro cómodo, y que no ande muy pendiente de las nuevas novedades que van saliendo en las nuevas versiones. Pero me voy tropezando con código que hace gente más joven o que encuentro por internet. Aunque algunas cosas me parecen alternativas dudosas, como el tema de lambdas y fluent apis, otras veo que claramente son útiles.

Y aunque son «antiguas» (Java 17), dejo aquí un par de mejoras en instanceof y en switch

Java Instanceof Pattern Matching

Algo frecuente de tener que hacer es comprobar el tipo de una objeto que te llega para convertirlo al tipo adecuado. En mi forma tradicional, hacía cosas como esta

if (value instanceof String) {
   String aString = (String)value;
   // Usamos aString como String en lo que necesitemos
}

Desde Java 17, ahora es posible hacerlo de una forma más directa

if (value instanceof String aString) {
   // Nos ahorramos el cast, ya tenemos aString como String
}

Java Switch Pattern Matching

Quizás no tan frecuente, pero relacionado con lo anterior. Si tenemos posibilidades de que el objeto que nos pasan sea de varios tipos posibles, antes había que hacer un if para casa caso. Ahora switch nos permite hacerlo más fácilmente.

switch (value) {
    case Integer anInteger :
        System.out.println("Es entero "+anInteger);
        break;
    case String aString:
        System.out.println("Es string "+aString);
        break;
    case Double aDouble :
        System.out.println("Es double "+aDouble);
        break;
    default:
        System.out.println("Ninguno de los anteriores");
}

Así que ya sé alguna nueva cosa útile que ahorra código y, lo más importante, el firme propósito de revisar las novedadees tanto de las versiones de java que no he revisado como de las nuevas.

Publicado en java | Etiquetado , , , | Deja un comentario

Record en Java 14

Logo de Java

No suelo mirar en cada versión de Java qué novedades hay. Más bien me suelo tropezar con ellas. Y la última con la que me he tropezado son los record, de java 14 en adelante

Qué es un record en Java. Un ejemplo de record

public record Registro(int a, String b) {
}

Esto crea una clase Java, de nombre Registro, con dos campos int a y String b. Pero esa clase tiene una serie de particularidades

Tiene un constructor con dos parámetros, a y b

Los campos a y b son inmutables, es decir, una vez construida la clase, no podemos cambiar su valor

Tiene getter, aunque no cumplen la nomenclatura estándar. Son registro.a() y registro.b(). Como son inmutables, no hay setter

Tiene una implementación por defecto de hashcode(), equals() y toString() que contemplan todos los campos (a y b)

Para qué sirve un record en Java

Pues tiene una utilidad muy relativa. Es una forma sencilla de construir una estructura de datos que no se pueden modificar

Así que sirve principalmente para pasar datos de un lado a otro, pero que no necesiten ser modificados, ya que es inmutable. Y para esos casos, es una forma sencilla de definir esa estructura de datos.

Constructores y métodos en un record en Java

Podemos dar lógica al constructor por defecto de record, que lleva dos parámetros o definir otros constructores

public record Registro(int a, String b) {
    public Registro(int a, String b){
        if (null==b){
            throw new IllegalArgumentException();
        }
        this.a=a;
        this.b=b;
    }
    public Registro (int a) {
        this(a,"");
    }
    public Registro(){
        this(0,"");
    }
}

Hemos añadido lógica al constructor con dos parámetros para que no admite un null como parámetro b. Hemos añadido un constructor con un solo parámetro y sin parámetros, que llaman al constructor con dos parámetros dando valores por defecto.

Podemos también añadir otros métodos. Lo único es que en esos métodos no podemos modificar los valores de a y b, ya que internamente están definidos con final.

Publicado en java | Etiquetado , , | Deja un comentario

Apache Kafka Listeners y Advertised Listeners

Logo de Apache Kafka

He empezado a jugar, investigar y escribir alguna cosilla sobre Apache Kafka. Lo utilicé hace ya unos años, pero siempre me lo habían dado configurado y yo únicamente programaba clientes productores/consumidores.

Al ponerme a investigar cómo instalar Apache Kafka con Docker me he encontrado con un montón de parámetros de configuración, algunos de ellos muy parecidos y poco clara su utilidad, en concreto KAFKA_LISTENERS, KAFKA_ADVERTISED_LISTENERS y KAFKA_LISTENER_SECURITY_PROTOCOL_MAP. Detrás de ellos, unos cuantos derivados como KAFKA_INTER_BROKER_LISTENER_NAME y KAFKA_CONTROLLER_LISTENER_NAMES

Vamos a ver qué son y por qué tantos

Problema con los Listener de Kafka

Si instalamos uno o más nodos de Apache Kafka en cluster, dentro de una única red y en el que todos los clientes van a acceder también desde dentro de esa red, no hay mucho problema. Cada nodo estará accesible desde kafkaN:9092 o como queramos configurarlo, siendo kafkaN el nombre del nodo concreto en la red y 9092 el puerto que queramos poner.

El problema surge si varios de esos nodos están en redes distintas y/o los clientes acceden desde redes distintas. Es posible que el acceso a un nodo no sea igual desde dentro de una red que desde otra.

Un ejemplo claro es si montamos en cluster de nodos Kakfa en una red de contenedores docker pero algún cliente va a correr en el host anfitrión. En este caso, es normal publicar el puerto 9092 en el host anfitrión con la opción -p 9092:9092 del contenedor. Desde dentro de la red de dockers podemos acceder al nodo con kafkaN:9092, pero desde el host anfitrión accederíamos con localhost:9092.

Hasta ahora esto no sería un problema si no fuera por el siquiente comportamiento de Kafka. Cuando el cliente se conecta desde el host anfitrión con localhost:9092, lo primero que hace es preguntarle a Kafka cual es el nodo principal y Kakfa le contesta con la IP/nodo y puerto del nodo principal. En nuestro ejemplo, le contestaría con kafkaN:9092. Nuestro host anfitrión iría a ese nodo:puerto a seguir la comunicación, pero no la tiene accesible.

Veamos como resolver todo esto con los parámetros de configuración de arriba.

KAFKA_LISTENERS

En esta propiedad ponemos los puertos e interfaces de red por los que tiene que escuchar el broker de Kafka. Podemos poner tantos como nos hagan falta y en la misma propiedad ponemos un nombre de nuestro gusto a esa esccuha. Por ejemplo

KAFKA_LISTENERS: ‘LISTENER_INTERNO://:9094,LISTENER_EXTERNO://:9092’

Hemos dicho a Kafka que abra dos puertos, el 9092 y el 9094. Para identificarlos en las siguientes propiedades, les hemos puesto los nombre LISTENER_INTERNO, para la red interna de dockers y LISTENER_EXTERNO, para el host anfitrión. Si tenemos otras configuraciones de red o necesidades, podemos definir aquí los que queramos con los nombres y puertos que queramos.

En el formato podríamos poner, por ejemplo, LISTENER_INTERNO://<IP>:9094 siendo la IP la de la interface de red por la que queramos que se ponga a la escucha. Si no ponemos nada (como en el ejemplo) o ponemos 0.0.0.0, se pone a la escucha de todas las interfaces de red disponibles.

KAFKA_ADVERTISED_LISTENERS

En esta propiedad debemos decir qué IP y puerto debe comunicar Kaka a sus clientes para que puedan conectarse correctamente. Esta IP puerto puede ser distina según sea alguien interno a la red de dockers o alguien del host anfitrión.

Imagina que usando las opciones de docker, hemos publicado el puerto 9092 en el puerto 29092 del host anfitrión y que al host docker le hemos puesto de nombre kafkaN. Esa propiedad podría quedar así

KAFKA_ADVERTISED_LISTENERS: ‘LISTENER_EXTERNO://localhost:29092,LISTENER_INTERNO://kafkaN:9094’

Para los que se conecten al puerto 9092 (LISTENER_EXTERNO, es decir, host anfitrión), KAFKA les dirá que deben hacerlo a través de localhost:29092. Para los que se conecten al puerto 9094 (LISTENER_INTERNO, es decir, dentro de la red interna de docker) deben hacerlo con kakfaN:9092

KAFKA_INTER_BROKER_LISTENER_NAME

Los distintos broker de Kafka se conectan entre ellos para intercambiar información. Como hemos abierto dos puertos, uno para listener internos y otro para externos, debemo indicar aquí a Kafka cuál debe usar para interconexión entre nodos. Habitualmente es el de la red interna. Un ejemplo podría ser como lo siguiente

KAFKA_INTER_BROKER_LISTENER_NAME=LISTENER_INTERNO

KAFKA_CONTROLLER_LISTENER_NAMES

Este es sólo necesario si usamos KRaft en vez de Zookeeper. Usando KRaft, los mismos servidores Kafka tienen un protocolo entre ellos para decidir cual de ellos hace de controlador principal del cluster.

Para los controladores debemos seguir una lógica similar a los Listener. Es decir

  • En KAFKA_LISTENER definir un puerto adicional para atender a los demás broker del cluster. Además de los dos que ya tenemos, añadimos algo como CONTROLLER://9093.
  • En KAFA_CONTROLLER_LISTENER_NAMES debemos indicar cual de los tres listener que hemos abierto (interno, externo y controller). Es decir, KAFA_CONTROLLER_LISTENER_NAMES=CONTROLLER

Publicado en Kafka | Etiquetado , | 2 comentarios

Jugando con Elasticsearch y Java

En el curro estamos empezando a usar Elasticsearch para algunos temas. Como a mí en concreto no me ha tocado, me ha dado por hacer algunas pruebas por mi cuenta, desde Java, y así aprender lo básico.

Llevo un par de días con ellos … y no sé si me convence. No Elasticsearch en sí, no he hecho pruebas de rendimiento, ni con clusters ni nada de nada. Sólo con la API de Java.

¿Y por qué no me convence?. Varios motivos.

Fluent builders de la API Java de Elasticsearch

La web de Elasticsearch, respecto a la nueva API de Java, pone

Use of fluent builders and functional patterns to allow writing concise yet readable code when creating complex nested structures.

básicamente, que como se usan «fluent builders», las estructuras complejas anidadas se hacen más concisas y legibles. Aquí un ejemplo de código de la documentación.

// Search by product name
Query byName = MatchQuery.of(m -> m 
    .field("name")
    .query(searchText)
)._toQuery(); 

// Search by max price
Query byMaxPrice = RangeQuery.of(r -> r
    .field("price")
    .gte(JsonData.of(maxPrice)) 
)._toQuery();

// Combine name and price queries to search the product index
SearchResponse<Product> response = esClient.search(s -> s
    .index("products")
    .query(q -> q
        .bool(b -> b 
            .must(byName) 
            .must(byMaxPrice)
        )
    ),
    Product.class
);

Pues como promete la documentación, es una sintaxis «sencilla» y sobre todo «legible» para consultar productos con un nombre concreto y un precio máximo. No quiero ni imaginarme cómo será una consulta con más condiciones.

Documentación escasa

La documentación oficial es muy escueta, poco o nada didáctica. Las búsquedas por google tampoco dan mucha más información más allá de la documentación oficial. Quizás porque esta API de Java es relativamente nueva en Elasticsearh, antes se usaba directamente una API REST.

Si te fijas en el ejemplo anterior, para buscar por nombre usa MatchQuery y para lo del precio máximo RanqeQuery. Más allá del Javadoc, no he encontrado un tutorial «didáctico» donde te explique las alternativas que hay, cómo se usan, etc.

En fin, toda una odisea de búsqueda de documentación, prueba, ensayo y error como quieras hacer algo más allá de una consulta simple.

Aunque no sea exactamente de la API de Java, reto a alguien a que me diga que roles son los que hay que poner al crear un usuario que quieras que haga las típicas operaciones CRUD. Aquí tienes la lista de roles predefinidos.

Dichosa caché

En mis pruebas, un main sencillo, todo seguido: crear un índice, insertar, hacer un search para consultar lo insertado …. y la consulta no devuelve nada. Mirando directamente con el navegador web conectado a la URL de Elasticsearch veo que los datos sí se han insertado.

Un buen rato probando la consulta, por id de objeto insertado con get() funciona, con search() y condiciones no funcina, pongas lo que pongas…. pero de forma aleatoria, alguna vez sí va.

Así que sospechando de algo similar al commit que se hace con las tradicionales SQL, veo que hay un elasticsearchClient.indices().flush(). Tampoco va. Pero si después de eso pones un sleep() entonces ya funciona todo.

Es decir, tiene pinta de que en el mismo programa, con la misma instancia de elasticearchClient hay alguna caché que es distinta para cada operación y lo que insertas en una línea, no está disponbile para consulta en la siguiente.

Hay dos clases cliente de Elasticsearch. Una es síncrona y la otra asíncrona. La síncrona teóricamente te devuelve el control una vez ha terminado la operación, la asíncrona te lo devuelve inmediatamente aunque no haya terminado la operación.

¿Con cual de las dos crees que me estaba pasando lo de insertar y no tener disponibles los resultados?

¡¡Efectivamente, lo has adivinado!!. ¡¡Con la SINCRONA!!.

Una lanza a favor de Elasticsearch

En el curro, como comento, lo están usando. Pero lo usan en combinación con Kibana, o con clusters de nodos por el tema de alta disponibilidad, etc. Parece que en ese sentido la gente está muy contenta.

El uso que están dando es el almacenamiento masivo de información procedente de sensores desde uno o varios ejecutables separados. Luego con Kibana o bien con una aplicación hecha a medida, las consultas de esos datos históricos. Con Kibana no hay que pelearse con las consultas. Con la aplicación a medida seguramente topes con el primer problema que he comentado, pero todo es cuestión de pelearse y acostumbrarse.

En cuanto a cluster de nodos, parece que dos grupos de trabajo que lo han probado por separado e implementado en sus proyectos han quedado contentos con su comportamiento.

Conclusión

Pues lo dicho, no parece adecuada para uso como una base de datos de trabajo normal. Pero sí parece adecuada para meter muchos datos en cluster y análisis a posteriori de dichos datos, bien con Kibana, bien con aplicaciones a medida.

Publicado en elasticsearch | Etiquetado , | 1 comentario