
Qué estamos haciendo (I)
Bienvenidos a esta nueva sección donde hablamos de los proyectos que empiezo y dejo a mitad. Hoy vamos a hablar de… Tiamat.
Tiamat… ¿Y eso qué es?
Si alguno ha entrado en mi repo de bitbucket quizás haya visto que uno de los proyectos más antiguos era Tiamat. Así que vamos a hacer algo de historia. Hace años intenté hacer una aplicación como asistente para partidas de rol en mesa (D&D y demás), adelantándome un poco a todas las webs que saldrían más adelante, pero sin los conocimientos. No sé si llegué a hacer algo más que un editor de sesiones, localizaciones, etc. Y todo enlazado.
Pero sin saber nada de front, la falta de motivación, y que realmente era más ambicioso de lo que podía asumir, lo dejé abandonado. Eso y que el DM que tenía en aquella época tampoco mostraba ninguna clase de interés.
Pero nos quedamos con el nombre, que creo que inauguró la larga tradición de ponerle nombres de la mitología mesopotámica a los proyectos. Es la diosa que engendró a los dragones o algo así (¿eh? ¿lo pilláis? ¡dragones! soy muy ocurrente) y también es una diosa de D&D, aunque de eso me enteré más tarde.
Vale, gracias por hablarnos de algo que abandonaste
Sí y no. Hace una semana reflexionaba sobre los más de 5 meses que los jugadores de mi partida llevan sin ponerse de acuerdo para jugar (en realidad creo que son 10 meses), que el Divinity 3 no tiene fecha aún y el Solasta 2 apenas tiene una demo. Y necesito algo que me mate el gusanillo. Y a parte de volver a instalar el Solasta para jugar una de las campañas con mi señora esposa, decidí pensar en desarrollar alguna aplicación relacionada con el rol.
Ahora soy un poco más mayor, más listo, programo ligeramente mejor. Y tengo una mínima experiencia como DM. Así que empecé a pensar qué podría hacer. Aplicaciones para que usasen los jugadores quedaron descartadas. Ya son un problema de por sí los móviles en la sesión. Generadores de mapas también, porque está el de Azgaar, que es lo único que a mí me anima a ser master, a día de hoy. Yo sólo dirijo partidas para poder usarlo. ¿Generadores aleatorios de mazmorras? Es una de las cosas a las que me niego. Los mapas pueden generarse de forma aleatoria, pero un edificio no. Nadie pone paredes aleatorias y sale algo. Los edificios tienen intención y debería verse reflejada en el propio edificio. ¿Otro editor como el que empecé con el primer Tiamat? Resulta que descubrí Manuskript y cumple casi todas las funciones. Vale, pues un simulador de combates. Era una opción, pero demasiado ambicioso (¿para qué sistema? ¿con mapa?).
Bueno, en resumen, quería algo que me ayudase con el worldbuilding y que no estuviese ya disponible por ahí. Bueno, o al menos opensource (ya sé que hay un montón de webs que ya hacen esto pero no voy a pagar una subscripción para jugar dos sesiones al año).
¿Entonces qué? ¿Qué vas a hacer?
Bueno, quería algo sencillito con lo que empezar e ir ampliando. Algo que se quedaba un poco corto en el manuskript, es la visualización. Al final es un editor de textos, y aunque permite insertar enlaces que llevan a otras páginas, si quiero ver todo el conjunto de alianzas y demás, se queda corto. El editor de Azgaar permite visualizarlas pero sólo entre estados. Y además mi viejo portátil puede arder si lo abro en mitad de una sesión. Así que hemos decidido empezar un módulo de relaciones entre facciones.
He borrado el antiguo repositorio de Tiamat y he creado uno nuevo, pero dejaba muchas incógnitas abiertas…
Ya sé que la teoría dice que esas decisiones de implementación pueden dejarse siempre para el final. Y es algo en lo que insiste la arquitectura hexagonal, pero había una ineludible: ¿En qué lenguaje de programación?
Aquí estuve charlando con ChatGPT un rato. Se me metió en la cabeza hacer una aplicación de escritorio, y eso me dejaba con dos alternativas: Objective-C o Java. Objective-C hacía mil años que no lo tocaba, pero sé que quedaría mejor. Pero si la hacía en Java sé qué podría migrarla a una Web si hacía falta.
¿Y una web por qué no?
Bueno, no va a usarla nadie más, de momento. Por otro lado, si jugamos en un sitio que no tenga conexión, me quedo sin aplicación. Se puede empaquetar en un docker y toda la historia para arrancarlo en local, pero no tenía muchas ganas. Aun así, lo bueno de la arquitectura hexagonal es que cambiarlo sería trivial (excepto rehacer la interfaz con Vue o algo).
Vale, entonces Java
Sí, la idea sería hacer una aplicación java, crear la interfaz con JavaFX y empaquetarla con jpackage o gluon o ya veremos. De momento lo dejamos para más adelante.
¿Nos vas a contar ya “Qué estamos haciendo”?
Y sin más preámbulos… hemos creado un maven multimódulo con la siguiente estructura:

- commons: Como bien habréis podido deducir por el nombre, contiene elementos comunes al resto de módulos: Por ejemplo algunos ValueObject de identificadores, algún layer supertype, alguna utilidad para la persistencia, y algunas dependencias.
- factions: Lógica de dominio y casos de uso de facciones, así como implementaciones concretas para la persistencia.
- factions-ui: Aquí me imagino que irán los controladores de JavaFX y las plantillas, pero la verdad es que aún no lo he empezado. Pero en hexagonal es el input port y adapter. Qué bien domino el lenguaje.
- app: La aplicación. Todavía no la he empezado, pero me imagino que llevará la main class de JavaFX, alguna plantilla, configuración en general.
Vamos a meternos dentro del módulo de facciones.

Como veréis, hemos seguido la estructura clásica que se ve cuando uno busca arquitectura hexagonal en Google. Con su dominio, su aplicación y su infraestructura. Dentro de los dos primeros los he dividido en función del agregado del que tratan, en lugar del clásico entities, services, repositories, etc. ¿Por qué? Pues porque algunos sí que nos hemos leído algún que otro libro sobre DDD y sabemos que no aporta nada tener un paquete enorme de servicios o de entidades.
Otra cosa que se puede ver es el mezclote de idiomas. Ya sé que las buenas prácticas™ dicen que sólo hay que programar en inglés porque es el idioma universal y tal y cual. Pero también creo que no se puede hacer DDD en un idioma que no es el tuyo. Siempre va a haber matices que no se pueden capturar si no es en alguna de tus lenguas maternas. Y entonces el lenguaje ubicuo está mal y bla, bla, bla. Pero no sé muy bien cómo resolverlo si luego lombok me genera un getFaccion() y la costumbre me lleva a llamar las cosas Repository. Un caos, la verdad. Pero tranquilos, posibles empleadores, si me pagáis usaré un inglés impecable. En mis proyectos personales espero me permitáis usar el idioma que me plazca.
También habréis visto que en la capa de infraestructura hay dos paquetes, persistence y spring. Pero con eso iremos más adelante. Reservamos de momento. Vamos con aplicación, que es el más aburrido de todos. Un FaccionService, que permite crear facciones y un RelacionService, que hace lo propio para las relaciones.
No tienen mucho misterio, voy a poner un ejemplo de código porque no cuesta nada, pero vamos, que eso es un servicio de aplicación en DDD, implementa un caso de uso, gestiona transaccionalidad (que en mi caso no tengo y luego veremos por qué) y poco más.
@Service
@AllArgsConstructor
public class FaccionService {
private final FaccionRepository faccionRepository;
public Faccion crearFaccion(String nombre, String descripcion, PersonajeId personajeId) {
Faccion faccion = FaccionFactory.crearFaccion(nombre, descripcion, personajeId);
return faccionRepository.guardarFaccion(faccion);
}
}
Superfácil. Se reciben parámetros, se instancia la entidad, se envía al repo. Y chimpúm. Así debería ser un servicio de aplicación.
Siguiente: La capa más divertida, la de dominio. Aquí ya hay algo más de chicha:

Como veréis, no tiene mucho misterio, os he engañado. Pero básicamente Facción es una clase abstracta que tiene dos implementaciones concretas: Individuo o Colectivo. Pensé que quizás tendría sentido no tratar igual las personas concretas que los grupos. Por ejemplo, mi grupo de jugadores no necesariamente tiene que llevarse bien con determinado rey, pero quizás algún personaje en concreto sí.
@Getter
private PersonajeId personajeId;
protected Individuo(FaccionId id, String nombre, String descripcion, PersonajeId personajeId) {
super(id, nombre, descripcion);
setPersonaje(personajeId);
}
private void setPersonaje(PersonajeId personajeId) {
if (null == personajeId) {
throw new IllegalArgumentException("El ID del personaje no puede ser nulo");
}
this.personajeId = personajeId;
}
Aquí en la entidad quiero señalar un par de cosas:
- Constructor protected: De esta forma evitamos que se instancie la entidad de forma inadecuada fuera del paquete. Y así tenemos controlados en qué puntos se hace.
- Además los setters privados. De momento sólo se utilizan para inicializar la entidad. No es necesario que se llamen desde fuera del constructor.
- Y el constructor, por supuesto, asigna los campos mediante los setters. La validación de los campos se hace en el propio setter. En la entidad, no en otros sitios. No se validan los campos en la capa de aplicación (o al menos no sólo), porque ello implicaría que en otro punto puede instanciarse una entidad sin pasar por las validaciones. También es cierto que la validación que he puesto de ejemplo es una tontería.
Y vamos a enseñar el factory. Que será el único punto desde el que se podrá instanciar las Facciones. En realidad podría haberse hecho con un constructor estático (o el propio constructor). Pero en este caso, al tener colectivos e individuos, pensé que tendría sentido crear un factory.
public static Faccion crearFaccion(String nombre, String descripcion, PersonajeId personajeId) {
FaccionId id = FaccionId.nuevo();
if (null == personajeId) {
return new Colectivo(id, nombre, descripcion);
} else {
return new Individuo(id, nombre, descripcion, personajeId);
}
}
Más simple que el mecanismo de un botijo. No sé ni para qué lo enseño.
Bueno, ¿vas a contarnos por qué el paquete spring?
Vale. Si insistís, sigo con esta entrada infumable. Pensé que estaría bien poder usar el contexto de Spring para la inyección de dependencias, persistencia, etc. Así que en este paquete sobre todo hay ficheros de configuración de Spring. Luego le echaremos un ojo. Seguimos reservando, que tiene chicha.
Te gusta mucho oírte hablar, ¿verdad?
Sí, por eso vamos a hablar de la persistencia. Una vez implementadas las capas de aplicación y dominio —y sus tests, que no los enseñe no significa que no existan— había que tomar una decisión: cómo guardar los datos en disco.
Aquí estuve preguntando otra vez a ChatGPT por alternativas. Sólo tenía una cosa clara: no quería una base de datos relacional. Últimamente veo la cantidad de problemas que traen cuando se implementa mal la persistencia. Y si se implementa bien, te sirve igual una no relacional.
Ya, ¡pero las relaciones! Bueno, es que eso quizás es responsabilidad de tu capa de dominio, no del motor de base de datos.
Total, que al ser una aplicación de escritorio no tenía ni idea de qué montar. Sabía que una alternativa sería SQLite, pero es relacional. También quería poder hacerlo con Spring Data, por comodidad más que nada. Y al final de todas las alternativas que estuve discutiendo con Chato la que más me convenció fue NitriteDB.
Esto ya implicaba sacrificar Spring Data y la transaccionalidad. Empezábamos mal. Pero parecía sencilla de utilizar y de momento no suponía un problema.
El funcionamiento es el siguiente:
- Tienes una entidad anotada con @Entity, que debe tener un campo @Id
- Abres o creas la base de datos. Aquí registras todos los módulos necesarios. Vamos con eso luego.
- Obtienes de la base de datos un ObjectRepository<La entidad>
- Se envía al repositorio la entidad, e internamente llama a alguno de los conversores, registrados previamente como módulos.
- El conversor transforma la entidad en un Document, que es lo que se almacena.
Había una alternativa para el primer punto. No quería crear otra FaccionEntity, que tuviese los mismos campos, porque tendría que mapear de Faccion a FaccionEntity, y luego de FaccionEntity a Document. Pero desde luego no iba a usar nada de la capa de infraestructura en la capa de dominio. La alternativa era usar un entityDecorator.
El problema es que me dio problemas, quizás por cómo lo configuré, a la hora de mapear FaccionId. Me imagino que podría haberlo arreglado, haberle dedicado más tiempo o haberle preguntado a Copilot. Pero me cansé y acabé creando una entidad FaccionEntity (y sus herederas). Mapeo con MapStruct y listo. Hay un par de conversiones, pero bueno, ya nos ocuparemos del rendimiento más tarde si es un problema.
@Component
public class FaccionRepositoryImpl implements FaccionRepository {
private final RepositoryProxy<FaccionEntity> repository;
@Override
public Faccion guardarFaccion(Faccion faccion) {
FaccionEntity entity = FaccionMapper.INSTANCE.toEntity(faccion);
repository.update(entity, true);
return faccion;
}
}
Así se queda la implementación del repositorio. De momento.
¿Y ese RepositoryProxy<FaccionEntity> de dónde sale?
Punto por daros cuenta. Por fin hemos llegado a la raison d’être del paquete spring. O a parte de ella.
Como ya he comentado, quería usar el contexto de spring para la inyección de dependencias y demás, pero había un problema: no puedo instanciar la base de datos, ni los ObjectRepository<> si todavía no sé qué fichero ha abierto el usuario. Esto no lo he explicado, pero la idea era que el usuario pueda guardar distintos proyectos, por lo que al arrancar la aplicación (y el contexto de Spring con ella) no sabría qué base de datos hay que usar, al contrario que en una aplicación web en la que la base de datos está previamente en marcha. Además el usuario podría cerrar el proyecto y abrir uno nuevo.
¿La solución? El patrón delegado. El RepositoryProxy<> llama por debajo al ObjectRepository<> concreto. Al arrancar la aplicación, spring instancia el RepositoryProxy<> con un ObjectRepository nulo. Cuando el usuario abre un fichero, recorro todos los RepositoryProxy y les asigno el ObjectRepository adecuado. De esta forma Spring no va a quejarse cuando instancie RepositoryProxy. Eso sí, anirà tot per l’aire cuando se intente acceder antes de haber inicializado la base de datos. Ahí tengo un TODO puesto de momento.
public class RepositoryProxy<E extends NitriteEntity> {
private ObjectRepository<E> repository;
private final Class<E> entityClass;
public RepositoryProxy(Class<E> entityClass) {
this.entityClass = entityClass;
}
public void setNitriteDb(Nitrite nitriteDb) {
this.repository = nitriteDb.getRepository(entityClass);
}
public WriteResult update(E entity, boolean upsert) {
checkRepositoryInitialized();
return this.repository.update(entity, upsert);
}
}
Y luego en un @Configuration de Spring tengo lo siguiente:
@Bean
RepositoryProxy<FaccionEntity> faccionRepository() {
return new RepositoryProxy<>(FaccionEntity.class);
}
De momento no he sabido hacer que detecte automáticamente todas las NitriteEntity, que es una interfaz que me he creado para marcar las entidad. Lo intenté por reflexión, pero me crea proxies para Colectivo e Individuo también.
Finalmente, tengo un NitriteManager que se encargará de gestionar cuándo asignar a todos los proxies la bbdd:
@Component
@RequiredArgsConstructor
public class NitriteManager {
private final Set<RepositoryProxy<NitriteEntity>> repositories;
private final Set<EntityConverter<? extends NitriteEntity>> entityConverters;
private Nitrite nitriteDb;
public void abrirDb(Path path) {
// TODO persistencia en disco
NitriteBuilder dbBuilder = Nitrite.builder();
for (EntityConverter<? extends NitriteEntity> converter : entityConverters) {
dbBuilder.registerEntityConverter(converter);
}
this.nitriteDb = dbBuilder.openOrCreate();
for (var repository : repositories) {
repository.setNitriteDb(nitriteDb);
}
}
public void cerrarDb() {
if (this.nitriteDb != null) {
this.nitriteDb.close();
this.nitriteDb = null;
}
}
}
Está aún por terminar, pero bueno, os hacéis a una idea.
Y ya estaría. Esto hemos estado haciendo. Estoy agotado porque lo he escrito del tirón. Os dejo de portada el diagrama de secuencia de la creación de una facción.