CounterLocalService - Solución elegante a un problema habitual

No te ha pasado nunca que usas algo habitualmente que te descuadra, que tiene algo que no entiendes del todo. Pues esa ha sido mi experiencia con el servicio de Counter de Liferay. Por ello me he sumergido en el código para entender el funcionamiento completo y así poder dormir tranquilo de nuevo wink

¿Para qué sirve?

El servicio Counter nos permite obtener el ID de forma automática para una nueva Entidad al guardarla en BBDD. Más concretamente al definir una nueva entidad con Service Builder en Liferay suele ser habitual no añadir ningún tipo de secuencia o autoincremento a la clave primaria de nuestra Entidad (esto es debido principalmente a que no todas las BBDD cuentan con todas estas opciones).

Al no tener autoincremente de ningún tipo en la clave primaria de nuestra Entidad necesitamos algún método para generar ese ID y no asignar el mismo número a dos entidades distintas.

Para ello se utiliza habitualmente el servicio Counter para obtener este número. Más concretamente se suele utilizar CounterLocalServiceUtil.increment(miClase.class.getName()). Este método devuelve un identificador distinto cada vez que se invoca y nunca se repite. Parece magia, su funcionamiento es bastante simple.

 ¿Que ves?

Una vez que conoces esto (y sobre todo para hacer pruebas) sueles buscar la tabla de BBDD donde se almacena el número por le que va para tu entidad. Rápidamente localizas la tabla counter que contiene las columnas name y currentId. En la columna name se almacena la clase para la que se guarda el número actual (en nuestro caso el nombre canónico de miClase) y la columna currenId almacena el número actual.

Ya aquí las cosas empiezan a no cuadrar, ya que ves que para tu clase el currentId vale 200 y la última entidad guardad tiene el ID 210.

Peor se pone cuando de forma (aparentemente aleatoria) los ID's pegan saltos de 100 números. 

De esta forma nunca estás seguro de que número asignar al currentId en BBDD cuando cargas entidades a mano o que ID poner en tus nuevas entidades insertadas manualmente.

Pues todo esto tiene un sentido y ahora verás cual es.

¿Cómo funciona?

Una vez que accedes al código fuente de la implementación del servicio y profundizas un poco en el descubres que el funcionamiento es mucho más simple del que parece.

Cuando arranca el servidor de Liferay este crea una clase singleton donde almacenará un mapa con todos los currentId utilizables (este mapa estará vacío en el arranque). En dicho mapa se almacena también el currentId base (que es el obtenido de BBDD) y el incremento (vamos a llamarlo) por salto.

Una vez que se llama al método CounterLocalServiceUtil.increment para nuestra clase se accede al mapa en busca del currentId para esa clase.

  • Si no se encuentra dicha clave el servicio accede a BBDD en busca el currentId para dicha clase en la tabla counter. Una vez localizado el currentId actual en BBDD se obtiene y se incrementa en función de la base del incremento (es un segundo parámetro que admite el método). Normalmente para un incremento base de 1 el currentId inicial se aumenta en 100. Una vez que se añade el nuevo currentId al mapa de la clase singleton se devuelve el valor actual.
  • Si se encuentra dicha clave se incrementa el currentId obtenido del mapa en función del incremento base y se devuelve. En este punto se comprueba si el nuevo currentId es mayor o igual que el currentId base + incremento por salto. En caso afirmativo se accede a BBDD y se actualiza el currentId de la fila en cuestión (asignando el actual currentId devuelto por el método).

Imagino que explicado de esta forma puede parecer un poco lioso. Veamos un ejemplo con números.

Supongamos que tenemos una entidad propia llamada miClase, para la que hay 120 entidades cuyos ID's van correlativos del 1 al 121. Como hemos utilizando previamente el servicio Counter en la tabla counter tendremos una fila con el nombre canónico de nuestra clase y el valor 100 en el currentId.

Al arrancar nuestro Liferay este crear el singleton con el mapa vacío y al llamar al incremento para nuestra clase (para almacenar una nueva entidad) este incrementa el currentId de BBDD de 100 a 200 y devuelve el ID 200 a nuestra nueva entidad.

Al llamar de nuevo al incremento para una nueva entidad, el método nos devolverá el siguiente ID (201) sin tocar la BBDD. Y eso ocurrirá así para todos los números hasta llegar al 300. En caso de solicitar el incremento y este llegar al 300, ese mismo número se almacenará el BBDD.

Así en caso de reiniciar el servidor de Liferay, este volverá a incrementar a 400 el currentId de nuestra clase en su primera petición.

Conclusiones

Con este diseño se ha conseguido un gran rendimiento en una operación que por su frecuencia de uso es crítica (influye en todas las operaciones de creación de objetos en BBDD). Ya que el valor del ID actual de cada clase lo mantiene en memoria (en la clase singleton) y solo accede a BBDD la primera vez (para cargarlo) y una vez de cada X (en función del incremento base y del salto) para actualizar la BBDD.

El único inconveniente de este sistema es que se pierden varios números cada vez que se reinicia el servidor.

Como reza el título, una solución muy elegante a un pequeño problema de rendimiento jeje.