domingo, 21 de septiembre de 2014 0 comentarios

Forzando el Lazy Load de Cooperator Framework

El patrón llamado “Lazy Load”  o “Carga diferida” (porque traducido literalmente al español sería “carga perezosa” y no me gusta como se escucha) es una técnica usada para retrasar la carga de un objeto anidado en otro hasta el momento en que éste es necesario, es decir – no se moleste señor, si lo necesito, sé como comunicarme con usted, así que yo lo llamo después – y es un gran placer saber y comunicar que los objetos/entidades creados mediante Cooperator Framework cuentan con la implementación de este patrón de diseño.

Este patrón se puede implementar de varias formas y Cooperator Framework utiliza una de ellas llamada “Lazy Initialization” combinada con otros patrones de diseño como “factory”, “factory method pattern” y “multiton pattern”. Este patrón de inicialización diferida (lazy initialization)  se basa comúnmente en el uso de una bandera que indica si el objeto anidado ha sido cargado con anterioridad y Cooperator simula esta bandera verificando si el objeto a cargar es Nulo o no.

Como todo en la vida, usar algo con responsabilidad y cautela puede ayudar mucho y abusar de ese algo puede causar grandes problemas y todo eso se explica en los videos que tratan el tema de la impedancia (4 y 5) así como en el documento de referencia del Framework y el Modeler, donde se ve como activar esta característica del lazy load en los objetos/entidades y también en las listas anidadas mediante el Modeler, por lo que en esta entrada del blog me centraré en el uso de esta implementación para resolver un problema que para mí digamos, es poco común.

Antes de mostrar el código, he de decir que recientemente encontré una forma al parecer más correcta de solucionar el problema y evitar el uso de la carga diferida para resolverlo, pero de todos modos voy a continuar con este tema porque tal vez a alguien le sirva de ayuda en algún momento, así que primeramente voy a hacer una breve descripción del problema para que se entienda mejor el código y el porqué tuve que resolverlo utilizando lazy load y antes de finalizar la entrada mostraré el código con la solución que definitivamente creo más correcta y conveniente.

Resulta que hace un tiempo se me presentó un pequeño problema que, aunque sé como resolverlo, no me había tocado hacerlo usando Cooperator Framework y tenía que ver con la longitud de algo que en México se hace llamar “Registro Federal de Contribuyentes” o más conocido por sus siglas como RFC. El RFC es un código de identificación tributaria equivalente al CUIT en Argentina, al NIF en España, al RUC de Perú y Paraguay, al RUT  de Uruguay, al NIT que se usa en varios países de centroamérica o al SSN en Estados Unidos. Y pues bien, este dichoso RFC puede tener una longitud no menor de 12 caracteres y no mayor de 13 y esta variación de longitud se debe a otra cosa llamada “Clasificación Fiscal” (según el SAT o Servicio de Administración Tributaria). Esta clasificación fiscal es la manera en que el SAT distingue a los contribuyentes que son “Personas Físicas de otro tipo de contribuyentes a los que llaman “Personas Morales”, de tal modo que, los RFC de las personas físicas tienen una longitud de 13 caracteres y los RFC de las personas morales tienen una longitud de 12 caracteres.

Pues bien,  tengo en una aplicación una entidad Proveedor la cual puede o no estar almacenada en la base de datos con un RFC, ya que muchas veces por hacer el proceso más rápido, se puede omitir su RFC al darlo de alta en el sistema y agregarlo posteriormente pero, de tener RFC, éste debe cumplir con el requisito de la longitud. La aplicación no valida la formación del RFC, solo su longitud y por tal motivo, la validación de esta característica en la entidad Proveedor se hace utilizando su clasificación fiscal.

Para solucionar el detalle de la longitud del RFC, se modeló la entidad Proveedor con un objeto anidado llamado ClasificacionFiscalEntity que por default se carga con este modo “lazy load” al igual que todos los objetos / entidades (xxxEntity) anidados que son agregados por asociación (véase el documento de Referencia del Framework páginas 15,16 y 18) pero primero, se crean mediante el constructor de la clase principal (en este caso Proveedor) de la siguiente forma:

  1. Public Partial Class Proveedor
  2.     Inherits Objects.ProveedorObject
  3.     Implements IMappeableProveedor
  4.     Implements IEquatable(Of Proveedor)
  5.     Implements ICloneable
  6.  
  7.     #Region "Ctor"
  8.  
  9.     Public Sub New()
  10.         MyBase.New()
  11.         If _ClasificacionFiscalEntity Is Nothing Then _ClasificacionFiscalEntity = New Objects.ClasificacionFiscalObject()
  12.         If _CondicionDePagoEntity Is Nothing Then _CondicionDePagoEntity = New Objects.CondicionDePagoObject()
  13.         If _DatosDeContactoEntity Is Nothing Then _DatosDeContactoEntity = New Objects.DatosDeContactoObject()
  14.         If _DomicilioEntity Is Nothing Then _DomicilioEntity = New Objects.DomicilioObject()
  15.         If _MonedaEntity Is Nothing Then _MonedaEntity = New Objects.MonedaObject()
  16.         If _TipoDeAutorizacionEntity Is Nothing Then _TipoDeAutorizacionEntity = New Objects.TipoDeAutorizacionObject()
  17.         If _TipoDeOperacionEntity Is Nothing Then _TipoDeOperacionEntity = New Objects.TipoDeOperacionObject()
  18.         If _TipoDeProveedorEntity Is Nothing Then _TipoDeProveedorEntity = New Objects.TipoDeProveedorObject()
  19.  
  20.     End Sub

En el fragmento de código anterior se puede ver que al crear la nueva entidad, se revisa si los objetos/entidades anidados han sido instanciados y en caso de no ser así, se crean mediante su constructor.

Ahora bien, todo esto funciona bien normalmente, pero en mi caso existe el problema de que la propiedad EsPersonaMoral de ClasificacionFiscalEntity es de tipo Boolean y por lo tanto se inicializa en Falso y es utilizando el valor de esta propiedad que se hace la validación. Por lo que si tengo un código como el siguiente:

  1. If (Me.RFC.Trim().Length <> 12 And Me.ClasificacionFiscalEntity.EsPersonaMoral) Then
  2.    AddError(ProveedorColumn.RFC.ToString("G"), String.Format(msgWrongLenght, "El R.F.C.", 12))
  3. Else
  4.    RemoveError(ProveedorColumn.RFC.ToString("G"), String.Format(msgWrongLenght, "El R.F.C.", 12))
  5. End If
  6.  
  7. If (Me.RFC.Trim().Length <> 13 And (Not Me.ClasificacionFiscalEntity.EsPersonaMoral)) Then
  8.     AddError(ProveedorColumn.RFC.ToString("G"), String.Format(msgWrongLenght, "El R.F.C.", 13))
  9. Else
  10.     RemoveError(ProveedorColumn.RFC.ToString("G"), String.Format(msgWrongLenght, "El R.F.C.", 13))
  11. End If

nunca validará correctamente la longitud de los RFC’s de las personas morales, porque la propiedad EsPersonaMoral siempre tendrá el valor de Falso.

Entonces, para resolver el problema de inicialización de esta propiedad se hace algo que yo llamo “EggedLoad” (o sea “Carga de a Huevo”); bueno no, ya en serio le llamo “Forced LazyLoad” ya que literalmente estoy forzando a que se cargue la entidad ClasificacionFiscal utilizando los métodos compartidos del mecanismo de LazyLoad. Y esto se muestra en el siguiente fragmento de código:

  1. ' Esto forza a que se recargue la propiedad ClasificacionFiscalEntity vía LazyLoad.
  2. ' El campo _ClasificacionFiscalEntity está declarado en Proveedor.Auto.vb
  3. Dim lazyProvider As LazyLoad.ILazyProvider = LazyLoad.LazyProviderFactory.Get(GetType(Objects.ClasificacionFiscalObject))
  4. _ClasificacionFiscalEntity = CType(lazyProvider.GetEntity(GetType(Objects.ClasificacionFiscalObject), _
  5.                                                           New Objects.ClasificacionFiscalObject(Me.IdClasificacionFiscal)),  _
  6.                                    Objects.ClasificacionFiscalObject)
  7.  
  8. If (Me.RFC.Trim().Length <> 12 And Me.ClasificacionFiscalEntity.EsPersonaMoral) Then
  9.    . . .

Con este par de líneas de código (divididas en varias para que no queden tan largas) se logra forzar la recarga de una entidad mediante el mecanismo de lazyload. Lo que sigue a esta recarga, son las validaciones que hacen referencia a la propiedad EsPersonaMoral la cual tendrá ahora el valor correcto.

Ahora bien, la desventaja que tiene hacer esta carga forzada, es que cada vez que se ejecute el método Validate del objeto/entidad se hará una consulta a la base de datos, lo cual podría afectar el rendimiento de la aplicación si se utiliza la técnica del databinding ya que, el método Validate se ejecuta prácticamente cada vez que se cambia el foco de entrada de un control a otro y si a eso se agregan los llamados explícitos a dicho método…pues ya sabrán.

La solución que encontré para evadir estas continuas consultas a la base de datos fue implementar el controlador del evento SelectedIndexChanged del combobox al que tengo enlazada la lista de clasificaciones fiscales haciendo lo siguiente:

  1. Private Sub cboClasificacionFiscal_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles cboClasificacionFiscal.SelectedIndexChanged
  2.     If bolLoaded Then
  3.         oProveedor.ClasificacionFiscalEntity = CType(cboClasificacionFiscal.SelectedItem, ClasificacionFiscalObject)
  4.  
  5.     End If
  6.  
  7. End Sub

En el fragmento de código anterior se puede ver que cada vez que se cambia el elemento del combobox, se asigna directamente el objeto/entidad asociado con este control a la propiedad ClasificacionFiscalEntity de la entidad Proveedor. La bandera “bolLoaded” se utiliza para que el código de este método no se ejecute durante la carga del control, sino solamente hasta que todo el formulario está cargado.

Con esta sencilla implementación se ahorra mucho tiempo comparado con la técnica del “EggLoad”, porque no hay consultas a la base de datos y además la entidad asociada (ClasificacionFiscalEntity) con el objeto principal (oProveedor) siempre tiene el objeto correcto con sus propiedades cargadas también correctamente.

Y eso es todo, en realidad es mucho rollo para mostrar un par de líneas que son las que importan en esta entrada, pero la explicación del problema era necesaria. Espero que algún día esta entrada le ayude a alguien que use Cooperator Framework y necesite forzar la carga de un objeto/entidad con él mecanismo de lazy load.

 
;