Clases base JPA
El arquetipo adopta DDD pragmatico: JPA esta permitido en el dominio, y las entidades heredan de una de tres clases base segun quien genera el ID. Esta pagina documenta el contrato de cada una.
Para la decision "que clase uso al crear una entidad", ver Como definir el modelo de dominio.
Las tres clases son variaciones del mismo patron: implementan Persistable<PK>, aportan auditoria y equals/hashCode identicos, y se diferencian solo en el tipo de PK y quien la genera.
AlphaIdEntity
Clase base con PK String alfanumerica generada en memoria por @AlphaIDSequence.
Tipo de PK |
|
Generacion |
Automatica antes del |
Contrato del dev |
Heredar — no declarar |
Caso de uso tipico |
Aggregate root expuesto por API REST (ID no enumerable, mitiga IDOR) |
@Entity
@Table(name = "customers")
public class Customer extends AlphaIdEntity {
// sin @Id — viene del padre
}
Generacion del AlphaID
El generador vive en commons/id/AlphaIdentifierGenerator y se invoca via la anotacion @AlphaIDSequence (un @IdGeneratorType de Hibernate). El algoritmo tiene dos pasos:
-
IdGenerator.generateId()produce unlongviaMath.abs(UUID.randomUUID().MSB ^ LSB)— entropia efectiva ~63 bits. -
AlphaEncoder.encode(long)lo codifica en base 64 con el diccionario URL-safe0-9 A-Z a-z - _.
Resultado: hasta 11 caracteres (log_64(2^63) ~ 10.5).
|
Caracteres visualmente ambiguos ( Personalizacion: |
SequenceEntity
Clase base con PK Long generada por una sequence de base de datos.
Tipo de PK |
|
Generacion |
Hibernate la asigna antes del INSERT desde la sequence |
Contrato del dev |
Declarar |
Caso de uso tipico |
Tablas de alto volumen donde importa orden numerico o JOINs por FK numerica |
@Entity
@Table(name = "invoices")
@SequenceGenerator(name = "entity_seq", sequenceName = "seq_invoices", allocationSize = 50)
public class Invoice extends SequenceEntity {
}
El allocationSize es configurable por entidad: valores altos mejoran performance a costa de gaps ante reinicios del proceso.
|
Multitenancy: si la entidad no declara |
CustomIdEntity<PK>
Clase base generica sin generacion automatica de ID. El dominio conoce y asigna la PK.
Tipo de PK |
Parametro de tipo: |
Generacion |
Ninguna — responsabilidad del dev |
Contrato del dev |
Asignar el ID antes de llamar a |
Caso de uso tipico |
Claves naturales definidas por el dominio (codigo ISO de pais, RUC, CUIL) |
@Entity
@Table(name = "countries")
public class Country extends CustomIdEntity<String> {
private Country(String isoCode) {
this.id = isoCode; // asignacion obligatoria antes del save
}
public static Country create(String isoCode) {
return new Country(isoCode);
}
}
// Uso:
persistencePort.save(Country.create("PE"));
|
¿Por que es critico asignar el ID antes de
La disciplina se captura mejor en una factory ( |
Que incluyen todas las clases base
Las tres clases aportan tres comportamientos comunes:
| Aspecto | Resumen |
|---|---|
Auditoria |
|
|
Basados en el ID, compatibles con proxies de Hibernate. Ver gotcha con entidades sin persistir. |
|
|
Auditoria
Los cuatro campos se pueblan via AuditingEntityListener de Spring Data, que consulta dos beans configurados en config/JpaConfig:
-
AuditorAware<AuditUser> auditorProvider— extrae el usuario actual delSecurityContextHoldery mapeaUser.getUsername()aAuditUser.of(subject). Elemailquedanullsalvo que un bean custom lo provea. -
DateTimeProvider dateTimeProvider— retornaInstant.now()del clock del proceso (UTC).
Ambos estan declarados con @ConditionalOnMissingBean, asi que pueden reemplazarse declarando un bean propio.
|
Cuando los campos de auditoria quedan
Solucion para ejecuciones sin HTTP: declarar un |
equals y hashCode
Las tres clases base implementan equals y hashCode sobre el ID, con soporte para proxies de Hibernate (ProxyUtils.getUserClass(obj)). No redeclarar estos metodos en las entidades hijas.
|
Gotcha con entidades sin persistir: como Implicancia practica: al armar un |
Fuera de alcance
- IDs compuestos (
@EmbeddedId/@IdClass) -
No estan soportados por las clases base. Para integrar con esquemas legacy que los requieran, declarar la entidad directamente sin heredar de ninguna base.
- UUID automatico
-
No existe una clase base dedicada. Usar
CustomIdEntity<UUID>asignandoUUID.randomUUID()antes delsave(). Si el patron se vuelve recurrente en el equipo, abrir un issue para evaluar una claseUuidEntitydedicada.
Referencias
Las clases base son un mecanismo — el diseno del modelo que las usa se apoya en literatura DDD establecida:
-
Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software (Addison-Wesley, 2003). Definiciones canonicas de aggregate root, entidad, value object y factory.
-
Martin Fowler — DDD Aggregate. Definicion concisa del patron.
-
Vaughn Vernon — Effective Aggregate Design. Tres articulos sobre diseno de aggregates, consistencia transaccional y relacion con persistencia. Lectura recomendada para profundizar en por que las transacciones atraviesan el aggregate root y en criterios para decidir tamano y limites.