@MappedEntity
public class Customer {
@Id
private Long id;
private String nombre;
private String plan;
...
}
Recientemente he tenido que implementar una movida para uno de mis proyectos
en la que había que persistir en base de datos no sólo los datos típicos del
cliente-usuario, sino que también tenía que guardar el Plan
de subscripción
en el que se encontraba.
La solución por la que optado está bien explicada en las guías de Micronaut pero me he decidido a escribirla aquí por el matiz de "negocio" que creo que puede ser interesante para otras situaciones.
Digamos que tenemos una tabla donde persistimos los datos de un Customer
tipo:
@MappedEntity
public class Customer {
@Id
private Long id;
private String nombre;
private String plan;
...
}
Donde plan
era un string donde se iba a almacenar algun tipo de constante. Como
no había nada en concreto para ello (ni se le esperaba) la idea era un mantenimiento
a mano en la base de datos y en lugar de optar por valores numéricos que no dicen
mucho se optó por un String que era más representativo, con lo que en la bbdd
encontramos registros tipo
1, jorge, free
2, pepe, basic
3, manolito, free
Obviamente una vez que lo tienes funcionando y empiezas a guardar esos valores llega un momento en el que tienes que hacer algo con ese campo que no sea meramente guardarlo.
Así que lo primero que uno piensa es "codificar" los posibles valores de una forma más
decente en lugar de los típicos static final String FREE="free";
y cambiar el tipo
del atributo a un enum
public enum Plan{
FREE("free")
BASIC("basic"),
PREMIUM("premium");
private final String name;
Plan(final String name) {
this.name = name;
}
public String getName(){
return name;
}
@Override
public String toString() {
return name;
}
}
@MappedEntity
public class Customer {
@Id
private Long id;
private String nombre;
private Plan plan;
...
}
Básicamente con esto Micronaut ya es capaz de guardar y recuperar registros de la bbdd
asignando al atributo plan
el enum correspondiente (y dando error si en la bbdd algún
valor no está en el enum) SIN TENER QUE TOCAR LA BASE DE DATOS
Digamos que el "problema" al que me enfrentaba ahora era que, una vez recuperado un customer
de la base de datos, debía conocer en qué plan se encontraba para poder sugerirle los
siguientes planes (por ejemplo). En mi caso era tan "simple" como una serie de ifes pero
el añadir nuevos planes, por ejemplo SUPER
, haría que tuviera que revisar esos ifes
Una de las posibles soluciones, tal vez la más sencilla, sería convertir al enum en un enum complejo tipo
public enum Plan {
FREE("free", 1)
BASIC("basic", 2),
PREMIUM("premium", 3);
private final String name;
private final int level;
Plan(String name, int level) {
this.name = name;
this.level = level;
}
public String getName() {
return name;
}
public int getLevel() {
return level;
}
}
De esta forma ordenar planes en base a su "prioridad" es tan sencillo como ordenar por level
y
así puedes saber qué funcionalidades puede optar, etc.
Micronaut (y supongo que otros frameworks) NO saben (sin ayuda) convertir estos valores y al intentar recuperar de la base de datos algún Customer tendrás errores tipo:
"io.micronaut.data.exceptions.DataAccessException: Cannot convert type [class java.lang.String] with value [] to target type: Plan plan. Consider defining a TypeConverter bean to handle this case."
Como bien nos dice Micronaut, tenemos que añadir un algo que le diga cómo convertir de un String a un Plan y viceversa (que es el objetivo de este post) para lo que hay que seguir estos pasos:
Anotaremos al enum Plan
con un TypeDef
@TypeDef(type= DataType.STRING) (1)
public enum Plan {
...
}
1 | Puesto que partimos de un string en el modelo inicial
|
@Factory
public class PlanConverter {
@Singleton
TypeConverter<Plan, String> planStringTypeConverter(){
return ((object, targetType, context) -> Optional.of(object.name()));
}
@Singleton
TypeConverter<String, Plan> stringPlanTypeConverter(){
return ((object, targetType, context) -> Arrays.stream(Plan.values())
.filter(p->p.getName().equals(object))
.findFirst()
);
}
}
Básicamente el primero dado un Plan devuelve un String y el segundo a la inversa, busca en el array de Plan aquel que coincida su nombre con el String proporcionado
Todo esto viene explicado en la guía de Micronaut Data pero el caso de uso que emplean para explicarlo no me decía mucho pero una vez leído y aplicado a mi problema me ha parecido interesante compartirlo
2019 - 2024 | Mixed with Bootstrap | Baked with JBake v2.6.7 | Terminos Terminos y Privacidad