Agregar una entidad al sistema de versionado de entidades
En esta guía vamos a explicar cómo añadir una nueva entidad de la Config DB al sistema de versionado de plataforma.
La funcionalidad que sustenta el sistema de versionado de recursos es bastante genérica, y está basada en el uso de la interfaz Versionable<T> , por lo que el esfuerzo que se requiere para añadir una entidad a este sistema es mínimo. A continuación detallaremos los pasos a seguir.
1. Implementación de la interfaz Versionable<T>
public interface Versionable<T> {
public default String serialize() throws IOException {
return new YAMLMapper().writeValueAsString(this);
}
@SuppressWarnings("unchecked")
public default T deserialize(String content) throws IOException {
return (T) new YAMLMapper().readValue(content, getClass());
}
public String fileName();
public Object getId();
}
La interfaz Versionable sirve para indicar que una clase, en este caso una entidad JPA, debe pertenecer al sistema de versionado de la plataforma. En dicha interfaz se indican 4 métodos genéricos que serán necesario para las diferentes fases del versionado: serialización, deserialización, restauración…
Los métodos serialize y deserialize se implementan por defecto, ya que para la mayoría de entidades (entidades sin relaciones JPA o sin código HTML) no hará falta sobreescribirlos. Un caso ejemplo de entidad que necesitaría sobreescribir estos métodos sería Dashboard o Ontology.
En el caso de Ontology se necesita sobreescribir para evitar que en la fase de serialización a Yaml cargue por completo las relaciones. Las relaciones siempre las representaremos por el ID único de base de datos, por ejemplo (Ontology.java):
@Override
public String serialize() {
final YAMLMapper mapper = new YAMLMapper();
final ObjectNode node = new YAMLMapper().valueToTree(this);
node.put("dataModel", dataModel == null ? null : dataModel.getId());
node.put("ontologyKPI", ontologyKPI == null ? null : ontologyKPI.getId());
node.put("ontologyTimeSeries", ontologyTimeSeries == null ? null : ontologyTimeSeries.getId());
final ArrayNode n = mapper.createArrayNode();
ontologyUserAccesses.forEach(oua -> n.add(oua.getId()));
node.set("ontologyUserAccesses", n);
try {
return mapper.writeValueAsString(node);
} catch (final JsonProcessingException e) {
return null;
}
}
Si os fijáis las relaciones con DataModel, OntologyKPI, etc. , que a su vez son entidades, se sustituyen por el ID de la entidad en cuestión, si es que existe.
En el caso de Dashboard, lo sobreescribimos para representar el HTML dentro del Yaml correctamente (escapándolo con un wrapper, clase HTML.java)
@Override
public String serialize() throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final Map<String, Object> map = mapper.convertValue(this, new TypeReference<Map<String, Object>>() {});
map.put("headerlibs", new HTML(headerlibs));
prettyHTML5Gadget((Map<String, Object>) map.get("model"));
return VersioningUtils.versioningYaml(this.getClass()).dump(map);
}
La clase HTML tiene asociada un Tag yaml -!html- que se usa para representar e interpretar el contenido en la serialización y deserialización del fichero, se puede ver en la última línea del método: VersioningUtils.versioningYaml(this.getClass()).dump(map);
Dicho esto, el primer paso es implementar esta interfaz, tomaremos como ejemplo la entidad API.
A continuación tendremos que implementar los dos métodos de la interfaz que no tienen un default, getId() y fileName(), el primero no hará falta ya que al ser entidades del sistema, todos tienen un atributo id (salvo User por ejemplo). Con lo cuál automáticamente se detecta por la interfaz.
En el método fileName() lo que se devuelve es el nombre del fichero donde quedará la entidad serializada, la convención que estamos usando es identification + _ + id + .yml, para las entidades que tengan ambos atributos, si no únicamente se usa el id + .yml
A continuación identificamos los atributos de la entidad que son relaciones JPA, en el caso de Api serían: ontology y userApiAccesses. Estos serán los atributos que nos obligarán a sobreescribir el método serialize, y como dijimos anteriormente, tenemos que sustituirlos por el Id en caso de existir, por lo que quedaría así:
Ahora para el proceso de deserialización, tendremos que tener en cuenta que hemos sustituido entidades por un String id, por lo que tendremos que indicar como deserializar esos campos: ontology y userApiAccesses.
Para esto, la mejor opción es crear un @JsonSetter ya que estamos utilizando la librería de Jackson internamente. Por ejemplo para estos dos atributos crearíamos los siguientes:
Con establecer el Id en la entidad correspondiente (p.e. Ontology) es suficiente, no hace falta traerse la entidad entera de base de datos, es una de las ventajas de JPA.
2. Añadir repositorio a VersioningRepositoryFacade
Existe un repositorio Facade que gestiona los diferentes repositorios de las entidades versionable, para poder ser invocado de manera sencilla por las capas de servicio superiores.
Tendríamos que añadir a la clase VersioningRepositoryFacade lo siguiente para el caso de Api.
3. Métodos en el repositorio
Es necesario que el repositorio de la entidad Versionable implemente el siguiente método:
En algunos casos de modelo de entidad-relación complejos, se tendrá que implementar un default, por ejemplo en realms (App.java)
4. Consideraciones extra si la entidad no extiende de OPResource
1. Find By User
Para el caso de entidades versionables que no extiendan de OPResource habrá dos casos:
La entidad tiene atributo user
En este caso tendremos que ir al repositorio de la entidad, tomemos como ejemplo DashboardUserAccess y DashboardUserAccessRepository, y asegurarnos de que existe el método findByUser, si no, lo creamos:
La entidad no tiene atributo user
En este caso iremos al repositorio de la entidad, si podemos hacer una query que relacione un usuario con la entidad la crearemos, y si no, crearemos el método findByUser como un default tal como en los siguientes ejemplos:
Esto es necesario para que funcionen ciertas funcionalidades para usuarios no administradores.
2. Find versionable views
Se tendrá que añadir el método findVersionableViews, tómese el siguiente ejemplo. Se necesita pasar como argumento, la identifcación, id y nombre de la clase al constructor.