Ir al contenido principal

Karina me lo dijo hace 20 años y tenía razón

Hace unas semanas descubrí que Karina tenía razón hace 20 años, solo hizo falta que esos 20 años pasaran, que yo abriera mi mente y me animara a más. A modo de reconocimiento y por si ayuda a algún otro a abrir su mente, va este post.

Mi problema en ese momento

Hace muchos años que trabajo con GeneXus, empecé en un cliente hace como 20 años y recuerdo una llamada a soporte, en ese momento  Karina era "soporte-documentación-test-capacitación-consultoría-y-aindamais".

En ese momento yo estaba desarrollando un sistema para el control de flotas (stock de repuestos, manejo de combustible, manejo del taller, etc).
Precisaba hacer un proceso de actualización de stock "batch" pero GX en ese momento solo tenía Transacciones y Reportes. No existían los procedures (mucho menos workpanels ni nada de lo que existe hoy).

Llamé  a soporte a ver como podía resolver el tema y Karina me intentó convencer de que lo que estaba haciendo estaba "conceptualmente mal", que la actualización a la base de datos debia ser vía la transacción que era donde estaban las reglas del negocio y aseguraba la consistencia de los datos, etc, etc. Argumentación atendible.

Yo tenía 20 años, era un pollito de Facultad y Karina era muy convincente.  A pesar de ello no me convenció y yo pecador (y vasco) hice un programita en DBase III Plus (¡que producto!) muy sencillo e integrable que resolvió el tema, por suerte no hubo que migrar eso a RPG porque ahí hubiera patinado un poco más :)

Nota: Antes de seguir aclaro que este post no es un plagio al de Armin sobre el tema, coincidimos que a ambos nos dio por escribir sobre eso nomás y el publicó primero (mientras yo me peleaba con el Camtasia!!).

Mi problema hoy

Hace unos días tuve que hacer una aplicación y quería que tuviera Geografías (la típica de Pais/Estado/Ciudad)  y tomé por el camino "conocido".

Lo primero que hice fue definir la estructura geográfica que quería, bastante sencilla:

CountryID*
CountryName
(StateId*
StateName
(CityId*
CityName))

Luego me puse a desarrollar por el "viejo_conocido_seguro_confiable" camino de un procedure con "news" que poblara los datos. Algo como:

new
CountryId=1
CountryName="Uruguay"
endnew
new
CountryId=1
StateId=1
StateName="Montevideo"
endnew
new
CountryId=1
StateId=1
CityId=1
CityName="Montevideo"
endnew
etc, etc, etc.

Estuve un rato de "copy/paste" bastante largo y aburrido, proceso en el cual cometí N errores (me suele suceder cuando me aburro) porque en un bloque ponía el código del país mal y me quedaban mal los datos y así sucesivamente.

Tiene que haber un modo mejor

Esto de "inicializar" datos es algo que suelo precisar porque hago muchas KBs "from scratch" y siempre el tener un juego de datos "razonable" es un problema.
Creo también que es algo bastante común en procesos de implantación donde luego del "create" hay que poblar con datos iniciales la base de datos.

Anduve buscando algunas KBs a ver como lo tenían resuelto (¡aguante GXserver!) y me encontré en varios lugares la misma solución del proc y los news. Incluso alguna parecida a lo que quería pero no lo suficiente para re-usarla a bajo costo.

Me acordé de un DataProvider que publiqué hace tiempo en el wiki, uno que hice con un programita que leia los ISO codes de un excel y generaba un TXT con el formato DP, luego "copié/pegué" del TXT en el DataProvider.  A veces practico esos deportes :)
Ese está bueno pero no tiene los estados ni ciudades... en fin... no aplicaba mucho a mi caso, sin embargo por el lado de los DP parecía venir la solución.

Al final de la historia (así la hago corta) me quedé con una solución con DataProviders que que retorna los datos y un procedure que en lugar de un "new" usa un business component, también usé autonumber y un par de serial para sacarme de encima el lío de la primary key.

A continuación un video de cómo lo hice:



Ventajas que encontré

1. Más simple. Quiero poblar la estructura de Countries, entonces Drag&Drop de la misma a un DP y alguna cosita más y listo.

2. Más barato. Me llevó mucho menos tiempo la versión inicial. Si bien tuve un trabajo de "copy/paste" fue más sencillo, al menos no me preocupé para nada de los CountryCode, StateCode, etc.

3.Más claro. Separé el "alta" de la "fuente de datos", con esto si quiero agregar datos no tengo que meterme con los news ni nada, simplemente los agrego en el  DP.

4.  Más potente. En mi caso era una estructura bastante sencilla con reglas sencillas, pero igual me resolvió el alta, la numeración y las reglas que tuviera o vaya a tener asociadas al Country etc (si le tengo que agregar alguna regla a la TRN de Countries se que no tendré que modificar el procedure, esto es importante para la consistencia/calidad de los datos).

5.  Más re-usable. Mi desafío ahora es evitarme el copy/paste e intuyo que via el Data Provider Generator y servicios como los de freesbase.com no tendré mayores inconvenientes en hacerlo (quedará para cuando pueda bucear en freebase.com un poco más).
En cualquier caso si esto lo fuera a implantar en un cliente talvez la fuente de datos sea otra y habiendo separado los datos del alta en si creo que será más fácil adaptarel "input" del DP.

En fin, unas cuantas ventajas del uso de DP y BC en un escenario como el descrito. Luego de "abrir mi mente" respecto a los DP y BC creo que hay muchos más escenarios de uso posibles para los mismos así que mi mensaje es solamente ese: mantenga su mente alerta, hay nuevas y mejores soluciones para nuevos y viejos problemas.

Ahh... me olvidaba: Karina, tenías razón, solo se trata de abrir la mente y para la actualización alcanza la transacción (o casi).

Comentarios

  1. UPDATE: subi la KB a GXServer por si a alguno le interesa:
    http://gxserver.genexusx.com/genexusserver/home.aspx?dp%20and%20bc%20sample

    ResponderBorrar
  2. Muy bueno. El tema de los DP me gustò cuando lo empecè a entender, mezclarlo con BC, mejor aùn.

    Jorge

    ResponderBorrar
  3. Muy instructivo, efectivamente es el primer paso para eliminar los datos "hardcoded"... lo que me ha venido a la mente, especialmente en el ejemplo que utilizas, es el hecho de que, aún utilizando una técnica mas limpia, se sigue utilizando la técnica de tener una serie de datos hardcoded en el programa... ¿no tendría sentido en este ejemplo el dar el paso adicional de hacer que todos los datos que se alimentan automáticamente estén codificados en archivos (xml seguramente seria lo mejor), separados del código, de modo que la generacion de los datos en la base de datos no dependiera directamente del código, y fuera personalizable por implementación (imagina que tienes que vender el software a un cliente de Brasil y otro en Uruguay, o en cualquier parte del mundo, la diferencia sería el "dame la estructura de tu país, que te hago una versión nueva del programa" contra "aquí tienes el xml de la estructura del país, añade lo que estimes oportuno, y luego lo cargamos"...
    En realidad lo comentas de pasada en un momento del vídeo, pero creo que sería interesante el eliminar definitivamente la costumbre de dejar valores fijados en el código.

    ResponderBorrar
  4. Jesus,

    Comparto lo que decis, diría que el desarrollo de los "data providers", el data provider generator y algunas otras funcionalidades que están en I&D van por ese lado.

    De todos modos mi objetivo primario ha sido separar la lógica del "insert" de los datos en si. Lo que me complicaba era tener ambas cosas mezcladas porque perdía dinamismo, además como no usaba BCs se me complicaba la integridad, re-uso de reglas, etc.

    Me ha tocado participar en algunos proyectos y las "fuentes de datos" son bastante diversas, diría que TXT, DBFs, XML, un export de SQL, XLS, WS, etc.
    En mi opinión, en el caso que se tienen esas fuentes tan "diversas", se puede hacer un procedure (script, XSL, etc) que lo transforme en el SDT que el procedure que inserta utilice.

    O sea, el "input" del procedure debería ser siempre el mismo, en mi caso me servía un DP, en tu caso diría que en lugar de invocar al DP podrías hacer un procedure que lea el XML y le pase eso como SDT al procedure del insert.
    ¿Podrás en todos los casos zafar de la programacion de ese procedure que produce el "output" que el otro precisa como "input"?
    Estimo que no (por la "diversidad" de las fuentes) pero por lo menos te asegurás que el insert quede bien y no precises nunca re-programarlo.

    Si en tu caso logras un XML con la estructura geográfica sería entonces hacer un proc que lea el XML y le pase el SDT al procedure que hace el insert.

    NOTAS:
    1. Habrá más post/news sobre el tema DP especificamente.
    2. El tema de la "inicialización" de datos es divertido y diría que por el lado del "initial value" puede que haya novedades :)

    ResponderBorrar
  5. Romper paradigmas crecer.
    pero segun mis calclulos 19 años.
    Un abrazo

    ResponderBorrar
  6. Si, pueden ser 19 pero para los que pasamos los 40 ya los números "exactos" no son tan importantes :)

    ResponderBorrar
  7. Muy buena info, te hago una consulta, estoy haciendo un proyecto y me surge la siguiente duda, se trata de un banco, este al crear un cliente de enmediato tendria q crearle una tarjeta de credito, como tendria q hacerlo, ya q en el video vos le das los nombres del pais, etc, y yo lo que qeuiro es q el usuario ignrese los datos del cliente y ahi se cree la tarjeta

    gracias

    ResponderBorrar
  8. Lucho,

    Depende un poco como modeles la realidad.
    Podria ser algo asi:

    OPCION 1 - todo en una TRN.

    Definis una transacción de dos niveles, algo como:
    ClienteId*
    ClienteNombre
    ClienteCantTarjetas=count(TarjetaFchVto)
    (TarjetaNro*
    TarjetaFchVto)

    Para asegurarte que ingrese por lo menos una tarjeta podrias poner algo como:
    Error("debe ingresar por lo menos una tarjeta") if ClienteCantTarjetas=0 on after(level(TarjetaFchVto)

    Nota: la sintaxis puede no se exacta

    OPCION 2: dos transacciones "en cadena"


    Definis una transacción de clientes:
    ClienteId*
    ClienteNombre

    Una de tarjetas:
    ClienteId*
    TarjetaNro*
    TarjetaFchVto

    En la primera pones un:
    tarjetas.call(ClienteId) on afterInsert

    Depende como quieras la operativa en la de Tarjetas podes poner un error si quiere salir de esa TRN sin dar de alta una tarjeta o en la de Clientes borrar el cliente si no ingresó una tarjeta.

    Hay muchas maneras más de implementarlo, depende un poco de la realidad y lo que precises, incluso podría ser con BCs pero parecen más complicadas que las TRNs que describo

    ResponderBorrar

Publicar un comentario

Entradas más populares de este blog

El miedo, ese asesino silencioso

Hace unas semanas compré por Mercado Libre un tejido de alambre (de esos electrosoldados que se usan para cercos, pérgolas, etc) para armar una pérgola en casa. Como me suele pasar: compré de más. Así que decidí vender el resto del alambre por el mismo medio (Mercado Libre). Lo publiqué un Miércoles a las 13:00 y 4 horas después recibo un aviso de Mercado Libre que lo había vendido. Perfecto! Incluso lo había puesto al mismo precio x metro que la compra original así que notable! Revisé la compra y me pagó mediante Mercado Pago usando una tarjeta VISA, en fin, todo normal. Un rato después del aviso me llaman de un teléfono fijo al celular un tal Juan Rodriguez que coincidía con el nombre del comprador que me había llegado. Capitulo 1: ¡para qué abrís la boca! No sé cómo decir esto de un modo "políticamente correcto" pero la manera de expresarse parecía de alguien que no tenía un gran nivel de educación. Demasiado "lunfardo" para hablar con alguien que no con...

Liberar una versión: el fin del principio

Quienes desarrollamos software buscamos afectar positivamente la vida de las personas a través de nuestras aplicaciones. Ese es nuestro objetivo final, ese es nuestro éxito. Quienes desarrollamos , que ayuda a nuestros colegas en esa misión, solo tenemos éxito cuando ellos tienen éxito, por eso solemos decir que cuando liberamos una versión es "el fin del principio". Es un escalón fundamental, pero solo el primero. Tuve el privilegio de formar parte del equipo de y de su pero a su vez no he podido dedicarle tanto tiempo   como quisiera, casi no he podido  , ni  he podido leer. El mundo siguió girando, hay  , hay que me perdí y . Casi de casualidad me enteré del encuentro del a partir del 16/marzo en Montevideo. En cualquier caso: ¡el esfuerzo valió la pena!. Ahora que está siendo liberada en el   , hay que celebrar porque aplica aquello de "release is a feature". Podría tirarme en mi casa y estrenar mi  con una buena peli o sino aprovechar ir al un poco má...

Sobre plomeros y sanitarios

Hace unos días estaba en casa de mi madre, quien sigue viviendo en la ciudad donde yo nací (Florida-Uruguay), cuando sonó el timbre. Fui a atender y me encontré con Martín, un ex compañero de liceo que hacía años no veía. Luego de preguntarle cómo andaba y el saludo de rigor, le pregunté que estaba haciendo por allí y me dijo "estoy trabajando aquí", ahí noté que venía con baldes y herramientas y recordé que es hijo del plomero que toda la vida trabajó en mi casa. El baño de mi madre estaba con problemas en la presión de agua así que llamó al plomero "tradicional" y este le comentó que estaba retirado pero que enviaría a su hijo. Allí pues estaba Martín. Es interesante encontrarse con compañeros de escuela/liceo que uno no ve hace muchos años, da para otro post seguramente. La cuestión es que seguimos conversando mientras él trabajaba. Le pregunté por el padre y me dijo que estaba bien pero ya medio cansado del trabajo y que nunca se había podido adaptar a los cambi...