Migraciones y Timezones
Había una vez dos aplicaciones, una de Contabilidad y otra de Costos, que convivían en una misma máquina y lo hicieron durante muchos años en armonía.
Ambas aplicaciones nacieron mucho antes de que GeneXus tuviera soporte para TimeZone.
Un día, los dioses de los microservicios decidieron que era una buena idea instalar las aplicaciones en diferentes equipos. Para ello, se creó una KB intermedia con servicios simples que permitieran transferir datos de una aplicación a la otra. Esta KB, llamada Servicios SOAP, leía datos de la base de datos de Contabilidad y los devolvía en una colección de SDT para que el sistema de Costos pudiera procesarlos.
La KB de Servicios SOAP y la de Costos estaban programadas en GeneXus, generando en .NET Framework, y convivieron muchos años transfiriendo asientos sin inconvenientes.
La KB de Servicios SOAP no grababa nada en la base de datos; solo leía y devolvía algunos servicios, por lo que no la considerábamos crítica. Todos los campos eran de tipo DATE y no DATETIME, por lo que no parecía riesgoso.
El servicio de CargoAsientos era trivial: un Data Provider expuesto como servicio SOAP que solo hacía lo siguiente:
A partir de ese momento quedo asi
El servidor donde ejecutaba Servicios SOAP estaba en GMT-3 (Montevideo), así que cada vez que leía algo de la base de datos, pensando que estaba en GMT, lo convertía a GMT-3, restándole 3 horas. Por lo tanto, todos los asientos quedaban cargados con la fecha incorrecta.
Además, como todos los campos eran fechas, en el sistema de Costos se hacían operaciones del tipo:
Ambas aplicaciones nacieron mucho antes de que GeneXus tuviera soporte para TimeZone.
Un día, los dioses de los microservicios decidieron que era una buena idea instalar las aplicaciones en diferentes equipos. Para ello, se creó una KB intermedia con servicios simples que permitieran transferir datos de una aplicación a la otra. Esta KB, llamada Servicios SOAP, leía datos de la base de datos de Contabilidad y los devolvía en una colección de SDT para que el sistema de Costos pudiera procesarlos.
La KB de Servicios SOAP y la de Costos estaban programadas en GeneXus, generando en .NET Framework, y convivieron muchos años transfiriendo asientos sin inconvenientes.
La KB de Servicios SOAP no grababa nada en la base de datos; solo leía y devolvía algunos servicios, por lo que no la considerábamos crítica. Todos los campos eran de tipo DATE y no DATETIME, por lo que no parecía riesgoso.
El servicio de CargoAsientos era trivial: un Data Provider expuesto como servicio SOAP que solo hacía lo siguiente:
MovContables
where &FchIni <= AsientoFecha and AsientoFecha <= &FchFin
{
MovContables_Item
{
AsientoNumero
AsientoFecha
Importe
}
}
Era un programa tan sencillo que no teníamos pruebas unitarias para él.
Sabiendo que el generador .NET Framework estaba pasado de moda y desaconsejado por Microsoft, decidimos migrar a .NET Core, que estará soportado por más tiempo. Para ello, creamos un nuevo Environment en la misma KB de Servicios, y por error, dejamos la propiedad DateTime storage timezone en su valor por defecto: GMT.Hice algunas pruebas desde el sistema, viendo que lo que traía de Contabilidad era lo que estaba grabando y controlando los totales dentro del mes.
Sabiendo que el generador .NET Framework estaba pasado de moda y desaconsejado por Microsoft, decidimos migrar a .NET Core, que estará soportado por más tiempo. Para ello, creamos un nuevo Environment en la misma KB de Servicios, y por error, dejamos la propiedad DateTime storage timezone en su valor por defecto: GMT.Hice algunas pruebas desde el sistema, viendo que lo que traía de Contabilidad era lo que estaba grabando y controlando los totales dentro del mes.
A partir de ese momento quedo asi
Además, como todos los campos eran fechas, en el sistema de Costos se hacían operaciones del tipo:
&FechaInicial <= Fecha and Fecha <= &FechaFinal
Como &FechaInicial era 01-01-2024, por ejemplo, no tomaba en cuenta el movimiento que quedaba como 31-12-2023.
También había un programa que borraba movimientos y los volvía a importar. Como había movimientos que quedaban con fecha del último día del mes a las 21:00:00, esos registros no se borraban y podían duplicarse.
En fin, un gran lío por un pequeño descuido.
Había programado controles para revisar si los movimientos en Contabilidad y Costos estaban correctos y no faltaba ninguno. El sistema recuperaba y grababa los movimientos de forma coherente, por lo que no daba diferencias, pero no era lo real. El problema era que si en un mes no había asientos el primer día o el último día, también los totales coincidían con lo que había en la base
Como &FechaInicial era 01-01-2024, por ejemplo, no tomaba en cuenta el movimiento que quedaba como 31-12-2023.
También había un programa que borraba movimientos y los volvía a importar. Como había movimientos que quedaban con fecha del último día del mes a las 21:00:00, esos registros no se borraban y podían duplicarse.
En fin, un gran lío por un pequeño descuido.
Había programado controles para revisar si los movimientos en Contabilidad y Costos estaban correctos y no faltaba ninguno. El sistema recuperaba y grababa los movimientos de forma coherente, por lo que no daba diferencias, pero no era lo real. El problema era que si en un mes no había asientos el primer día o el último día, también los totales coincidían con lo que había en la base
Aprendizajes
- No hay migraciones sencillas; en todas hay que revisar mucho las propiedades.
- Siempre tener pruebas unitarias de tus servicios.
- Siempre tener pruebas de integración entre las aplicaciones.
- No hay migraciones fáciles (sí, está repetida, pero es para que lo recuerdes).
- El manejo de timezone siempre trae problemas, no importa cuando leas esto.
Tengo un caso similar para una KB nueva de Gx18U9. resulta que tengo accesos por dataviews a otro motor SQL para leer datos de otro sistema. En ese caso todos los datetime tambien vienen mal.
ResponderBorrarSi estuviera a nivel de datastores podria haber sido simple, pero como es para toda la KB no quedó otra que poner la KB nueva con timezone=undefined.
Es util esa propiedad para un entorno de una misma kb o relacionadas con otras similares, pero segun el contexto de trabajo puede complicarse bastante...
Saludos
Si, es como decis. El Timezone deberia poder especificarse para un DataStore, y tambien para un external object, para un Storage Provider y tambien cuando se consumen servicios externos.
BorrarCada vez que nuestra aplicacion necesita interactuar con un objeto externo a ella y tienen campos Date o DateTime hay que tener en cuenta en que timezone vienen los datos y convertirlos en forma correcta. No es un tema trivial y siempre trae problemas.
El problema no es solo de los datetime, sino que tambien se afectan los DATE.
Es uno de los problemas divertidos que va a ser cada vez mas comun.