Capa de Persistencia de Datos
Una jerarquía de puertos que desacopla los contratos de guardado de los backends de Unity para que el almacenamiento en ficheros, PlayerPrefs y el cifrado puedan cambiar sin tocar el código del juego.
Para qué sirve este sistema
Todos los juegos necesitan guardar datos, y todos los proyectos acaban acoplando esa necesidad a un backend concreto demasiado pronto. Las rutas de ficheros se dispersan por los sistemas, las claves de PlayerPrefs colisionan, el cifrado se añade como parche posterior y cambiar un enfoque de almacenamiento por otro se convierte en una refactorización en lugar de un cambio de configuración.
La capa de Persistencia de Serenity define el contrato de guardado a nivel de dominio y mantiene Unity fuera de él. Los backends implementan puertos. El código del juego habla con puertos. El sistema de Checkpoint y Game Settings se apoyan en la misma base sin saber qué store hay debajo.
El problema en Unity
Llamar directamente a File.WriteAllBytes o PlayerPrefs.SetString desde el código del juego funciona hasta que deja de funcionar. La primera vez que se quiere añadir cifrado, hay que tocar cada punto de guardado. La primera vez que el proyecto se ejecuta en un contexto con el sistema de ficheros restringido, no hay ninguna costura para inyectar una alternativa. Los health checks no existen, por lo que los fallos de persistencia afloran como excepciones en tiempo de ejecución en lugar de como degradación controlada.
Las colisiones de namespace son otro modo de fallo silencioso. Dos sistemas que escriben la misma clave de PlayerPrefs se sobreescriben mutuamente sin avisar. No hay separación impuesta, no hay contrato de comprobación de existencia y no hay operación de borrado común. Cada sistema inventa sus propias convenciones y el proyecto acumula patrones incompatibles.
Cómo lo aborda Serenity
Serenity define una jerarquía de puertos bajo el namespace Serenity.Persistence. IPersistenceStore es la base: lleva una propiedad Namespace y un método HealthCheckAsync que todos los backends deben satisfacer. IKeyedStore lo extiende con ExistsAsync y DeleteAsync. IBlobStore añade I/O de stream asíncrono a través de IReadOnlyBlobStore, mientras que IKeyValueStore ofrece get y set tipados a nivel de bytes para backends ligeros. IAppendableBlobStore gestiona escenarios de append al estilo de log. IContentTransformer cubre el par Transform e InverseTransform para pipelines de cifrado y compresión.
Dos backends concretos se incluyen de serie. FileStore implementa IBlobStore e IAppendableBlobStore usando IFileWriterService para escrituras atómicas sobre el sistema de ficheros. UnityPlayerPrefsKeyValueStore implementa IKeyValueStore y almacena los valores como strings Base64 en PlayerPrefs bajo un prefijo opcional, evitando colisiones de claves entre sistemas. Ambos informan de un string de namespace y responden a los health checks.
Para datos estructurados y consultables — registros con campos, filtros y ordenación en lugar de un blob opaco — la misma capa gana la familia de contratos IStructuredStore, agnóstica a la tecnología: registros con clave, consultas de filtro/orden/límite/conteo y upsert condicional. Es el backend compartido detrás de Clasificaciones, GameSave/GameProgress y el logging estructurado, y es deliberadamente plural en backends: clave-valor local del dispositivo, archivos JSON legibles, una base de datos SQLite embebida, tu propio servicio HTTP o una conexión directa a Postgres/MySQL/MongoDB para entornos de confianza. Un contrato, cinco lugares intercambiables donde poner los datos.
Cómo encaja en Serenity
Persistencia sigue la estructura por capas de Serenity. La capa de Dominio contiene las interfaces de puerto: IPersistenceStore, IKeyedStore, IReadOnlyBlobStore, IBlobStore, IAppendableBlobStore, IKeyValueStore, IContentTransformer e ISerializer. La capa de Aplicación publica los casos de uso PersistenceSaveObject, PersistenceLoadObject, PersistenceSaveBytes y PersistenceLoadBytes junto con sus DTOs de entrada, agrupados bajo PersistenceUseCases. La capa de Infraestructura contiene FileStore en el módulo FilePersistence y UnityPlayerPrefsKeyValueStore en el módulo PlayerPrefsPersistence. La instalación conecta todo a través de PersistenceInstaller, FilePersistenceInstaller y PlayerPrefsPersistenceInstaller.
El sistema de Checkpoint y Game Settings consumen la persistencia a través de las mismas interfaces de puerto, por lo que son agnósticos al backend por defecto. Reemplazar FileStore por un backend en la nube o cambiar el serializador implica modificar un installer, no editar los sistemas del juego.
El lado estructurado vive al lado, en el módulo StructuredPersistence, con sus propias implementaciones de backend tras IStructuredStore — KeyValue, LocalFile (JSON con formato legible), Sqlite (una base de datos embebida en un archivo, con el esquema creado automáticamente en el primer uso), y HTTP/Postgres/MySQL/MongoDB tras los scripting defines SERENITY_STRUCTURED_*. Cada backend de base de datos necesita DLLs de driver instaladas a mano y fuera del control de versiones, así que Tools ▸ Serenity ▸ Validate ▸ Database Drivers lee un manifiesto por backend con los paquetes NuGet y archivos de ensamblado exactos que cada uno necesita, marca cuáles ya están instalados y solo ofrece activar el scripting define cuando el conjunto completo está en su sitio — un define nunca se enciende antes que las DLLs de las que depende.
Flujo de trabajo práctico
- Elegir qué backend se ajusta a los datos objetivo: FileStore para guardados binarios y replays, UnityPlayerPrefsKeyValueStore para ajustes ligeros, o un backend IStructuredStore para registros consultables como clasificaciones y slots de guardado.
- Registrar el backend elegido a través del installer correspondiente para que el contenedor resuelva la implementación correcta de IPersistenceStore.
- Opcionalmente, encadenar un IContentTransformer para cifrar o comprimir los payloads antes de que lleguen al store.
- Usar los casos de uso de aplicación PersistenceSaveObject y PersistenceLoadObject para persistir objetos de dominio sin escribir código de serialización a mano.
- Llamar a HealthCheckAsync al arrancar o desde un sistema de diagnóstico para confirmar que el backend es accesible antes de la primera escritura.
- Para datos estructurados, elegir una tarjeta de backend (dispositivo / archivos / base de datos) en el asset de ajustes de la feature correspondiente y, para un backend de base de datos, abrir Database Drivers para instalar los paquetes exactos que necesita antes de activar su scripting define.
- Añadir un nuevo backend implementando las interfaces de puerto relevantes y registrándolas a través de un installer personalizado, sin necesidad de cambiar el código del juego.
Qué incluye
- Puerto base IPersistenceStore con propiedad Namespace y HealthCheckAsync para todos los backends
- IKeyedStore que añade ExistsAsync y DeleteAsync al contrato común
- IBlobStore e IReadOnlyBlobStore para I/O binario asíncrono basado en streams
- IKeyValueStore para get/set tipado de bytes a nivel ligero, adecuado para backends al estilo PlayerPrefs
- IAppendableBlobStore para escenarios de solo append como logs y replays
- IContentTransformer con Transform e InverseTransform para pipelines de cifrado y compresión
- FileStore que implementa IBlobStore con escrituras atómicas a través de IFileWriterService
- UnityPlayerPrefsKeyValueStore que almacena valores Base64 en PlayerPrefs bajo un prefijo de clave configurable
- Familia de contratos IStructuredStore para registros consultables, filtrables y ordenables — compartida por Clasificaciones, GameSave, GameProgress y el logging estructurado
- Cinco backends estructurados intercambiables: clave-valor del dispositivo, archivos JSON legibles, SQLite embebido, tu propio servicio HTTP o Postgres/MySQL/MongoDB directos
- Ventana Database Drivers (Tools ▸ Serenity ▸ Validate ▸ Database Drivers) que lista las DLLs de driver y paquetes NuGet exactos de cada backend, y bloquea su scripting define hasta que estén todos instalados
Cuándo usarlo
- Proyectos que necesitan cambiar el backend de guardado entre plataformas sin modificar los sistemas del juego.
- Juegos que requieren cifrado o compresión en los datos guardados y quieren un único punto de transformación.
- Bases de código donde múltiples sistemas guardan datos y necesitan separación de namespace impuesta para prevenir colisiones de claves.
- Proyectos que construyen sobre los sistemas Checkpoint o Game Settings de Serenity, que consumen estos puertos directamente.
- Features que necesitan registros consultables, clasificados o filtrados — clasificaciones, slots de guardado, logs estructurados — respaldados por cualquier cosa desde un archivo local hasta una base de datos real.
Sistemas relacionados
Usa Serenity cuando quieras contratos de persistencia que sobrevivan a cualquier backend concreto — ficheros hoy, nube mañana, registros estructurados o un blob en bruto — con cifrado, health checks y seguridad de namespace ya incorporados.
English
Español
Català