miércoles, 17 de diciembre de 2008

Un ejemplo práctico

image

Con esta entrada pretendo dar una visión generar del modelo y mostrar algunos ejemplos de su utilización.

Quiero hacer hincapié en que lo que se pretende es acortar el tiempo de desarrollo, homogeneizar el código fuente y ALGO MUY IMPORTANTE: reducirlo . Ya lo dijo el sabio “menos líneas menos errores”.

Lo primero que tenemos que hacer es configurar una solución con el aspecto de la imagen de la derecha.

Todos los ficheros están como antiguas entradas. La tabla que vamos a utilizar tiene el siguiente definición:

CREATE TABLE [dbo].[T_Envio](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [IdEnvio] [nvarchar](250) COLLATE Modern_Spanish_CI_AS NOT NULL,
    [Observaciones] [nvarchar](200) COLLATE Modern_Spanish_CI_AS NULL,
    [NumeroDeRegistro] [nvarchar](250) COLLATE Modern_Spanish_CI_AS NOT NULL,
    [FechaDeRegistro] [datetime] NOT NULL,
    [Fecha] [datetime] NOT NULL CONSTRAINT [DF_T_Envio_Fecha]  DEFAULT (getdate()),
    [Usuario] [nvarchar](250) COLLATE Modern_Spanish_CI_AS NOT NULL,
 CONSTRAINT [PK_T_Envio] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

A continuación deberemos crear un nuevo proyecto para la capa que nos queda, la capa de presentación. En este proyecto es en el que veremos las distintas formas de utilizar los objetos.

De dónde se obtienen los datos.

Una de las primeras cosas que, cuando empecé en esto, me sorprendía, es que en todos los ejemplos de acceso a datos que encontraba por ahí, las conexiones con la base de datos se establecían al ladito de donde se cargaban los objetos de la capa de negocio. Esto, a mi modo de ver, no encaja en un enfoque orientado a objetos donde estos TIENEN que tener la posibilidad de no conocer cual es origen de los datos, pero también TIENEN que tener la posibilidad de especificarlo.

¿Como se resuelve esta aparente paradoja? Pues dotándoles de dos propiedades que son delegados que retornaran, si están establecidos, la conexión y la transacción asignados a ese objeto. En el caso que no establezcamos dichos valores será la capa de acceso a datos la que tenga que decidir que conexión utilizar. Esta decisión se basa en si el proyecto es WEB o WindowsForm (supongo que la conexión está abierta durante la ejecución de la aplicación).

Para simplificar las cosas aquí van dos ejemplos:

Aplicación ASP.NET

  Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
    'Dal.DataServer.SetWebConnection(System.Configuration.ConfigurationManager.AppSettings("ConnectionString"))
    Dal.DataServer.SetWebConnection("server=.;database=TEST;uid=user;pwd=xxx")
  End Sub

Aplicación WindowsForm:

Dal.DataServer.Open("server=.;database=TEST;uid=user;pwd=xxx")

La diferencia entre los dos “Modelos” es que en la primera se especifica la cadena de conexión y en la otra se abre realmente y queda a disposición de la aplicación.

Veamos cual es el camino que sigue una petición de carga:

1. Se solicita la carga de un objeto:

 Dim __Envios As Negocio.Envios = New Negocio.Envios().Carga()

2. La capa de negocio pasa la petición a la de Datos y le informa de lo que sabe (sus “provider”)

    Public Function Carga(ByVal id As Integer) As Envio
      Using D__ As New Dal.Envios(ConnectionProvider, TransactionProvider)
        Dal.Loader.LoadObject(Me, D__.GetItem(id))
      End Using
      Return Me ' El que los métodos devuelvan la instancia permite muuuuchas cosas.
    End Function

En este enlace se puede ver algún ejemplo de lo que se puede conseguir con la implementación de funciones que devuelven la instancia en lugar de la implementación de simples métodos. El que lo dice no es un cualquiera.

3. El DAL realiza una serie de comprobaciones para determinar los parámetros( conexión y transacción) que debe utilizar:

Protected Function CreateCommand() As System.Data.IDbCommand
  Dim cmd As IDbCommand = ConexionEnUso.CreateCommand()
  cmd.Connection = ConexionEnUso
  cmd.Transaction = TransaccionEnUso
  Return cmd
End Function
Private _ConexionActual As IDbConnection = Nothing
Protected ReadOnly Property ConexionEnUso() As IDbConnection
  Get
    ' Primero se intenta usar la conexion asignada al objeto
    If _ConexionActual IsNot Nothing Then Return _ConexionActual
    ' Luego se intenta usar el proveedor de conexion
    If Me.ConnectionProvider IsNot Nothing Then
      _ConexionActual = ConnectionProvider.Invoke()
      Return _ConexionActual
    End If
    ' Si la conexion compartida no existe se crea una nueva (normalmente para uso con WebForm)
    If DataServer.Connection Is Nothing Then
      _ConexionActual = DataServer.GetNewConection()
      Return _ConexionActual
    End If
    Return DataServer.Connection() ' Se devuelve la connexion compartida (Esta ser� la usada normalmente por WindowsForm)
  End Get
End Property
Public Function GetItem(ByVal id As Integer) As IDataReader
  Using cmd As IDbCommand = CreateCommand()   
    cmd.CommandText = String.Format("Select * from [TELMA_Envio] where Id={0}", id)
    Return DataServer.ExecuteReader(cmd)
  End Using
End Function

Otro día entraré a fondo en la clase DataServer y sus métodos y como nos permite controlar, trazar TODAS las consultas que hacemos a la base de datos. También debería volver a buscar las páginas de las que aprendí todo lo que estoy poniendo aquí y enlazarlas. Prometo hacerlo.

Cómo deben comportarse los objetos

Los objetos deben comportarse bieen y ser bueeeeeeeeenos. Además, y algo muy importante, TODOS deben comportarse de forma parecida a fin de que el conocimiento por parte de un componente del equipo del código de un objeto u objetos, le haga sentirse a gusto con el código desarrollado por los otros componentes.

Yo propongo que:

  1. Los elementos (no colecciones) deben poder grabarse, borrarse y cargarse.
  2. La grabación: Inserciones y actualizaciones se facilita si adoptamos Id únicos por registro. Yo siempre que puedo uso auto numéricos.
  3. Con autonumericos el establecimiento de objeto.Id=0 implica una inserción en la base de datos al llamar a objeto.Save(). Posteriores llamadas as Save() provocan la actualización.
  4. Los constructores de objetos NUNCA cargan el objeto. Utilizo New Negocio.Envio().Carga(25).
  5. Los Elementos No cargan colecciones, eso lo dejo para las coleccionesDeObjetos
  6. Las Colecciones de objetos SON LAS RESPONSABLES de cargar grupos de objetos. Para ello deberán tener metodos con nombres significativos como: CargaPorId(codigo), CargaPendientes(), etc.  Por favor evitar esto: CarIElePend(), No hay quien se entere luego.
  7. Siempre que se pueda, y aquí entran cuestiones de rendimiento, el primer enfoque que doy a la carga de entidades relacionada es la Lazy Load o Carga Perezosa con algun mecanismo de caheo. De esta manera los objetos no pierden “su integridad” y siguen funcionando como se espera tras implementar diferentes modos de carga: en un escenario haciendo un inner join con Provincias y en otro escenario con Facturas donde no interesa en absoluto la información de las anteriores. Esto también es tema de grandes discusiones y de otro posible post.

El ejemplo

Bueno todo esto empezó con que iba a poner un ejemplo. Al final me he liado.

Una vez que tenemos una ventana de prueba en un proyecto de prueba en el que hemos referenciado Dal.dll y Negocio.dll

image

escribimos es siguiente código en el formulario:

Public Class Form1
  Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    ' Si quisieramos una sola conexión por aplicación descomentariar la linea siguiente
    'Dal.DataServer.Open("server=.;database=TRACTORES;uid=xxx;pwd=xxx")
    ' Para simular un entorno WEB donde las conexiones se deben crear y cerrar mu rapido :-)
    Dal.DataServer.SetWebConnection("server=.;database=TRACTORES;uid=xx;pwd=xx")
  End Sub
  Private Sub btnEjecutar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEjecutar.Click
    Me.ListBox1.DataSource = Nothing
    With New Negocio.Envios().Carga()
      Me.ListBox1.DisplayMember = "IdEnvio"
      Me.ListBox1.DataSource = .this
    End With
  End Sub
End Class

Nuestra ventana se ve así

image

Bueno pues esto es todo por hoy.
No he pensado nunca en hacer esto, y mira lo he hecho he escrito un articulo en un blog.
CONTINUARÁ
Saludos.

1 comentario:

Anónimo dijo...

Fantástico !!! Espero impacientes próximas entregas.

“menos líneas menos errores” , más rapidez en desarrollo, fácil mantenimiento: es el objetivo. Pero existen muchos caminos o no para ello.

Se puede generar un debate:

Siendo puristas, objetos de lógica de negocio (entidades como Envios) que dispongan de propiedades de acceso a Conexión-Transacción puede generar opiniones a favor o en contra.

Otro debate, el caso de mantener una conexión abierta durante "toda la vida" de la aplicación Windows.

Una complicación más, el uso de esta arquitectura utilizando WCF. Sería interesante que Microsoft creara una aplicación de ejemplo con esta metodología. Con Servicios WCF ya no se puede tener una conexión abierta durante toda la ejecución de la aplicación Windows que usa los servicios (o sí?).
El tema de transacciones de Servicios WCF habría que estudiarlo...


En definitiva, lo que cuenta es un caso práctico real donde se vea el potencial de una arquitectura así, que el rendimiento sea bu ena y el mantenimiento respecto a cambios sea fácil.

Donde Microsoft no dice ni mú, ni aporta aplicaciones de ejemplo que sean reales con código fuente, en Codeproject, como en Geeks y usted mismo dan salto de calidad !!!

Gracias !!!