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

Abrir links con aplicaciones nativas y no el browser (deeplinking)

El problema que tengo con algunas aplicaciones Android/iOS es que cuando recibo un link por algún medio (mail, tweet, etc) al abrirlo me lo abre con el browser, en lugar de abrirlo con una aplicación nativa asociada a ese “contenido”. Por ejemplo, si recibo un link a un tweet espero que lo abra con alguna aplicación de twitter que tenga instalada y no con el browser. De modo análogo si recibo un mail con una nota de prensa de un medio X y tengo la aplicación de ese medio X instalada, espero que el link lo abra con la aplicación nativa y no con el browser. Lo mismo quisiera con mi aplicación de "banking" o cualquiera que tenga instalada y sepa manejar ese "contenido" (link). Los motivos son bastante obvios pero los resumo en: la experiencia de usuario es mucho mejor en la aplicación nativa que en el navegador. Parte importante del tema es que el mismo link sea válido tanto para ver el contenido en el browser como para verlo en la aplicación, porque como prove

¡A la salud de mi KB!

Es bueno, especialmente en "bases de conocimiento" (KB) que han pasado por varias versiones de Genexus, chequear su "estado de salud". En este sentido KBDoctor  es una herramienta que ayuda mucho, principalmente desde el punto de vista del "modelo" Genexus (atributos, calls, definiciones de variables, etc) representado en una KB. También es útil revisar la salud de los archivos que lo soportan. Hasta la 9.0 eran archivos C-tree (los famosos .DAT) que tenían indices (los famosos .IDX) y teníamos en "rebuild -y" que mejoraba esos archivos y sobre todo reconstruía los indices. A partir de la X las KBs se almacenan en MS SQL Server por lo cual la administración de la misma pasó de ser un "file server" a un "database server". En este sentido algo que me ha dado muy buenos resultados es el "CheckKnowledgeBase".

Rocha:Constantes tipo fecha

En la Rocha se soportan constantes del tipo fecha o fecha-hora con formato ANSI/ISO (AAAA-MM-DD HH:MM:SS).  Tecnicamente (Sintáxis): <date>::=    [0-9]{1,4}"/"[0-9]{1,2}"/"[0-9]{1,2} | [0-9]{1,4}"."[0-9]{1,2}"."[0-9]{1,2} | [0-9]{1,4}"-"[0-9]{1,2}"-"[0-9]{1,2} <hms>::=    [0-9]{1,2}[ap] | [0-9]{1,2}":"[0-9]{1,2}[ap]? | [0-9]{1,2}":"[0-9]{1,2}":"[0-9]{1,2}[ap]? <constant> ::=   "#"<date>"#" | "#"<date> <hms>"#" | "#"<hms>"#" Funcionalmente Se pueden utilizar esas constantes en las reglas, eventos, propiedades, etc (todo lugar donde se utilice el parser): Algunos ejemplos básicos: &FechaInicial=#2007-01-01# &FechaHoraInicial=#07-1-1 11:15a# &HoraInicial=#11a# Me parece bueno no tener que escribir funciones (CTOD, TTOC) sobre constantes tipo char para lograr una fecha y mucho mejor aun en