2 de diciembre de 2002

Un servidor de base de datos económico con VFP y COM+

Publicado originalmente en UTMag/Rapozine
Introducción

La idea de este artículo vino a mí contestando una pregunta en un grupo de noticias que ya había respondido varias veces de diferente manera. La pregunta era acerca de cómo usar Visual FoxPro como un servidor de base de datos. Muchas veces quien hace la pregunta originalmente no llega a tener incluso lo suficientemente claros los conceptos involucrados en este tema como para entender lo que realmente quiere.

De todas maneras, las respuestas normalmente varían desde alentarlos a cambiar a un verdadero motor de base de datos relacional como SQL Server, y cuando la complejidad o los costos de licenciamiento son una preocupación, algunas otras alternativas limitadas como MSDE. A veces se proponen arquitecturas mixtas basadas en Linux/MySql u otros productos de código abierto, pero generalmente veo esto excesivo para alguien que quiere evitar instalar SQL Server sólo por temor a lo desconocido.

Así que, en primer lugar, quiero dejar algo en claro: estoy de acuerdo en que un servidor de base de datos de primera clase es la verdadera solución. De hecho, los que usamos en mi empresa es SQL Server (y también Oracle). Si tiene al menos un par de años de experiencia con VFP, usar SQL Server no es tan difícil. Pero hay circunstancias en las que migrar a otro motor no es una opción válida. Tal vez ya tenga un montón de aplicaciones que no quiera -o no pueda- modificar, corriendo sobre tablas libres o DBCs de VFP. Lo que necesita es descargar ciertas tareas intensivas; necesita una solución remota en VFP.

Las soluciones del pobre en el período Jurásico

Desde los tiempos del DOS (en la era del FoxBase+), me apoyé en algún tipo de proceso del lado del servidor para optimizar tareas largas, manteniendo la carga de trabajo de los clientes lo más baja posible. En aquellos días, podíamos simplemente tener una tabla con un nombre de procedimiento y un campo de estado. Cuando uno quería correr uno de estos procedimientos, abría la tabla, buscaba el registro apropiado, y si el estado estaba "desocupado", lo cambiaba a "ejecutar".

Del lado del servidor, un programa estaba esperando en un bucle infinito, verificando el contenido de la tabla con cierta frecuencia. Cuando se detectaba un estado "ejecutar", era cambiado a "corriendo" y se disparaba el proceso correspondiente. Al terminar, el estado volvía a "desocupado" y el bucle continuaba.

Por supuesto, ésta no era una solución perfecta. Sólo se podía ejecutar un proceso a la vez, el bucle de espera consumía tiempo de procesador, etc. Pero piensen que el más barato de los motores de bases de datos reales costaba como mínimo decenas de miles de dólares.

Después de la revolución industrial

Al igual que los mamíferos, los sistemas operativos evolucionan. Windows llegó (presencié la aparición de Windows 2.0 ¡buah!), y pudimos moldear un poco nuestro código para que fuese más piadoso. Pero las cosas seguían sin ser ideales.

Con el tiempo aparecieron algunas soluciones fantásticas como DDE, y aprendimos que la buena teoría no siempre da buenos resultados. Después vinieron OLE, las primeras versiones de COM, una buena promesa con DCOM, y finalmente tuvimos COM+ (la historia sigue y llega hasta .Net, pero nos detendremos aquí).

Desde hace algunas versiones, VFP nos permite producir servidores COM en forma de archivos EXE o DLL. Mientras los servidores EXE son una buena solución para componentes complejos como interfaces compartidas, procesadores de automatización y cosas así, los componentes DLL son muy buenos para lógica de negocios, o implementaciones abstractas (no visuales).

Para ir al grano, hoy tenemos una manera mejor de realizar procesamiento remoto que nuestra solución original, y son los componentes COM+. Podemos implementarlos utilizando MTS (Microsoft Transaction Server) bajo NT 4 con el Option Pack 4 instalado, pero voy a utilizar su equivalente en Windows 2000 (y XP), rebautizado Servicio de Componentes.

Este servicio es un administrador de componentes que se encarga del caché, la optimización de recursos, la coordinación de transacciones entre componentes, y más. Para ponerlo en términos simples, es un espacio que alberga sus DLLs para compartirlas con aplicaciones externas en una forma controlada y eficiente. Para el propósito de este artículo, es también muy importante que hace muy sencillo el compartir estos componentes a través de la red.

También usaré directamente sintaxis y características de Visual FoxPro 7. Entiendo que algunos desarrolladores no puedan afrontar el cambio a SQL Server, pero no puedo encontrar motivo para no cambiar a la última versión de VFP, ya que es absolutamente compatible hacia atrás, y su costo es rápidamente amortizado por el incremento de productividad que permite.

Poniendo el genio dentro de la botella

Las bases de nuestro servidor de base de datos barato son sencillas. Veamos primero todo el código:
DEFINE CLASS Executor AS Session OLEPUBLIC

PROCEDURE SqlCommand( tcCommand as String ) as String

        local lcReturn
        lcReturn = ""

        Close Databases all

        Set Path To c:\develop\vfp\miniserver

        &tcCommand

        If Empty( Alias() ) or Reccount() = 0
        * Nada que hacer

        Else
        CursorToXML( Alias(), "lcReturn", 1, 1 + 8, 0, "1")
        endif

        Return lcReturn
ENDPROC

ENDDEFINE
¿Eso es todo? Bueno, básicamente si. ¿Qué es lo que hace? Sólo recibe un comando SQL (puede ser un select, update, delete, etc) y lo ejecuta. Lo importante es que, como veremos más adelante, el código es corrido en el servidor, y solamente los resultados vuelven al cliente (¿ya habían oído sobre esta idea?).

Leámoslo de nuevo, línea por línea:
DEFINE CLASS Executor AS Session OLEPUBLIC
Se define la clase como Session por sus ventajas intrínsecas, que van más allá de los límites de este artículo, y -si no las conocía- descubrirá por si mismo al probarlo. También es declarada como OLEPUBLIC para ser accesible como un componente COM al compilarse.
PROCEDURE SqlCommand( tcCommand as String ) as String
Nuestro único método (por ahora) acepta una string como parámetro (el comando SQL) y devuelve otra como resultado (veremos su contenido una líneas más abajo).
local lcReturn
lcReturn = "" 
Definimos e inicializamos una variable local. Por favor, no se duerma todavía...
 Close Databases all
 
 Set Path To c:\develop\vfp\miniserver
Esto es sólo por si acaso. Cerramos todas las áreas de trabajo y nos aseguramos que la ubicación de nuestros datos sea alcanzable. Para este ejemplo está fija, pero por supuesto, trate de hacerla configurable.
&tcCommand
Esta línea ejecuta el comando recibido. No, no hace ningún control de errores. Esa es su tarea para el hogar.
If Empty( Alias() ) or Reccount() = 0
  * Nothing to do
  Else
  CursorToXML( Alias(), "lcReturn", 1, 1 + 8, 0, "1" )
  endif
Si el comando SQL produjo algún cursor como resultado (como una típica sentencia SELECT) se lo convierte en una string XML. Esta es una forma sencilla (y a la moda) de pasar datos por la tubería entre servidores COM y los bordes del cliente. También podría usar ADO, un archivo ASCII delimitado por comas, o un DBF escrito en un directorio determinado. Cada método tiene sus pro y sus contra, pero el concepto sigue siendo el mismo.
 Return lcReturn
ENDPROC

ENDDEFINE
Devolvemos los resultados (al menos una string vacía), y todo ha terminado.
Ahora debemos probarlo. Guárdelo como MiniServer.prg y desde la ventana de Comandos, úselo:
loServer = NewObject( "Executor", "MiniServer.prg" )

messageBox( loServer.SqlCommand( "select * from products into cursor Parts") )
si todo salió bien, verá un Messagebox con el comienzo de un documentó XML como el de la Figura 1.

Figura 1: La primer prueba


Puede verlo más bonito haciendo:
loServer = NewObject( "Executor", "MiniServer.prg" )

lcXml = loServer.SqlCommand( "select * from products into cursor Parts")

XMLToCursor( lcXml, "Results" )
Browse
Por ahora, esto no es más que una forma complicada de hacer un simple SELECT, ¿no es cierto? Así que construyamos la DLL y probemos de nuevo. Genere un proyecto (lo llamaremos Miniserver.pjx en nuestro ejemplo), y compílelo como una DLL de subproceso múltiples (multi-threaded). Por ahora sólo acepte mi palabra de que en casi cualquier situación es preferible a una DLL de subproceso único (single-threaded). Hace apenas unos días hallé un caso en que no es así, pero esa es otra historia.

Puede hacer las mismas pruebas, esta vez instanciando la DLL en lugar de la clase local:
loServer = CreateObject( "miniServer.Executor" )

lcXml = loServer.SqlCommand( "select * from products into cursor Parts")

XMLToCursor( lcXml, "Results" )
Browse
Note que al escribir "loServer.", el Intellisense mostró sólo el método Executor() en lugar de todos los nativos de VFP. Esta es una de las ventajas de usar una clase Session para crear un componente COM.

Pero hasta ahora estamos corriendo el cliente y el servidor en la misma máquina. Movamos la DLL a otra parte. Puede hacer las siguientes pruebas en su PC de desarrollo sin problemas, pero lo aliento a hacerlo en otra, sólo para que aprecie cuán fácil es construir un entorno de procesamiento distribuido con VFP y COM+.

Creando un paquete COM+

Tenga cuidado con un detalle. La ruta que fije en la DLL debe apuntar al directorio en que realmente residen sus datos dentro del servidor en el que correrá su componente. Veremos más adelante porqué es tan importante indicar explícitamente esta ruta.

Ahora, copie su DLL y TLB (generada tras la compilación) a un directorio en el servidor, y luego seleccione desde el menú de Inicio de Windows, Herramientas Administrativas, Servicio de Componentes. Llegará a la Consola de Servicio de Componentes.

Figura 2: Consola Administrativa de Servicio de Componentes

Primero navegue el árbol de la Consola hasta su máquina, y dentro de ésta, hasta Aplicaciones COM+. Allí haga clic derecho y cree una nueva aplicación. Se disparará un Asistente así que es sumamente sencillo hacerlo:
  1. Haga clic en Siguiente en la pantalla de Introducción
  2. Haga clic en Crear una aplicación vacía
  3. Llámela MiniServer e indíquela como Aplicación de servidor.
  4. Seleccione el usuario que el proceso utilizará al correr. Esto puede ser peliagudo en aplicaciones reales y en un ambiente seguro. En muchos casos puede creara un usuario especial sólo con los derechos para asegurar que el componente puede funcionar según lo previsto. Para nuestro propósito actual, use su nombre de usuario y contraseña.
  5. Haga clic en Finalizar.
Por supuesto, sólo creamos la aplicación, que es un contenedor para una serie de componentes. Así que agreguemos nuestro primer y único componente dentro. Para hacerlo, busque MiniServer dentro de aplicaciones COM+. Expanda la rama y seleccione Componentes. Haga clic derecho y seleccione Nuevo para disparar otro Asistente, y siga estos pasos:
  1. Haga clic en Siguiente en la pantalla de Introducción.
  2. Haga clic en Instalar nuevos componentes.
  3. Navegue hasta el directorio donde copió la DLL y selecciónelo.
  4. Se poblarán dos listas. Arriba se ven los detalles de los archivos, mientras que debajo se ven los diferentes métodos. Sólo mírelos y haga clic en Siguiente.
  5. Haga clic en Finalizar.
Ahora puede navegar toda la jerarquía para verificar como se muestra todo. Finalmente, seleccione Componentes de nuevo (bajo MiniServer), y seleccione en el menú Ver / Estado.

Figura 3: Nuestro flamante componente COM+


Ahora, si quiere usar nuestro poderoso componente e hizo todo esto en su PC de desarrollo, está listo para hacerlo. Si siguió mi consejo y realmente lo instaló en un servidor, aún debe "decirle" a las máquinas clientes como comunicarse con éste. ¿Adivine qué? Hay otro Asistente para hacerlo. Seleccione MiniServer otra vez, haga clic derecho una vez más y seleccione Exportar.
  1. Haga clic en Siguiente en la pantalla de Introducción.
  2. Ingrese la ruta y nombre completo para el archivo de Microsoft Installer que este proceso va a generar. Usemos el mismo directorio donde ubicamos la DLL y en honor a la creatividad, llamémoslo Miniserver.msi. Luego seleccione Proxy de Aplicación, ya que esta opción creará un setup para que nuestros clientes sepan cómo instanciar el componente remotamente (la otra opción le permite generar una instalación para reproducir este paquete en otro servidor).
  3. Haga clic en finalizar. Le prometo que no usaremos más Asistentes en el resto del artículo.
Haga la prueba

Ahora puede ejecutar este archivo MSI y rápidamente preparará cualquier terminal para utilizar su componente. Para probarlo, simplemente copie y pegue este pequeño cliente de ejemplo:
Close Databases all
Clear

Local loServer as MiniServer
Local lcXml as string, lcFilter as String

loServer = CreateObject( "miniServer.Executor" )
lcFilter = InputBox( "Filtro", "Ingrese las primeras letras a buscar", "B" )

lcXml = loServer.SqlCommand( "select * from products where prod_name like '" ;
  + Alltrim( lcFilter ) +"%'" )

If Empty( lcXml )
   * Nada que hacer
Else
 XMLToCursor( lcXml, "Results" )
 Browse last
endif
Lo que hace este programa es realmente trivial. Primero cierra todas las áreas de trabajo y limpia el escritorio. Luego declara algunas variables y crea la instancia del componente. A continuación utiliza la función InputBox para pedir una letra de filtro. Antes de confirmarla, tiene una buena oportunidad para ir al servidor y echar una mirada a la consola de Servicio de Componentes. Si hizo todo bien, verá el contador mostrando una instancia activa, y la famosa bolilla girando, que muestra que el componente está en ejecución.

Ahora ingrese una letra y pulse Enter para continuar. Si todo está bien, el componente recibirá el comando SELECT, lo ejecutará y devolverá el resultado en XML. El programa convierte esta cadena nuevamente en un cursor y la muestra en un browse.

Pero tal vez las cosas no fueron tan bien, y usted obtuvo un error como el de la Figura 4.

Figura 4: La DLL no encuentra los datos

Lo que sucedió es que la ruta especificada en la clase no existe en el servidor. No importa si puso los datos en el mismo directorio que la DLL. Cuando ésta se instancia dentro de Servicio de Componentes, su directorio por defecto es System32 dentro de la carpeta principal de Windows. Pero podría que tampoco se de este caso, así que siempre debe asegurarse de establecer las rutas apropiadas.

Si tiene que arreglar esto, edite la clase, regenere la DLL y reemplácela en el servidor. Mientras no haga cambios en su interfaz, esto basta. Si más tarde agrega métodos o cambia parámetros, luego de copiar la nueva versión, reinstale la DLL repitiendo el segundo Asistente (hacerlo siempre como Nuevo Componente es más seguro, así que ignore las otras opciones), y luego ejecute el tercero para regenerar la instalación Cliente.

Tenga en cuenta también que en el servidor debe tener instalados los Runtimes de VFP. Como en todos los casos, el código dentro de su DLL no es código de máquina ni mucho menos. Por lo tanto, necesita las librerías de tiempo de ejecución. Puede instalarlas como lo hace para cualquier otra aplicación.

Conclusión

Por supuesto sólo hemos arañado la superficie de lo que podemos hacer con COM+. La idea principal de este ejemplo es brindar una manera de ejecutar comandos SQL básicos (select, update, delete, etc). Puede mejorar este componente fácilmente con un método para reindexar y compactar su base de datos (si está usando tablas de VFP), ejecutar procesos largos o complejos, y mucho más. Por supuesto, también deberá agregarle control de errores.

El objetivo principal es mostrar cuán fácil es usar esta funcionalidad. No hace falta que lea libros enteros o haga un largo curso para poder poner esta solución en marcha, utilizando VFP 7, XML y COM+ para construir su primer aplicación distribuida en menos de media hora.

Martín Salías (Buenos Aires, Argentina) es Director de Investigación y Desarrollo en Merino Aller, una empresa de software ERP y MRP para el mercado latinoamericano y español. Lleva veinte años como desarrollador y ha usado todas las versiones de FoxPro desde FoxBase+. Es Consultor de Universal Thread y co-Editor de UTMag/Rapozine.


3 comentarios :

  1. Amigos lastima q no se ven las imagenes

    saludos

    ResponderEliminar
    Respuestas
    1. José ya están corregidos los enlaces a las imágenes.

      Eliminar
  2. Hola , He seguido los pasos para hacerlo en dos maquinas un servidor y un cliente . Pero me aparece Acceso Denegado al instanciar el objeto en la maquina cliente con createobject("miniserver.executor") Alguien sabe que puede ser?

    ResponderEliminar