domingo, 12 de junio de 2011

DataSnap, DBExpress y Threads

El primer problema al que se enfrenta el desarrollador DataSnap principiante es el problema de las conexiones a la base de datos. La costumbre le hace rápidamente cometer un error. En su afán por ahorrar conexiones es sumamente probable que cree un datamodule y redireccione todas sus datasets a la conexión disponible en el mismo. Acá es donde aparece el problema, dependiendo del lifecycle seleccionado en el componente TDSServerClass es altamente probable que varios threads intenten usar esta conexión, dbexpress no es thread safe y para peor podemos generar una buena cantidad de problemas si todos los thread manejan las mismas conexiones, active recordset etc.

Solución:

Una conexión por ServerModule.
Esto abre un interrogante, ¿Tengo que tomarme el trabajo de meter una conexión y configurara directamente en cada uno de mis servermodules?¿Tengo que generar el código para tomar los parámetros del conexión en cada uno de mis servermodules?
La respuesta es no, podemos usar la herencia que tan bien funciona en Delphi para codificar una vez y reutilizar cuando se necesite.De hecho esta saludable practica no solo va a aportar soluciones a este problema, nos va a ayudar en otras problemáticas que aparecerán en escenarios futuros cuando profundicemos mas en esta tecnología. En futuras entradas podremos sacar mas provecho de lo expuesto aquí.
Vamos paso paso como hacer esto.

Crear el servidor datasnap
Si lo desea puede utilizar el wizard de delphi














Seleccione la opción VCL Forms Application, Habilite TCP/IP y HTTP (Por las dudas si lo necesita en el futuro, para esto no es necesario), y en la ultima acción del wizard seleccione TDSServerModule.
El servermodule creado automáticamente por el wizard solo servirá a los fines de heredar de el los servermodules que verdaderamente ofrecerán la funcionalidad, coloque un nombre que demuestre su condicion por ejemplo
uAbstractServerModule para el archivo.
TAbstractServerModule para la clase.

Verifique que dicho servermodule no figure como auto creado (Project->Options->Forms)

Vale aclarar que este server module "abstracto" en realidad no lo es tal, el nombre indica su condicion donde del software pero no es una clase abstracta.

Conexión en el ServerModule Abstracto
Lo llamamos abstracto por nunca se va a crear una instancia directamente sino que sera por medio de servermodules hijos. A pesar de esto tenemos cosas que hacer en modulo, comenzamos configurando una conexión en el dataexplorer y haciendo drag and drop












































Hagamos uso del evento beforeconnect de la conexión para levantar los parámetros de la misma en tiempo de ejecución















Usemos también los eventos de creación y destrucción del servermodule para abrir y cerrar conexión respectivamente















Ahora si, casi estamos, nos quedan algunos detalles pero vamos a crear nuestro primer servermodule "real", para ello seleccionamos File->New->Other->Inheritable items

















Seleccionado claro, como base nuestro servermodule abstracto.
Para ir terminando, creamos una archivo ini en la misma carpeta donde esta nuestros servidor DataSnap, copiamos los parametros de conexión de dbxConnections.ini como indica la figura.














Si se trabaja con SqlServer no olvidar el parametro MARS_Connection para habilitar "multiple active record set"















Ahora creamos el cliente, verificamos que los métodos en la clase abstracta están presentes en la clase proxy generada, el sistema de herencia funciona.


















Como dijimos en próximas entradas veremos mas ventajas de operar con herencia en servermodules

15 comentarios:

  1. Pablo Soligo:

    Gracias por tu aporte, leí el articulo y me pareció superinteresante. Con respecto al ejemplo que vas a publicar en otras entregas como decís en el Blogs, te agradecería si podes contemplar lo siguiente..

    1.El pedido tiene un campo NumeroComprobante que es correlativo y no puede tener huecos.. (En este caso no es posible usar el generador)
    2. Que la tabla detalle del Pedido tenga un campo Orden (que permite mantener el orden de los items dentro del Pedido).

    Te agradezco mucho el aporte que estas haciendo, me resulta de mucha utilidad..

    Saludos, Claudio Viñas

    ResponderEliminar
  2. Hola Claudio,

    El punto 2 es bastante trivial, lo podemos manejar con el evento OnNewRecord mostrado en
    http://pablosoligo.blogspot.com/2011/07/master-details-y-autoincrementales.html
    Para el punto 1 se pueden utilizar un lifeCycle Server o también las secciones criticas mostradas en http://pablosoligo.blogspot.com/2011/06/threads-y-secciones-criticas.html para asignar de a uno a la vez, eso si con datasnap, en aplicaciones cliente/servidor es mas complicado. Puede ser el motivo de una nueva entrada en el blog

    ResponderEliminar
  3. Pablo, estoy comenzando con DataSnap en 3 capas... quisiera preguntarte si los ServerModules hijos.. hay que crearlos en algun momento en el servidor o los crea el cliente delphi al momento que consume el metodo?.. Que es mejor,, que la instanacias de los metodos se consuman desde una clase proxy como explicas aca o desde el componente que trae delphi para ejecutar metodos del Servidor DataSnap???
    Espero tu respuesta...

    ResponderEliminar
    Respuestas
    1. Hola Marcelo, la primera pregunta no se si la entiendo del todo, yo los servermodules los creo via herencia, porque en la clase padre pongo la conexión los métodos de log (Con secciones criticas) y demás cuestiones comunes y necesarias. Esto es programación pura y es independiente del lifeCycle (Server/Session/Instance)que según el caso decidirás cual usar. Respecto a la segunda pregunta la verdad no hay mejor ni peor, la clase proxy es mas comoda porque queda todo tipificado y tenes disponible el autocompletar del codigo, pero como desventaja con cada cambio en el server necesita ser recreada, si te fijas en el codigo de la clase proxy no hace mas que usar un componente como el que usas cuando haces drag and drop.
      Slds

      Eliminar
  4. Pablo necesito ayuda para navegar una base de datos DBISAM remotamente desde un servidor linux. No existen JDBC connectors a esta base de datos por lo que tengo que desarrollar un método alternativo. Me gustaría ver si me podrías ayudar a generar un demonio que escuche consultas http. La idea sería pegarle desde mi linux y mandarle el select sql en el request. El demonio realiza la consulta de forma local y me devuelve el resultado en un xml. Estoy buscando quien me pueda desarrollar esto como freelance.

    ResponderEliminar
  5. Hola Pablo, Hice todos estos pasos, pero al ejecutar el Cliente me da un Error que dice: "Remote Error: DBX Error: Driver could not be properly initialized. Client library may be missing, not installed properly, or of the wrong version." utilizo delphi 2010 con SQL Server 2008.
    Sera un problema del controlador dbxmss.dll ?
    Espero tu respuesta.

    ResponderEliminar
    Respuestas
    1. Jeremias, fácil, es un error muy común, a mi me pasa cada tanto. O no tenes el driver instalado o es la versión incorrecta. Yo cuando encuentro la version de driver que se lleva bien con mi dbxmss.dll/dbxmss9.dll la guardo con mucho amor o la adjunto al proyecto para no perder tiempo con estas cosas.

      Slds

      Eliminar
  6. Gracias Pablo, voy a probar de buscar el Driver adecuado. Me sorprende que el controlador por defecto no funcione, ya que cuando hice la prueba con las dos aplicaciones (servidor y cliente) en mi maquina funciono, pero cuando traslade la aplicación servidor a otra máquina de la red y al cliente le asigne el IP del router para que se conecte por internet salto el problema antes mencionado.
    Una consulta mas. ¿Es necesario instalar el SQL Native Client para que funcione la aplicación vía internet?
    Agrego que utilizo: SQL Server 2008 R2 Express, Delphi 2010.

    ResponderEliminar
  7. Hola Pablo, tengo una pregunta, hice la aplicación servidor como indicas, luego en los servermodule hijos, tengo ADOQuery para realizar las consultas, tengo querys que son un poco grandes y demora algunos segundos en realizarse, si durante estos segundos un segundo cliente intenta hacer la misma consulta ocurre un error ya que el segundo cliente ocupa el mismo AdoQuery y se interrumpen ambos procesos.
    Sin embargo si el adoquery lo pongo sobre el AbstractServerModule no pasa este detalle, ¿tienes idea de que me falta configurar?
    Tengo el DSServerClass como Session en la propiedad LifeCycle

    ResponderEliminar
    Respuestas
    1. Hola Paco, no puedo opinar sobre el funcionamiento ADO/DataSnap porque nunca lo he usado intensivamente.

      Slds

      Eliminar
  8. hola pablo aky en la ultima imagen del tutorial de herencia, sale en

    Type TbasicsFunctionsClient = Class(TDSAdminClient)

    y a mi me sale.

    type
    TAbstractServerModuleClient = class(TDSAdminClient)

    He realizado los pasos muchas veces y llego a lo mismo te agradeceria muchisimo si me podes orientar un poco mas.
    Ya que estoy muy interesado en aprender lo que tu expones aky pero tengo un conocimiento basico. desde ya muchas gracias.

    ResponderEliminar
    Respuestas
    1. Hola Rene,
      En el server yo creo un datamodulo "abstracto" del cual luego heredo y le pongo el nombre TBasicFunctions. Por tanto cuando genero la clase proxy del lado cliente la misma se genera con el nombre TbasicsFunctionsClient. Del lado servidor no publico el modulo abstracto como creo lo estas haciendo vos. El modulo abstracto es simplemente para heredar y ahorrarme codificaciones comunes de conexiones de base de datos y demás. El tutorial toca un tema muy puntual que es el problema de los threads y las conexiones dbexpress. Para temas mas básicos o generales, o de como publicar tus módulos encontraras mucha información y white papers en internet, te recomiendo los de Bob Swart

      Slds

      Eliminar
    2. muchas gracias por responder te lo agradesco. Hoy pude realizar ejemplos de las 5 secciones de BOB Swart, pero no me sale como crear una datamodulo abstracto, si es posible ese dato se lo agradese. hasta luego que ande muy bien.

      Eliminar
  9. Hola Pablo... Sigo avanzando con DataSnap.. quisiera saber que hay q tener en cuenta con la Seguridad del servidor!
    Se puede tambien, indicarle al Cliente o Servidor, ( no se donde va). el ciclo de vida que tendrán.. Me refiero con esto, que cada tanto tiempo inactivo se cae la conexin dBExpress... Sabes como setear eso?...
    SLDS, marcelo.

    ResponderEliminar
  10. Hola Pablo, Estoy intentando realizar una aplicacion con servidor datasnap, en el servidor tengo una funcion que retorna un TStringList pero si lo coloco asi:
    EmpTrabaja := TStringList.Create;
    try
    ClDEmpTrabaja.First;
    EmpTrabaja.Sorted := True;
    while not ClDEmpTrabaja.Eof do
    begin
    EmpTrabaja.Add(ClDEmpTrabaja.FieldByName('Desc_Empresa_Trabaja').AsString);
    ClDEmpTrabaja.Next;
    end;
    Result := EmpTrabaja;
    finally
    FreeAndNil(EmpTrabaja);
    end;

    Al ejecutarla desde la aplicacion cliente sale error pero si no elimino el Tstring que se esta creando no sale error. Pero si dejo eso asi me crearia muchos TStringlist cada rato que se ejecute esta funcion y quedarian creados ocupandome memoria y degradando el sistema.

    ResponderEliminar