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
Pablo Soligo:
ResponderEliminarGracias 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
Hola Claudio,
ResponderEliminarEl 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
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???
ResponderEliminarEspero tu respuesta...
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.
EliminarSlds
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.
ResponderEliminarHola 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.
ResponderEliminarSera un problema del controlador dbxmss.dll ?
Espero tu respuesta.
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.
EliminarSlds
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.
ResponderEliminarUna 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.
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.
ResponderEliminarSin 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
Hola Paco, no puedo opinar sobre el funcionamiento ADO/DataSnap porque nunca lo he usado intensivamente.
EliminarSlds
hola pablo aky en la ultima imagen del tutorial de herencia, sale en
ResponderEliminarType 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.
Hola Rene,
EliminarEn 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
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.
EliminarHola Pablo... Sigo avanzando con DataSnap.. quisiera saber que hay q tener en cuenta con la Seguridad del servidor!
ResponderEliminarSe 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.
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:
ResponderEliminarEmpTrabaja := 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.