lunes, 7 de enero de 2008

como crear com+ conexion




Parte I Introducción: Descripción del escenario, datos del ambiente y lo que deben saber sobre transacciones
Acerca de las Transacciones
Para este tema deben estar familiarizados con lo que son las transacciones. A continuación les explicaré de manera muy sencilla porque se necesidad, pero pueden encontrar mas detalles en el sitio oficial de MS para transacciones: Transaction Management in Windows.
Un ambiente transaccional es requerido cuando estamos ejecutando un proceso constitudido de "n" pasos y cada uno de esos pasos puede realizar cambios en un sistema/app/DB. Si en alguno de esos pasos llegue a fallar, que es lo que pasa con el resto de los pasos que ya fueron ejecutado y que realizaron modificaciones. En el 90% de los casos, lo que se requiere es que la información que fue alterada sea restituida para mantener la consistencia de la información y para ello las transacciones nos ayudan. De tal forma que si estamos ejecutando nuestro proceso en un ambiente transaccional, este se encarga de que si alguno de los pasos falla, todas las modificaciones ocurridas durante dicha transacción se restablezcan automáticamente.
Una cuestión que se debe considerar cuando queremos realizar transacciones es que el sistema/app/DB que queremos que participe en dicha transacción debe soportar transacciones, el ejemplo por excelencia son las bases de datos (DB) como SQL, DB2, Oracle, etc. que son aplicaciones que están hechos para poder soportar transacciones, que por el contrario si vemos el File System de Windows que no soporta transacciones, por lo que si uno de las pasos dentro de nuestro proceso realiza una operación como crear un archivo y durante la ejecución existe alguna falla, la transacción deshará los pasos anteriores pero nuestro archivo simplemente se quedará intacto pues el File System no maneja transacciones. En su actualidad los ambientes que soportan transacciones son limitados, pero es una tendencia que puede ir cambiando con el tiempo y con los requerimientos.
Para ambientes que no maneja transacciones, estas deben ser controladas de manera manual, esto es que si creamos un archivos en el File System y el proceso falla en algún punto, tendremos que ejecutar un proceso de borrado de dicho archivo a esto se le llama compensación de transacciones y como se tiene que hacer el trabajo que no hace el sistema, puede llegar a ser bastante complicado el desarrollar este tipo de transacciones.
Otro hecho muy importante que se debe considerar es que cuando empleamos transacciones (haciendo notar que su uso es opcional) el performance se ve disminuido en comparación del mismo proceso cuando no se emplean transacciones.
Claro que en esta descripción existen muchas implicaciones muy interesantes, pero es la manera mas sencilla de explicarlo en este momento y los detalles serán explicados a lo largo de este post.
Ahora que hemos descrito lo que es una transacción, BTS dentro de las orquestaciones nos da la habilidad de poder emplear transacciones ya que una de sus funciones es poder modificar sistemas/app/BD y esto trae consigo la necesidad de mantener consistencia en la información en el caso de que uno de los sistemas/app/DB es modificado y existe una falla durante el proceso.
Acerca del Ambiente
El ambiente donde desarrolle este ejemplo es una maquina virtual con Windows 2003 Server SP1, BTS 2006 RC y SQL 2005. En el laboratorio emplearemos un .NET ServicedComponent (.NET con soporte de COM+ para realizar las transacciones sobre una base de datos de SQL.
Un elemento que controla las transacciones y que necesita funcionar correctamente es el Microsoft Distribute Transaction Coordinator (MSDTC). Los problemas mas comunes que provocan que no funcione correctamente pueden ser por instalación, configuración o por problemas de seguridad (firewall y banderas de acceso). Si están 100% seguros de que el MSDCT funciona correctamente, pueden continuar con la siguiente sección, si desean probar si esta funcionando correctamente, pueden emplear las siguientes herramientas:
DTCPing.exe. Esta herramienta no la he probado y la documentación no esta bien detallada. Soporta solamente Windows 2000.
DTCTester Tool. Permite probar conectividad empleando un SQL, que puede ser tanto local como remoto y básicamente lo que hace es ejecutar un comando en SQL empleando una transacción por medio de un DNS y por eso no importa la versión de SO. Esta herramienta contiene una mejor documentación que el DTCPing.
Si ya comprobaron que no funciona el MSDTC, la siguiente información les ayudará a encontrar el problema. Para revisar la configuración se debe contemplar el como esta distribuido nuestra ambiente, esto es si el BTS y el SQL están instalados en la misma máquina o en distintas. Aunque en nuestro laboratorio esta instalado todo en una sola máquina, incluyo la revisión de ambos ambientes, tanto el local como el distribuido:
1.
Local. Esto significa que tenemos BTS y SQL en la misma máquina y para esto solo revisaremos que la configuración de MSDTC este correcta. Por default tanto en Windows 2003 como en el SP1, esta configuración esta deshabilitada por cuestiones de seguridad, por lo que hay que activarla.
Pasos:
a.
Click Start, luego Administrative Tools, y finalmente Component services
b.
Una vez en Component Services, ir a la pestaña de MSDTC y luego en Security Configuration (ver imagen) revisar que los siguientes settings estén idénticos (ver imagen).
2.
Distribuido. Esto significa que BTS y SQL están instalado en distintas máquinas. También en esta configuración se debe llevar acabo el punto anterior, pero lo mas importante es revisar que el MSDTC tenga instalado el soporte de transacciones por red, ambos pasos deben ser para todas las máquinas que confirman el ambiente, las cuales pueden ser más de 2 si es que se trata de una arquitectura compleja. Por default Windows 2003 no tiene esta opción instalada. El artículo original lo pueden encontrar aquí. Este artículo contiene una liga por si necesitan configurar el MSDTC en un cluster de Windows 2003.
Pasos:
a.
Click Start, luego Control Panel, luego Add/Remove Programs y luego Add/Remove Windows Components.
b.
En Windows Componentes Wizard dar doble click en Application Server (ver imagen).
c.
Activar la opción de Enable network DTC access y dar click OK, Next, Finish (ver imagen).
d.
Se debe Reiniciar el servicio el servicio de Distribuited Transaction coordinator. Para esto existen varias formas, así que les explicare cada una de ellas:
i.
Con línea de comando con 2 instrucciones en este orden: net stop msdtc y net start msdtc
ii.
Otra forma con más clicks es:
1.
Click en Start, luego Administrative Tools y luego Services.
2.
En Services, buscar por nombre Distributed Transaction Coordinator y con click derecho ir a la opción de Restart y dar click sobre la opción (ver imagen).
Aprovechando que estamos en esta pantalla, para ver como es que obtuvimos el nombre(msdtc) para ejecutar el comando en la opción a, dando doble click sobre el servicio de Distribuited Transaction Coordinator para acceder a las propiedades, en la pestaña de General localizamos Service Name y como verán es MSDTC (no es case sensitive). Pueden hacer lo mismo de ejecutar en línea de comando para iniciar o parar otros servicios, aunque habrá algunos que no podrán ser detenidos por ser servicios especiales. Si el nombre del servicio contiene espacios, deben emplear comillas para que funcione, ej. net stop "EDI Subsystem" (ver imagen).
iii.
En el servidor de SQL existe una forma adicional para reiniciar el servicio que es por medio del SQL Server Service Manager. Por medio del botón de Stop y Start (ver imagen).
e.
También se deben reiniciar los sistemas/app/DB que soporten transacciones, como lo es el servicio de SQL y el Microsoft Message Queuing si es que esta instalado.
Por último, pueden existir elementos de seguridad que necesitan ser revisados debido primordialmente a las actualización del SO, como los con los SP. Les adjunto una serie de ligas que les pueden ayudar a revisar algunos detalles importantes si es que el MSDTC sigue sin funcionar.
New DTC Functionality in Windows Server 2003 Service Pack 1, aquí.
You receive an "A Microsoft Distributed Transaction Coordinator problem prevented connection to the Configuration database" error message when you publish assemblies from BizTalk Server 2004, click aquí.


Parte II .NET y COM+: Preparando nuestro componente transaccional y comprobando su funcionamiento
Construyendo el componente
En esta parte construiremos el componente de .NET con soporte de transacciones COM+. El código funciona tanto para BTS 2004 como para BTS 2006, solo tiene que ser recompilado. Pueden bajar el código fuente en el post.
Como saben todavía en el .NET Framework 2.0.se emplea el modelo de transacciones de COM+, lo cual representa una capa de interoperabilidad, pero próximamente tendremos la siguiente versión de COM+ para el manejo de transacciones distribuidas es Windows Communication Foundation WCF (Indigo) y no solo para reemplazar COM+, sino para proveer soporte en otros modelos de comunicación existentes como Remoting y para modelos transaccionales que no existían como en los Web Services (recordando que los Web Services no son transaccionales en la actualidad por lo que se aplican las transacciones por compensación).
Para la aplicación necesitaremos de una base de datos en SQL como se muestra en este diagrama y también anexo el script.
Ahora en este caso emplearemos una operación de inserción a una base de datos para mostrar la transaccionalidad. Para bases de datos, ADO.NET ofrece 2 formas para hacer las transacciones:
1.
DbConnection.BeginTransaction. Esta forma ofrece la ventaja de ejecutar por medio de la misma conexión "n" comandos que participarán en la transacción. Todos los proveedores de ADO.NET ofrecen este mecanismo (Oracle, Informix, etc) un ejemplo es:
private static void ExecuteSqlTransaction(string connectionString){ // El using nos ayuda a no tener que hacer un connection.Close() using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand command = connection.CreateCommand(); SqlTransaction transaction; //La conexión ya debe estar abierta para crear la transacción transaction = connection.BeginTransaction("SampleTransaction"); command.Connection = connection; command.Transaction = transaction; try { command.CommandText = "Insert into Region (R_ID, R_Description) VALUES (100, 'Description')"; command.ExecuteNonQuery(); command.CommandText = "Insert into Region (R_ID, R_Description) VALUES (101, 'Description')"; command.ExecuteNonQuery(); transaction.Commit(); Console.WriteLine("Both records are written to database."); } catch (Exception ex) { Console.WriteLine("Commit Exception Type: {0}", ex.GetType()); Console.WriteLine(" Message: {0}", ex.Message); try { transaction.Rollback(); } catch (Exception ex2) { Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType()); Console.WriteLine(" Message: {0}", ex2.Message); } } }}
Este mecanismo funciona bien si los comando se ejecutan sobre la misma conexión, pero si se requiere que la transacción opere sobre mas de 1 conexión, entonces ya se requiere de otro método transaccional.
2.
COM+. El cual abarcamos en este artículo, pero como el mundo COM+ es muy grande, solo será lo necesario para nuestro componente.
Partamos de un class library para poder ejecutar una inserción de un registro a una base de datos:
using System;using System.Data;using System.Data.SqlClient;using System.Diagnostics;using System.Xml;namespace EnterpriseComponents.DB{ public class TxComponent { public void InsertRows(string DBConnString,int ID) { SqlConnection objConn = new SqlConnection(); SqlCommand objComm = new SqlCommand("insert into monitor values (@ID,@D)", objConn); SqlParameterCollection Pams = objComm.Parameters; objConn.ConnectionString = DBConnString; Pams.Add("ID", SqlDbType.Int, 4).Value = ID; Pams.Add("D", SqlDbType.DateTime).Value = DateTime.Now.ToString("yyyy-MM-dd"); try { objConn.Open(); objComm.ExecuteNonQuery(); } catch (Exception ex) { EventLog.WriteEntry("TestsCOM+inBTS", string.Format("Error actualizando {0}.", ex.Message)); } finally { if (objConn.State != ConnectionState.Closed) objConn.Close(); } } }}
Se requiere añadir las referencias para todo el soporte de COM+, para esto es necesario que por medio de Add Reference agreguen las librerías de System.EnterpriseServices y System.Runtime.InteropServices; asi como añadirlas a su código por medio de la directiva using.
Básicamente necesitamos 3 elementos para habilitar nuestro componente como transaccional en COM+:
1.
El atributo Transaction. Este atributo se aplica a nivel clase y tiene algunos valores por default que les recomiendo revisen (ver ayuda). Dentro de las propiedades de este atributo, value que es el miembro por default, representa TransacionOption que indica como se comportará la transacción.
Member name
Description
Disabled
Ignora cualquier transacción del contexto actual.
NotSupported
Crea el componente en un contexto donde no exista una transacción.
Required
Comparte la transacción en case de que exista una, pero si no existe la crea.
RequiresNew
Obliga la creación de una nueva transacción, independientemente si el contexto tiene uno o no.
Supported
Si existe una transacción en el contexto la comparte.
En nuestra clase emplearemos TransactionOption.Required, por lo que el código es:
[Transaction(TransactionOption.Required)]public class TxComponent
o
[Transaction]public class TxComponent
2.
Heredar de la clase ServicedComponent. Nuestra clase transaccional debe heredar de ServicedComponent para poder contener las propiedades y métodos de soporte para COM+. Dentro de las características que podemos resaltar es el método Dispose. Este método es importante debido a la administración de recursos que tiene .NET en especial cuando son recursos del mundo COM. Nuestro código es:
[Transaction(TransactionOption.Required)]public class TxComponent : ServicedComponent
3.
Exponer el componente a COM. Para habilitar el componente en COM empleamos el atributo ComVisible(true). Este atributo se puede añadir a nivel assembly o a nivel clase. La diferencia radica en que si se hace a nivel clase, se generarán los metadatos para las clases marcadas con el atributo para el archivo de tipos (tbl) y si es a nivel assembly se generará información para todo el assembly. Listo las opciones para usar el atributo:
a.
A nivel de assembly. Normalmente este atributo lo pueden localizar en el archivo de propiedades como se muestra aquí. Para acceder a la pantalla de propiedades tiene que dar click derecho en el proyecto, entrar a Properties. En la sección de de Application, el botón de Assembly Information, y marcando la opción de Make assembly COM-visible, como se muestran en esta figura.
Lo pueden modificar por medio del editor o empleando la pantalla de propiedades, ya que la pantalla de propiedades afecta los valores del archivo de propiedades AssemblyInfo.cs.
b.
A nivel clase. Permite de manera selectiva indicar cuales clases estarán disponibles para COM.
[Transaction(...), ComVisible(true)]public class TxComponent : ServicedComponent{...}
Controlando las transacciones: Commit y Rollback
Las transacciones cuentan con 2 procesos que controlan el estado, estas son:
Commit. Esta instrucción le indica a la transacción que todos los cambios deben ser guardados hasta el punto donde se invoca.
Rollback. Esta instrucción le indica a la transacción que todos los cambios hechos hasta el punto donde se invoca deben ser eliminados.
Este proceso debe controlarse dentro del código invocando métodos según la lógica que tengamos implementada, los nombres de los métodos pueden variar. La estructura mas común es:
try{ Componente1.Ejecuta(); Componente2.Ejecuta(); ..... ComponenteN.Ejecuta(); Commit(); //Solo es un ejemplo}catch (Exception ex){ ... Rollback(); //Solo es un ejemplo}
El bloque try-catch controla toda la transacción y los componentes "Component1..N" participa en ella, claro si y solo si son transaccionales. En el caso de que algun componete no sea transaccional no hay ningun problema. Este prodecimiento es manual, ya que nosotros estamos controlando la transacción al momento de invocar los métodos.
Existe otro mecanismo el cual dejamos que el sistema controle la transacción por nosotros. Para esto, los componentes emplean un mecanismo denominado votación para poderle indicar al sistema si debe invocar el Commit o el Rollback al final de la operación. Existen 2 formas de realizar esta votación:
1.
Manualmente. Invocando SetComplete y SetAbort del objeto ContextUtil
try{ objConn.Open(); objComm.ExecuteNonQuery(); ContextUtil.SetComplete();}catch (Exception ex){ EventLog.WriteEntry("TestsCOM+inBTS", string.Format("Error {0}.", ex.Message)); ContextUtil.SetAbort();}finally{ if (objConn.State != ConnectionState.Closed) objConn.Close();}
2.
Automáticamente. Empleando el atributo AutoComplete en el método.
[AutoComplete]public void InsertRows(string DBConnString,int ID)La mecánica que tiene el atributo es que si el método no arroja ninguna excepción, se vota a favor de la transacción de manera automática, de lo contrario se vota en contra. Debido a esta mecánica, dentro del componente estoy incluyendo una variable de tipo excepción que permita cerrar la conexión a la BD y posteriormente arrojar la excepción original, de lo contrario podríamos dejar la conexión abierta. Hay varias formas para poder manejar esto, pero les proponga una:
catch (Exception ex){ EventLog.WriteEntry("TestsCOM+inBTS", string.Format("Error {0}.", ex.Message)); bubble = ex;}finally{ if (objConn.State != ConnectionState.Closed) objConn.Close();}if (bubble != null) throw bubble;
Todos los componentes transaccionales que participen en la operación deben votar a favor, para que el sistema invoque el Commit, de lo contrario se invocará el Rollback.
Finalmente nuestro código quede de la siguiente forma:
using System;using System.Data;using System.Data.SqlClient;using System.Diagnostics;using System.Xml;using System.EnterpriseServices;using System.Runtime.InteropServices;using System.Runtime.Serialization;
[assembly: ApplicationName("BTSMyAppTX")]
namespace EnterpriseComponents.DB{ [Transaction(TransactionOption.Required), ComVisible(true)] public class TxComponent : ServicedComponent { [AutoComplete] public void InsertRows(string DBConnString,int ID) { Exception bubble = null; SqlConnection objConn = new SqlConnection(); SqlCommand objComm = new SqlCommand("insert into monitor values (@ID,@D)", objConn); SqlParameterCollection Pams = objComm.Parameters; objConn.ConnectionString = DBConnString; Pams.Add("ID", SqlDbType.Int, 4).Value = ID; Pams.Add("D", SqlDbType.DateTime).Value = DateTime.Now.ToString("yyyy-MM-dd"); try { objConn.Open(); objComm.ExecuteNonQuery(); } catch (Exception ex) { EventLog.WriteEntry("TestsCOM+inBTS", string.Format("Error actualizando {0}.", ex.Message)); bubble = ex; } finally { if (objConn.State != ConnectionState.Closed) objConn.Close(); } if (bubble != null) throw bubble; } }}
Registro del componente en COM+
El componetne debe ser instalado en COM+, por lo que se deben realizar los siguientes pasos:
Firmar el componente. Obtienen una llave por medio de la herramienta Strong Name Tool y registrarla en la propiedades del assembly. Para aquellos que no les gusta tener que abrir el command prompt para ejecutar el comando, pueden programar la generación de la llave por medio de External Tools del menu te Tools (ver imagen).
Proporcionar una identidad al componente. Por medio de atributos a nivel assembly se debe dar un nombre y/o un GUID
[assembly: ApplicationName("BTSMyAppTX")] [assembly: ApplicationID("4fb2d46f-efc8-4643-bcd0-6e5bfa6a174c")]
Ahora necesitamos registrar el componente. La manera mas rápida es empleando la herramienta .NET Services Installation Tool:
regsvcs C:\Tests\Transaccionts\EntServComponent\bin\Debug\EntServComponent.dll
si desean desinstalar el componente solo tiene que añadir el parámetro /u
regsvcs /u C:\Tests\Transaccionts\EntServComponent\bin\Debug\EntServComponent.dll
Este comando también lo pueden programa en el menu de External Tools.
Una vez que queda instalado el componente en COM+, lo puede revisar en Component Services. Click en Start, luego en Administrative Tools y luego en Component Services (ver imagen)
Errores comunes al ejecutar regsvcs :
Error:The following installation error occurred:1: Invalid ServicedComponent-derived classes were found in the assembly.(Classes must be public, concrete, have a public default constructor, and meet all other ComVisibility requirements)EnterpriseComponents.DB.TxComponent: Unspecified errorPosible Solución:Esto es porque no se a establecido el atributo de ComVisible(true), ya sea a nivel assembly o a nivel clase.
Error:Warning: No ServicedComponent-derived classes were found in the assembly.Posible Solución:Esto se arregla haciendo que la clase que queremos que sea transaccional herede de ServicedComponent
Probando el componente
Ahora en .NET Framework 2.0 tenemos nuevas características para el soporte de transacciones. Por medio de la clase TranactionScope podemos iniciar la transacción y realizar el control manual. Si desean hacer la prueba para .NET Framework 1.1, pueden usar este ejemplo How to: Use the BYOT (Bring Your Own Transaction) Feature of COM+.
Vamos a hacer la prueba por medio de una Winform. Añadiendo un botón con el siguiente código:
private void button2_Click(object sender, EventArgs e){ string DBConnString = "server=.;initial catalog=poolingdata;User Id=sa;pwd=password"; TxComponent T1 = new TxComponent(); TxComponent T2 = new TxComponent(); TxComponent T3 = new TxComponent(); using (TransactionScope tx = new TransactionScope()) { T1.InsertRows(DBConnString,1); T2.InsertRows(DBConnString,2); T3.InsertRows(DBConnString,3); tx.Complete(); }}
Si ejecutan el codigo, realizara la inserción de 3 registros en nuestra base de datos y se completará la transaccion pues no hay ningún error (la tabla debe estar limpia de registros), por lo que pueden ejecutar un query como SELECT * FROM monitor para poder ver los registros.
En Component Services hay un monitor de transacciones que pueden emplear para ver el comportamiento de nuestro código. Si colocamos un breakpoint en el código podremos ver que el monitor incrementa los contadores de las transacciones en la máquina (ver imagen)
Como podrán ver, después de dar click en el botón y terminar el proceso, el contador de transacciones finalizadas exitosamente (Commited) y el total se incrementa en 1.
Si desean establecer el contador de transacciones en ceros, tiene que reiniciar el servicio de MSDTC y una forma de hacerlo es dando click en My Computer y click en Stop MS DTC y nuevamente click derecho y Start MS DTC (ver imagen)
Ahora, sin borrar los registros que acabamos de insertar, modificamos el código de tal forma que podamos generar un error de llave única duplicada al momento de intentar insertar in ID que ya se encuentra en la base de datos. El código queda como sigue:
using (TransactionScope tx = new TransactionScope()){ T1.InsertRows(DBConnString,4); T2.InsertRows(DBConnString,2); T3.InsertRows(DBConnString,3); tx.Complete();}
Colocando un breakpoint en la segunda inserción y ejecutamos la aplicación. Lo que sucederá es que se ejecutará la primera inserción (ID = 4); en este momento pueden ejecutar un query de la forma SELECT * FROM monitor, pero de forma extraña el query analyzer se quedara ejecutando la instrucción sin regresar resultados. Esto es una característica que tienen las transacciones, al menos dentro de las bases de datos: los bloqueos de información cuando hay transacciones.
Estos bloqueos tiene una razón, la cual es asegurar la consistencia de la información. Con un ejemplo podemos verlo mejor. Supongamos que exactamente en el instante en el que se inserto el registro con el ID = 4, otro sistema intenta leer información de esa misma tabla. Si dejamos que lea todos los registros que existen, esto implica el registro con ID = 4 entonces esa otra aplicación tendrá como resultado 4 registros. Pero lo que sigue es que al momento que ejecutemos la siguiente instrucción de inserción con el ID = 2, generaremos un error pues el ID = 2 ya se encuentra en la base de datos, por lo que la transacción no se completará y el registro de la tabla con el ID = 4 desaparecerá, causando que la otra aplicación que ya había leído ese registro con ID = 4 tengo lo que se llama una lectura fantasma.
Regresando al breakpoint de la segunda inserción en el que estábamos, para poder hacer una consulta a la tabla sin que en query analyzer se bloquee, podemos usar la sentencia SELECT * FROM monitor with (READPAST). Esta opción establece que deseamos hacer la lectura de toda la información que no este bloqueada por una transacción como lo es el registro con el ID = 4 y así podremos ver los 3 registros que ya habíamos insertado.
NOTA. Necesitan tener cuidado mientras hacen sus pruebas, porque si dejan pasar mucho tiempo en el breakpoint, las transacciónes tiene un timeout y podría ser que se venza por lo que ya no haya transacción activa y se genere otro tipo de error. Este timeout es configurable.
Si de todas formas deseamos hacer una lectura fantasma, esto es mientras estamos en el breakpoint de la segunda inserción deseamos ver los registros de la tabla (incluyendo el registro con ID = 4) podemos emplear la sentencia SELECT * FROM monitor with (NOLOCK), de esta forma podemos ver los 4 registros que hasta ese punto hemos insertado
Si continuamos con la ejecución de la WinForm, se generará una excepción en la segunda inserción, por lo que la transaccion terminará. Regresamos a revisar los registros en la tabla y verán que solo tenemos 3 registros, pues aun que insertamos el registro con ID = 4, al momento de fallar la transacción ese registro deja de existir. En este momento el contador de transacciones fallidas (Aborted) y el total se incrementa en 1 como se muestra en la figura (ver imagen)

Parte III BTS, .NET y COM+: Realizando transacciones en una orquestación con el componente construido
Podemos decir que esta es la parte mas sencilla de hacer, pero la mas interesante por la parte de las conclusiones y una importante característica que lamentablemente en estos momentos no pude hacer funcionar.
Pueden bajar el código aquí. Pueden ver la estructura aquí.
Construyendo la orquestación
La orquestación recibe un documento por medio del FILE Adapter. Dicho documento contiene todas las claves que serán insertadas a la tabla en la base de datos. La orquestación se encarga de iterar por el documento para obtener todos los ID e invocar el método de inserción. De esta manera podemos controlar la transacción al momento de mandar un documento con ID que ya existan en la tabla y generar la excepción que necesitamos.
La orquestación luce así:
La estructura del documento XML es la siguiente:
....
Lo mas importante es que la orquestación esta configurada como Long Running Transaction (LRT) (ver imagen) y que en el Scope_1 donde se encuentra nuestro proceso de inserción esta configurado como Atomic Transaction (ver imagen).
Esto trae consigo el entender algunos de los términos mas importantes dentro de BTS, por lo que no esta por demás explicarlos. Vamos a suponer un ambiente donde se generan 100,000 documentos de manera diaria y que tiene que ser procesados por una orquestación. En dicha orquestación tenemos un paso que requiere por medio de un adaptador ir a un sistema externo que nos puede responder en 1 segundo o en 1 semana. Si analizamos el peor de los casos, en 1 semana podríamos llegar a tener hasta 700,000 orquestaciones (en memoria) esperando respuesta del sistema externo y las cuales están consumiendo recursos de nuestro servidor.
Por esto existe un proceso que se llama Dehydrate, el cual permite bajo ciertas reglas el "mover" una orquestación de la memoria a el MessageBox y así poder optimizar los recursos. Una vez que la orquestación ha sido guardada (con todo y su estado) en el MessageBox, existen ciertas condiciones que harán que la orquestación regrese a la memoria a esto se le llama Rehydrate y así poder continuar con su procesamiento en el punto exacto donde se había quedado, ver mas detalle sobre el proceso de Dehydrate/Rehydrate. En este proceso existen 2 importantes elementos:
1.
Serialización. Este es un proceso que tiene como objetivo el poder transformar información de un formato de una fuente a otro formato para un destino, de tal forma que si tenemos un objeto en memoria, al momento de serializarlo podemos guardar la información de objeto en otro medio como lo puede ser una base de datos. El ejemplo por excelencia de la serialización son los Web Services. En este aspecto para poder guardar una orquestación, se realiza el proceso de serializar toda la información de la orquestación que esta en memoria para poder guardarla en el MessageBox (Dehydrate), para posteriormente poderla recrear nuevamente en memoria cuando se necesite (Rehydrate). Como una orquestación puede contener objetos previnientes de clases que nosotros definimos, debemos habilitar la serialización para esas clases y para eso existen 2 tipos de serialización:
a.
Automática. Empleando el atributo Serializable a nivel de la clase. Como la información que deseamos guardar de la clase es su estado y su estado esta contenido en las variables miembro, al momento de aplicar este atributo automáticamente todos las variables miembro publicas y privadas se serializan por default a menos que se indique lo contrario empleando serialización manual o que se use el atributo NonSerialized a nivel variable miembro
b.
Manual. Implementando la interfaz ISerializable en la clase. Básicamente lo que se tiene que haces es añadir un método llamado GetObjectData, con el cual nos damos a la tarea de guardar un diccionario de datos (llave-valor) y algunas otras operaciones. ej.
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { // Serialize the desired values for this class info.AddValue("title", title); ....
Para mayor información acerca del proceso de serialización, pueden emplear las siguientes ligas
Run-time Serialization, Part 1
Run-time Serialization, Part 2
Run-time Serialization, Part 3
Custom Serialization
2.
Persistencia. Se le llama persistencia al proceso de actualización del estado de un orquestación en el MessageBox. En este aspecto las persistencia es uno de los proceso mas costosos para escenarios de baja latencia, es decir para escenarios que requieren una respuesta en muy poco tiempo hablando en el orden de milisegundos, pues las persistencia consume mucho tiempo. Los puntos de persistencia están establecidos por la lógica que es implementada en la orquestación, pero que se pueden llegar a controlar hasta cierto punto. Si desean analizar cuantos puntos de persistencia tiene una orquestación, pueden emplear los Performance Counters. Dando click en Start, luego en Administrative Tools y luego en Performance. Dando click en el botón de Add, seleccionar XLANG/s Orchestrations del drop-down list de Performance Object y dar doble click en Persistence points (ver imagen).
Si por algún motivo no aparecen los Performance Counters, con el siguiente comando pueden registrarlos (tienen que reiniciar después de ejecutar el comando)
lodctr C:\Program Files\Microsoft BizTalk Server 2006\BTSPerfMonExt.ini
Ahora que establecimos elementos importantes dentro de BTS, explicaremos como es que el componente transaccional esta involucrado en todo eso.
Cuando se emplea un shape de Scope y se configura para emplear Atomic Transactions lo que sucede es que no hay puntos de persistencia dentro de ese scope y en ese aspecto nuestra clase no requiere ser serializada. Un pequeño quiz: cual es la orquestación que se ejecuta mas rápido A, B o C. Aunque por lo explicado anteriormente es sencillo ver cual correo mas rápido, el segundo lugar puede ser engañoso.
Si queremos emplear el componente pero no de manera transaccional dentro de nuestra orquestación y no queremos cambiar el código y esto es factible. Recordando la opción TransactionOption del atributo Transaction con el valor igual a Supported nos ofrece la flexibilidad de que si el componente encuentra una transacción participará en ella, de lo contrario no será transaccional. El shape de Scope permite establecer el valor de Transaction Type = None y de esta manera ya no existiría una transacción. El compilador marcará un error si se encuentra con una clase que no es serializable y no se encuentra en un scope con Atomic Transaction por las razones que acabamos de explicar. Solo faltaría incluir el atributo de Serializable en nuestra clase y modificar TransactionOption igual a Supported, pero el compilador de todas maneras les marcará error. Lo primero es darse cuenta de que nuestra tiene una herencia (ServicedComponent) y en esta situación la clase padre también necesita ser serializable. Si revisamos la clase ServicedComponent con click derecho sobre la clase con la opción de Go To Definition verán que si esta marcada como serializable, entonces que sucede??? Esto tendremos que dejarlo pendiente por el momento.
Solo nos falta un paso para poder probar nuestra orquestación con el componente transaccional. En la Parte II registramos el componente en COM+ y lo probamos con una aplicación de WinForms. No tuvimos que registrar en el GAC el componente transaccional debido a que el WinForm tiene la referencia y hace una copia local en el directorio de ejecutable, así pudo ubicar el componente, pero en este caso BTS necesita que registremos el componente en el GAC porque esa es la única manera en que los puede localizar. También pueden programar el comando del GAC en External Tools. Cuando corran el comando asegúrense de que este seleccionado el proyecto del componente que quieren registrar (ver imagen).
NOTA. Si se dieron cuenta el método que estamos empleando para la inserción pudo haber sido estático, pero habría algunas características como el pooling de objetos que ofrece COM+ que no podrían aprovecharse.
Probando el componente en la orquestación
Deben realizar el proceso de deployment de la orquestación, revisando el nombre del servidor y de la aplicación dentro de las propiedades del proyecto de BTS para no tener ningún problema para poder empezar la prueba.
Estoy incluyendo 2 botones, los cuales tiene como objetivo generar los 2 documentos con los que probaremos la orquestación. Para esto tiene que cambiar la path de los directorios de entrada. El primer documento no tendrá ningún error (la tabla debe estar vacía) porque contiene los IDs 1,2 y 3 y el segundo documento que contiene los IDs 4,5 y 1 y en el cual se generará un error por que el ID = 1 esta duplicado y por lo cual los IDs 4 y 5 no serán insertados.
Pueden ver el monitor de transacciones para ver como se va a comportando la transacción, tanto cuando la transacción es exitosa como cuando falla. Mandando varios documentos con ID 4,5 y 1 verán que el contador de transacciones abortadas se incrementa.
Notas sobre la orquestación
1.
Estoy incluyendo un puerto de salida, donde lo único que hace es escribir exactamente el mismo documento del puerto de recepción.
2.
Pueden observar la manera en que se obtiene el ID cuando se itera por el documento. De la expresión de xpath que se emplea
xpathstring = System.String.Format("string(/*[local-name()='IDs' and namespace-uri()='http://CallingEntServComponents.Activar']/*[local-name()='ID' and namespace-uri()=''][{0}]/@*[local-name()='value' and namespace-uri()=''])",i);
Lo mas importante es la función de string debido a que así obtenemos el valor del atributo value. Si omiten esa función, la expresión de xpath regresará el atributo como tal y no su valor. Para ver mas detalles acerca de xpath dar click aquí.
Para obtener la expresión de xpath para el atributo pueden emplear el esquema, en la ventana de propiedades buscar Instance Xpath (ver imagen).
3.
Cuando se genere un error, el mensaje se suspenderá y habrá una entrada en el Event Log. Deben considerar que no estamos incluyendo un manejo de excepciones dentro de la orquestación y debido a que en nuestro método de inserción estamos arrojando la excepción, por eso se suspende el mensaje.
4.
Coloque una entrada en el Evento Log para ver el ID que se esta insertando durante la iteración en shape que se llama Inserta Registro.
Adaptadores vs Componentes
Cuando platicamos del porque la necesidad de las transacciones, pudo haber traído la duda: y los adaptadores soportan transacciones? Los adaptadores están construidos en .NET y en se sentido queda del lado del desarrollador el incluir dicha funcionalidad. Si el adaptador si soporta transacciones entonces lo que sucederá es que si tenemos por ejemplo un adaptador que se comunica con una base de datos como Oracle y el puerto participa en una transacción (LRT o Atómica) y realiza operaciones como insertar, borrar o modificar registros entonces tendrá un comportamiento como el de nuestro componente dentro de un scope transaccional.
Existen cierto escenarios donde existirá la opción de emplear un adaptador o un componente cuando estemos hablando de un puerto de salida. No podemos establecer una regla absoluta en cuando usar un adaptador o un componente, pero hay varias cosas que pueden ayudar a responde la pregunta, por mencionar algunas:
Necesitas monitoreo?
Necesitas escalabilidad
Necesitas baja latencia
Hay adaptador para el sistema externo?

No hay comentarios: