Transacciones Automáticas

Cómo usar transacciones a través de los Enterprise Services (COM+)

 

Fecha: 06/Jun/2005 (03/06/2005)
Autor: Ing. Gustavo Bonansea, E-mail, Blog

 


Introducción

Este artículo explica cómo transformar un componente cualquiera en un componente transaccional, de forma muy simple, basándose en COM+ y los Enterprise Services de .Net. La técnica que abordaremos aquí es no invasiva; es decir, que no es necesario "ensuciar" la lógica de negocios con instrucciones que hacen al manejo de las transacciones.

Esto de separar los aspectos secundarios (transacciones, manejo de errores, logueo, seguridad, etc.) que no son parte del "core business" del objeto es área de estudio de la Programación Orientada a Aspectos, AOP por sus siglas en inglés. Muchas de las implementaciones, en .Net, de este paradigma hacen uso de los mismos mecanismos que, como veremos más adelante, son utilizados por los Enterprise Services.

Transacciones

Básicamente podemos decir que una transacción es una serie de acciones que deben realizarse en conjunto, a "todo o nada". Esto significa que las transacciones son atómicas, todos los pasos deben llevarse a cabo correctamente o volver todos los cambios realizados hacia atrás. De esta forma nos aseguraremos que los datos no queden en estado inconsistente. El ejemplo clásico es el de una transferencia bancaria: Si queremos transferir $100 de la Cuenta A a la Cuenta B tendremos primero que quitar $100 de A y agregar $100 a B, si se produce algún problema en el medio del proceso habremos sacado $100 de A que nunca se agregarán a B y habrán desaparecido $100.

Cómo implementar transacciones

Como podemos adivinar las transacciones son una parte vital de los sistemas informáticos y existen varias formas de implementarlas. Las transacciones pueden manejarse directamente en el código SQL de un Procedimiento Almacenado (utilizando T-SQL por ej. en el caso de SQL Server), o podría realizarse en los componentes de negocio utilizando los métodos Commit y Rollback del objeto SqlTransaction en .Net. El primer método tiene la limitación de que no pueden manipularse los datos dentro de la transacción desde el código del componente y por lo tanto todo debe realizarse con T-SQL. En el ejemplo 1 podemos observar un procedimiento almacenado que se llama transferir , que llama otros dos llamados credito y debito para realizar su cometido dentro de una transacción denominada transferir .

CREATE PROCEDURE transferir
AS
BEGIN TRAN transferencia
EXEC credito 100, 'CuentaA'
IF (verificar si se pudo realizar el crédito)
	BEGIN
		EXEC debito 100, 'CuentaB'
		IF (verificar si se pudo realizar el débito)
			COMMIT TRAN transferencia
		ELSE
			ROLLBACK TRAN transferencia
	END
ELSE
	ROLLBACK TRAN transferencia
GO
    ejemplo 1 - Transacciones con T-SQL

Supongamos que necesitamos llamar varios procedimientos almacenados, ejecutar algunas consultas y manipular los datos desde nuestro componente en .Net y todo forma parte de una sola transacción. Para solucionar esta problemática es preciso utilizar el segundo método listado anteriormente, el objeto SqlTransaction (en el caso de utilizar otro tipo de base de datos deberíamos usar el objeto que nos brinde el proveedor de datos específico para cada caso).

 

Public Sub Transferir(ByVal strCadenaConexion As String)
      ' Apertura de la conexión
      Dim Conexion As New SqlConnection(strCadenaConexion)
      Conexion.Open()

      Dim Commando As New SqlCommand
      Dim Transaccion As SqlTransaction

      ' Inicio de la transaccion
      Transaccion = Conexion.BeginTransaction()

      Commando.Connection = Conexion
      Commando.Transaction = Transaccion

      Try
         ' Comando para descontar $100 de la Cuenta A
         Commando.CommandText = "Update into Cuentas Set Saldo = Saldo - 100 Where Cuenta = 'CuentaA'"
         Commando.ExecuteNonQuery()

         ' Comando para aumentar $100 de la Cuenta B
         Commando.CommandText = "Update into Cuentas Set Saldo = Saldo + 100 Where Cuenta = 'CuentaB'"
         Commando.ExecuteNonQuery()

         ' Finalización exitosa de la transacción
         Transaccion.Commit()
         Console.WriteLine("El Dinero fue transferido con éxito")
      Catch e As Exception
         ' Cancela toda la transacción
         Transaccion.Rollback()
         Console.WriteLine(("Se produjo un error al procesar la transferencia" + vbNewLine + "Detalle:" + vbNewLine + e.ToString()))
      Finally
         Conexion.Close()
      End Try
   End Sub
    ejemplo 2 - Transacciones en VB.Net

Pero supongamos ahora que la transferencia que intentamos realizar involucra cuentas que se encuentran en dos sucursales o bancos diferentes y por lo tanto afecta a dos servidores de base de datos distintos. En este caso la mejor opción es dejar en manos de los Enterprise Services la tarea de manejar las transacciones, porque los mismos hacen uso del coordinador de transacciones distribuidas (MS-DTC) que se encarga de todo el trabajo interno de comunicación y coordinación de los distintos servidores. Con este método también pueden realizarse transacciones que involucren servidores de base de datos heterogéneos, es decir que afecten a un servidor SQL Server y un servidor Oracle Database, por ejemplo. Esto puede realizarse debido a que el servidor Oracle incluye un servicio para manejar transacciones distribuidas a través de MTS (Oracle Services for MTS), por lo tanto podría ser incluido en la transacción cualquier servidor de base de datos con soporte para MTS, (DB2 e Informix entre otros).

COM+

La tecnología que nos suministra Microsoft para resolver el dilema antes planteado es COM+. Éste nació para facilitar la construcción de aplicaciones transaccionales distribuidas, basadas en componentes. No sirve solo para el manejo de transacciones (que pueden ser distribuidas a través del Microsoft Transaction Server - MTS), sino que simplifica además algunas tareas comunes como pooling de objetos, publicación y suscripción de eventos, colas de componentes, seguridad basada en roles, etc. Es decir que nos brinda una serie de servicios de infraestructura para que nuestros componentes sean parte de una gran solución empresarial, escalable, segura, flexible y con alta disponibilidad.

 

Enterprise Services y COM+

Los Enterprise Services son la cara visible de COM+ en código manejado, es decir que en .Net se tiene acceso a estos todos los servicios proporcionados por COM+ gracias a ellos. Una de las clases más importantes dentro de los Enterprise Services es la ServicedComponent . Para utilizar los servicios de COM+ nuestro objeto debe derivar de esta clase (esto puede ser una limitación en entornos de herencia simple como .Net). Esta clase utiliza el concepto de contextos y se basa en la tecnología de Remoting, que le permite interceptar las llamadas a los métodos o la activación de los objetos y de esta forma llamar automáticamente a los servicios propios de COM+. El conocimiento específico de contextos, intercepciones y remoting no es necesario porque es transparente para el programador. Esta es una de las ventajas mayores de los Enterprise Services: La Simplicidad.

NOTA: Para utilizar los Enterprise Services debe agregarse una referencia a los mismos desde el menú principal: Projects/Add Reference, luego elegir “System.EnterpriseServices”

Catálogo

Para que la aplicación pueda utilizar los servicios de COM+ debe estar registrada en el Catálogo. La adición y configuración desde un ServicedComponent puede ser realizada de forma automática por el CLR (en aplicaciones no manejadas ésto debe ser hecho en forma manual). Cuando el runtime detecta que un componente deriva de ServicedComponent lo busca en el catálogo y si no está lo agrega al mismo (esto normalmente se denomina Lazy Registration). Para configurar la aplicación se basa en los metadatos del assembly, es decir en los atributos del mismo y de las clases. El mecanismo de registración está disponible en .Net a través de la clase EnterpriseServices.RegistrationHelper para que su código pueda manipular el catálogo, aunque esta clase necesita permisos para ejecutar código no manejado, así como también derechos de administrador.

Podemos ver un ejemplo de estos atributos en el ejemplo 3 . En el archivo AssemblyInfo.vb se coloca el atributo ApplicationName para indicar el nombre que recibirá la aplicación en el catálogo de COM+ y el atributo AssemblyKeyFile para firmar con un strong name al assembly (debe tenerlo para poder ingresar al catálogo).

<Assembly: ApplicationName("DemoTrans")>
<Assembly: AssemblyKeyFile("DemoTrans.snk")>
    ejemplo 3: Atributos del assembly
<Transaction(TransactionOption.Required)> _ 
Public Class CuentaCorriente
	Inherits ServicedComponent 'Herencia necesaria para que el objeto entre en el catalogo de COM+
     ejemplo 4: Requerimientos a nivel de clase para soportar transacciones con COM+

Como podemos observar en el ejemplo 4, en la declaración de la clase en la que deseamos utilizar el servicio de transacciones de COM+ son necesarias dos cosas, primero debemos ponerle el atributo Transaction para que soporte transacciones. Como parámetro del mismo se puede colocar el tipo de transacción que el componente va ha utilizar. En la siguiente tabla se detallan las opciones posibles:

Disabled

Hace caso omiso de cualquier transacción en el contexto actual.

NotSupported

Crea el componente en un contexto sin ninguna transacción controladora.

Required

Comparte una transacción, si existe alguna, y crea una nueva, si es necesario.

RequiresNew

Crea el componente con una nueva transacción, independientemente del estado del contexto actual.

Supported

Comparte una transacción, si existe alguna.

Como segundo requerimiento es necesario también que la clase derive de ServicedComponent para que ingrese al catálogo de COM+ y pueda gozar de toda la funcionalidad brindada por los Enterprise Services.

Para manejar la transacción podemos optar por dos formas. La primera es utilizando los métodos SetComplete (realiza el commit de la transacción) y SetAbort (realiza el rollback) de la clase ContextUtil como se muestra en el ejemplo 5

<Transaction(TransactionOption.Required)> _ 
Public Class CuentaCorriente
	Inherits ServicedComponent 'Herencia necesaria para que el objeto entre en el catalogo de COM+

	 'Transfiere dinero de una cuenta a otra 
	Public Sub Transferir(CuentaOrigen As String, CuentaDestino As String, Monto As Integer)

		Try
			'Aquí debería ir el código propio del proceso de transferencia .......

			 'Si no se produjo ningún inconveniente en el proceso completo la transacción
			ContextUtil.SetComplete() 'Indica que la transacción se completó con éxito
		Catch (ex As Exception)
			 'Si se produjo una excepción en el proceso cancelo la transacción
			ContextUtil.SetAbort() 'Cancela la transacción y realiza un rollback
			Throw New Exception("No se pudo realizar la transferencia", ex)
		End Try
	End Sub 
    ejemplo 5: Uso de ContextUtil para gestionar la transacción

Otra forma más simple, elegante y no invasiva (y la que vamos a utilizar en el resto del artículo y en el código del ejemplo adjunto) es colocarle el atributo AutoComplete al método Transferir y dejar que los Enterprise Services se encarguen de manejar automáticamente la transacción. Cuando un método tiene ese atributo se iniciará automáticamente una transacción en el momento en que comience la ejecución del mismo y se completará exitosamente (commit) si termina normalmente o abortará automáticamente la misma en caso de que termine la ejecución del método por una excepción. De esta forma podemos hacer transaccional un método sin modificar la lógica interna.

Por lo tanto podemos transformar un objeto cualquiera en un componente que realice transacciones (incluso aquellas distribuidas entre múltiples bases de datos) con solo tres simples pasos (en la simplicidad está la belleza):

  1. Derivar el objeto de ServicedComponent
  2. Agregarle el atributo Transaction para indicar el tipo de transacción que se va a utilizar
  3. Agregarle al método el atributo AutoComplete para habilitar la administración automática

Algunas consideraciones sobre COM+

Existen dos formas en las que se puede agregar un componente a COM+: como Librería o como Servidor. En el primer caso el componente corre en el mismo proceso que lo instancia, en el otro caso se crea un proceso dedicado (Dllhost.exe) para ejecutarlo. La elección de esto va a impactar en la performance de la aplicación debido que en el segundo caso cualquier comunicación con el objeto debe traspasar los límites del proceso y como es sabido la comunicación interproceso es una tarea costosa, pero hace a la aplicación más segura y escalable debido a que el componente está separado y no comparte los recursos de ningún otro proceso y puede seguir ejecutándose aunque alguna de las aplicaciones que lo instanció termine. Supongamos que una aplicación web hace uso del componente, podría reiniciarse el servicio que alberga el componente sin afectar a las demás aplicaciones de ASP.Net que corren en el proceso aspnet_wp.exe.

Ejemplo

Ahora veamos el ejemplo se incluye con este artículo. Es un ejemplo muy simple para demostrar el uso práctico de todo lo que fue explicado anteriormente.

Pasos para hacer funcionar el ejemplo:

 

Cuando ejecutemos por primera vez el ejemplo la inicialización puede tardar un poco debido a que el CLR está registrando el componente de forma automática en el catálogo de COM+

El componente se registra con los valores que fueron indicados a través de atributos, por ejemplo, como se puede ver en la solapa transacciones se indica “Necesaria” que es lo que indicamos con el atributo <Transaction(TransactionOption.Required)> sobre la clase BCCuentaCorriente.

Cuando se llama a la primera instrucción contra la base de datos se inicia la transacción:

Cuando termina la ejecución del método el CLR realiza el commit de la transacción de forma automática:

En caso de que se produzca un error, supongamos que ingresamos un número de cuenta erróneo, cuando termine la ejecución del método por una excepción los Enterprise Services realizarán el rollback automático.

 


Espacios de nombres usados en el código de este artículo:

System.Data
System.EnterpriseServices


Fichero con el código de ejemplo: gbonansea_TransaccionesEnterpriseServices.zip - 138 KB


ir al índice