Transacciones Distribuidas con ODP.NET y Oracle Database 12c

Por Francisco Riccio
Publicado en Abril 2014

Introducción
Las soluciones empresariales muchas veces tienen el desafío de realizar operaciones sobre múltiples bases de datos, todas las operaciones ejecutadas deben ser trabajadas y vistas como sola transacción. Si una operación sobre una base de datos falla, todo el conjunto de operaciones deben ser canceladas. Asimismo se debe dar conformidad de la transacción si no hay ningún error.
El desafió se presenta más aún cuando cada de base de datos pertenecen a diferentes proveedores, tales como: Oracle, IBM, Microsoft, etc.
Antes de entrar en detalle sobre la implementación, primero se explicará una breve teoría sobre el procesamiento de transacciones distribuidas (DTP).
DTP se implementa mediante 3 componentes:



Figura 1

  • Application Program (AP), es el responsable de definir el inicio de una transacción distribuida y responsable de solicitar la aceptación o cancelación de todas las operaciones realizadas. Este componente es lo que programaremos más adelante mediante una capa de acceso a datos.
  • Resource Manager (RM), es una base de datos o cualquier fuente de datos que es utilizada por un Application Program. En nuestra implementación será una base de datos en Oracle RAC 12c y SQL Server 2012.
  • Transaction Manager (TM), es el servicio responsable de asignar un único ID a una transacción distribuida, monitorearla y solicitar la confirmación o cancelación de múltiples operaciones que están asociada a la transacción distribuida. Para este componente utilizaremos Microsoft Distributed Transaction Coordinator (MSDTC).

Las transacciones distribuidas a implementar estarán basadas en el estándar eXtended Architecture (XA), el cual define el protocolo two-phase commit y la API usada para la comunicación entre el Transaction Manager y el Resource Manager.
El protocolo two-phase commit permite que una transacción se ejecute en 2 fases. En la primera fase, el Transaction Manager pregunta a todos los Resource Manager enlistados en la transacción distribuida si tienen todo listo para realizar una confirmación de las operaciones ejecutadas, si ninguna de estas reporta algún problema se procede a la segunda fase que es básicamente en confirmar las operaciones. En caso de algún incidente la transacción entra en estado de duda y es resuelto por el Resource Manager.
Una transacción queda en estado de duda cuando ocurre los siguientes escenarios:

  • Falla en la comunicación de red entre las base de datos enlistadas.
  • Alguno de los servidores que participan en la transacción distribuida se cae.
  • Algún bug en el software del manejador de base de datos.

El objetivo de nuestra implementación es simular una transacción bancaria donde vamos a tener una cuenta de ahorros que realizará una transferencia de dinero a otra cuenta. La información de la primera cuenta de ahorros  se almacena en una base de datos Oracle RAC 12c y la segunda en una base de datos SQL Server 2012. Si alguna de las bases de datos presenta algún problema, la transferencia de dinero debe ser desechada, manteniendo cada cuenta la misma cantidad de dinero que originalmente tenía antes de la transacción.
La implementación de la solución se realizará mediante diferentes capas lógicas, permitiendo que cada componente pueda ser distribuido en diferentes equipos.
Asimismo se entrará en detalle sobre cada componente Oracle que se requiere tener instalado para el correcto despliegue de la solución como también las consideraciones que debemos tener en un ambiente Oracle RAC cuando queremos implementar transacciones distribuidas.
Antes de iniciar con explicación de la arquitectura, recomiendo revisar los siguientes URL:

http://www.oracle.com/technetwork/es/articles/dotnet/implementar-aplicaciones-net-1924201-esa.html
http://www.oracle.com/technetwork/database/options/clustering/overview/index.html
http://docs.oracle.com/cd/B28359_01/win.111/b28377/install.htm#i1007065
http://msdn.microsoft.com/es-es/library/ms731082(v=vs.110).aspx

La arquitectura que se diseñó para cumplir con el requerimiento funcional fue el siguiente:

 Figura 2
 

Servidor & Componente

Explicación breve

Oracle RAC 12c

Esta base de datos almacena la información de la cuenta de ahorros que iniciará la transferencia de dinero hacia otra cuenta. La base de datos se encuentra en una configuración Oracle Standard RAC 12c y sobre plataforma Oracle Linux 5.10 x64 bits.
Contará con una base de datos llamada PRD el cual presenta un servicio llamado PRD_XA.

SQL Server 2012

Esta base de datos almacena la información de la cuenta de ahorros que recibirá la transferencia de dinero. Contará con una base de datos llamada PRD que se encuentra en una instancia no nombrada.

DNS

Resolverá las IPs de los hostnames solicitados. Asimismo por buena práctica, será responsable de devolver cualquiera de las 3 IP’s asociadas al IP SCAN de nuestra configuración de Oracle RAC 12c.

MSDTC

Este servidor tiene instalado el sistema operativo Windows Server 2008R2 acompañado de una configuración básica para habilitar el servicio de MSDTC con soporte a transacciones XA.
También se ha instalado la versión Oracle Client 12c con la finalidad instalar el servicio OracleMTSRecoveryService que ayudará al MSDTC a resolver transacciones dudosas donde las  base de datos Oracle esten participando. El cliente Oracle también es importante porque nos provee ODP.NET, el cual permitirá que el servidor MSDTC pueda comunicarse con nuestra base de datos Oracle RAC 12c.
Adicionalmente este servidor presenta un servicio Windows que expone la lógica de negocio para realizar la transferencia bancaria y el manejo de la transacción distribuida todo a través de un servicio Windows Communication Foundation (WCF).

Cliente

La aplicación será una aplicación de escritorio que solo se comunicará con la capa de negocio ubicada en el servidor MSDTC para ejecutar las operaciones que requiera. Esta aplicación correrá sobre clientes Windows, en nuestro caso sobre Windows 7.


Implementación

Oracle RAC 12c
Contando con una base de datos en Oracle RAC, es recomendable crear un servicio de base de datos de tipo singleton si estamos manejando una configuración de servicios manejados por política; si estamos trabajando servicios administrados por el administrador debe crease el servicio con una única instancia preferida y las demás de tipo disponible.  Para ambos casos, debemos habilitar que el servicio será usado para DTP.
Ejemplo: En nuestro caso crearemos un servicio manejado por el administrador llamado PRD_XA el cual tendrá como instancia preferida a la instancia PRD1 y como disponible a PRD2.
Asimismo se ha habilitado el soporte DTP al servicio (Esto mediante la opción -x true).


srvctl add service -d PRD -s PRD_XA -preferred PRD1 -available PRD2 -x true

Procedemos a su validación:


Utilizando un servicio con la opción DTP, permite que todas las conexiones de base de datos enlistadas en una transacción distribuida sean manejadas por una única instancia y cualquier evento de reubicación del servicio a otro nodo será completamente soportado para DTP, de esta manera Oracle asegura que se ha hecho la recuperación efectiva de las operaciones que estuvieron pendientes, esto no ocurre cuando las conexiones hacia la base de datos que están enlistadas en la misma transacción distribuida se encuentran conectadas en diferentes instancias del Oracle RAC.
Sino cumplimos la recomendación previa, nuestra aplicación conseguirá posiblemente los siguientes errores en caso de ocurrir un failover del servicio: ORA-24756 (transaction does not exist) / ORA-01591 (lock held by in-doubt distributed transaction).
Es importante mencionar que en una configuración en Oracle RAC contamos con 1 background process de base de datos por instancia llamado GTX, el cual está configurado por default a un 1 proceso y este número es regulado automáticamente por Oracle Database dependiendo de la carga que se esté dando. Asimismo podemos manualmente configurar el número de procesos del background process a través del parámetro GLOBAL_TXN_PROCESSES.
Nota: Toda transacción distribuida que se ejecuta en una base de datos Oracle demandará un espacio adicional en el Large Pool de la instancia.

Servicio MSTDC

  • Habilitación del servicio MSDTC para el soporte del estándar XA y Conexiones Remotas.


Se describe algunas de las opciones:
Acceso a DTC desde la red: permite que el DTC pueda ser accedido desde la red.
Permitir entrantes: permite que una transacción distribuida se origine de manera remota y corra en el servidor. (En nuestro caso podemos omitir este check ya que la transacción distribuida se originará en el mismo servidor desde un servicio WCF). Mismo análogo es para la opción Permitir salientes.
Habilitar transacciones XA: permite habilitar el estándar XA.

  • Instalación del servicio OracleMTSRecoveryService.

El servicio OracleMTSRecoveryService (OraMTS) conversa directamente con el MSDTC con la finalidad de ayudarlo en mantener la consistencia de la información de las bases de datos Oracle enlistadas en la transacción distribuida cuando entran en estado dudoso.
Este servicio viene como parte de los componentes del Oracle Client.



Es importante que se seleccione la opción Oracle Services For Microsoft Transaction Server 12.10.1.0.

A partir de la versión Oracle Database/Client 12c cada instalación (ORACLE_HOME) mantiene su propio servicio y debemos crearlo de manera manual con el siguiente comando:


oramtsctl –new

 
*.- Debemos asegurarnos que nuestras variables de ambiente de la  sesión de Windows esté apuntando al ORACLE_HOME correcto.
Posterior a la ejecución del comando oramtsctl, veremos nuestro servicio creado.



Terminada la creación del servicio, debemos ejecutar el script oramtsadmin.sql en el servidor de base de datos. El script se encuentra ubicado en el Oracle Client instalado en la siguiente ubicación: %ORACLE_HOME%/oramts/admin. En dicho directorio veremos los siguientes archivos:



El script oramtsadmin.sql llamará internamente a los otros 2 scripts listados.
Este script creará un job a nivel de base de datos que correrá cada minuto y revisará si hay transacciones no resueltas en la base de datos que estén relacionadas con el MSDTC; si existe alguna, este ayudará al   servicio OracleMTSRecoveryService en la resolución del problema.
Terminado la ejecución del script, debemos ejecutar el siguiente script con el usuario SYS.


BEGIN
DBMS_NETWORK_ACL_ADMIN.CREATE_ACL('OraMTSadmin.xml','Allow usage to the UTL network packages','<usuario OraMTSadmin>', TRUE, 'connect');
DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE('OraMTSadmin.xml','<usuario OraMTSadmin>', TRUE, 'resolve');
DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL('OraMTSadmin.xml','*');
END;
/


Procedemos a su validación:



El valor esperado es: 400 Bad Request, un valor diferente indicaría que no se ha realizado los pasos correctamente.
Para obtener el puerto de atención del servicio OracleMTSRecoveryService, debemos abrir el regedit del Windows Server y lo buscamos en la siguiente ruta:


Nota
: Es importante que el servicio OracleMTSRecoveryService, el código Microsoft .NET que ejecuta la transacción distribuida y el servicio MSDTC se ejecuten en el mismo servidor.

 

Codificación de la Transacción Distribuida
El proyecto desarrollado en Visual Studio 2010 tiene la siguiente estructura:



Donde:


Capa

Rol

AppTXDistribuida

Mantiene la capa de presentación que correrá sobre las estaciones clientes y que utilizará la lógica de negocio que será expuesta en el servidor MSDTC.

DAO

Contiene el acceso a la base de datos.

Entidad

Mantiene las entidades de negocio, en nuestro caso la entidad ETransferencia.

LogicaNegocio

Lleva las clases que permiten responder a los requerimientos funcionales de la aplicación.

ServWin_Banco

Expone las funcionalidades de negocio mediante WCF. Esta capa puntualmente expone el servicio WCF mediante tcp corriendo en un servicio Windows.


Se presenta la arquitectura de software:

Figura 3

Capa DAO - TransferenciaDAO:
En ambas bases de datos se tiene una tabla llamada CTA con 2 columnas (cod y saldo) de tipo entero.
Se adjunta la cadena de conexión de ambas base de datos que utilizará la aplicación. Esta información se registra en el archivo app.config.


<appSettings>
<add key="CONEXION_ORACLE" value="Data Source=(DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = srvscan-121.riccio.com)(PORT = 1521)))(FAILOVER=YES)(LOAD_BALANCE=YES)(CONNECT_DATA=(SERVICE_NAME = PRD_XA)(SERVER = DEDICATED)));User Id=friccio;Password=oracle; EnList=true;"/>
<add key="CONEXION_MSSQL" value="Data Source=srvsql2012; Initial Catalog=PRD; User id=sa; Password=P@ssw0rd; EnList=true;"/>
</appSettings>


Es importante que ambas conexiones cuenten con la opción EnList=true. Esto permitirá que la conexión de base de datos pueda estar enlistada en una transacción distribuida.



La clase CommittableTransaction permite iniciar la transacción distribuida y enlista las conexiones de base de datos mediante el método EnlistTransaction, el cual es un método de las Conexiones.
Al ejecutar el método Commit de la clase CommittableTransaction se confirma la transacción en el servidor MSDTC y ambas base de datos confirman el cambio.
Se adjunta las librerías requeridas por la capa DAO y la cláusula using de la clase TransferenciaDAO.




Nota
: También existe la clase TransactionScope que permite también trabajar con transacciones distribuidas en vez de usar la clase CommittableTransaction. La clase TransactionScope no requiere que enlistemos las conexiones de base de datos ni tampoco ejecutar el método de rollback.

Capa LogicaNegocio - TransferenciaBO:


 

Capa Entidad – ETransferencia:



Capa ServWin_Banco

Esta capa permite exponer la lógica de negocio de TransferenciaBO como un servicio WCF dentro un servicio Windows.

  • Creación del Servicio Windows

Procedemos a modificar la clase Service1 por otro nombre. En el proyecto se cambió por el nombre
ServTransferencia.


Cada vez que el servicio de Windows inicie, creará un objeto de la clase ServicioBanco que es el que implementa la publicación por WCF. El método OPEN permite que el servicio WCF se quede escuchando requerimientos para proceder atenderlos. Mismo caso ocurre al detener el servicio Windows donde terminamos la publicación WCF y eliminamos el objeto creado.



En el entorno de diseño de la clase ServTransferencia, damos click al botón derecho y seleccionamos la opción AddInstaller, esto nos permitirá definir como se desplegará el servicio en el Windows Server. Al dar click se creará el archivo ProjectInstaller.cs.


Ingresando al entorno de diseño del archivo ProjectInstaller.cs veremos lo siguientes componentes donde personalizaremos nuestro servicio.


En el proyecto se optó por las siguientes configuraciones:
serviceProcessInstaller1


serviceInstaller1

  • Creación del Servicio WCF


Creamos el servicio ServicioBanco.


Creamos el ServiceContract y el DataContract


La clase ServiceBanco será la que implementará lo definido en la Interfaz IServicioBanco.


Definimos la dirección y la forma por donde será expuesto el servicio WCF.



Abrimos el editor de configuración de WCF y modificaremos el archivo app.config o web.config (Dependerá del tipo de proyecto que estemos implementando).
a) Definimos que la forma de publicación del servicio.


El servicio será publicado por el protocolo tcp mediante el puerto 7777.

b) Definimos los endpoints.
Creamos 2 endpoints.


Definimos las propiedades del primer endpoint
.
El primer endpoint indica que Contrato (Interfaz) se va a exponer al cliente.


Definimos las propiedades del segundo endpoint
.
El segundo endpoint permite a los clientes recibir la metadata del servicio WCF usando mensajes SOAP.

Por último desactivamos el acceso por HTTP Get, debido a que nuestro servicio será consumido por TCP.

Se presenta el contenido completo del archivo de configuración app.config como resultado final de la configuración.

Nota: Las formas de cómo puede ser expuesto un servicio WCF son: Enterprise Service, Web Service, Remoting, Sockets, Message Queues. En la implementación presentada solo hemos usado Socket.


Instalamos el Servicio Windows

Con la consola de comandos del Visual Studio ejecutamos el siguiente comando:
InstallUtil.exe <MiServicioEjecutable.exe>


Posterior a la instalación, debemos ser capaces de visualizar nuestro servicio.


Nota: InstallUtil.exe /u < MiServicioEjecutable.exe> permite remover el Servicio.

 

Capa Cliente
A continuación se presenta la pantalla de la aplicación.



Se detalla los pasos para consumir el servicio WCF expuesto en el Servicio Windows.



Añadimos un Service Reference al proyecto y consumimos el servicio WCF.


Se enmarca la llamada del servicio WCF al momento de ejecutar click en el botón de Transferir.

 

Monitoreo
La consola MSDTC presenta herramientas que nos permiten ver las transacciones distribuidas que se están ejecutando. Como también conseguir información histórica.


Asimismo todas las transacciones distribuidas pendientes de confirmación serán visualizadas en la vista V$GLOBAL_TRANSACTION.
Aquí se visualiza otra transacción distribuida, donde la base de datos Oracle está enlistada.


Cabe mencionar que a partir de la versión Oracle Database 11gR1, ODP.NET viene con una característica llamada Promotable Transaction, la cual hace que la conexión de la base de datos Oracle se comporte como si no estuviera dentro de una transacción distribuida, consumiendo menor cantidad de recursos hasta que exista una segunda conexión de base de datos enlistada, mientras tanto la considera local.

En caso que nos encontremos con una transacción en duda que el MSDTC no pudo resolver debemos ejecutar los siguientes pasos en nuestra base de datos Oracle:


a) Obtenemos la lista de transacciones dudosas:
SQL> select LOCAL_TRAN_ID, GLOBAL_TRAN_ID, STATE, FAIL_TIME, RETRY_TIME  from DBA_2PC_PENDING;
Si el campo STATE indica el valor de PREPARED ejecutamos el punto b en caso contrario ejecutamos el punto c.
b) Rollback a la transacción:
SQL> ROLLBACK FORCE '#ID_TRANSACCION';
c) Cancelamos la transacción distribuida:
execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('#ID_TRANSACCION');
Nota 1: El Id de la Transacción lo obtendremos del campo LOCAL_TRAN_ID de la vista DBA_2PC_PENDING.
Nota 2: Si existen otras bases de datos Oracle enlistadas en la transacción distribuida es importante que también conocer si la transacción pendiente está resuelta.
SQL> select DATABASE,LOCAL_TRAN_ID,DBID,SESS#,BRANCH from DBA_2PC_NEIGHBORS;


Conclusiones
Se puede apreciar durante todo el desarrollo de este documento, Oracle Database 12c nos provee una completa transparencia y compatibilidad para realizar transacciones distribuidas en nuestras aplicaciones con Microsoft .NET. Además de integrarse de manera sencilla al servicio MSDTC independiente si nuestra solución de Oracle Database corre sobre una plataforma diferente a Microsoft Windows como fue nuestro caso.
Asimismo Oracle Database se acopló sin problema a una solución distribuida propuesta con WCF, además de ofrecer disponibilidad y escalabilidad al servicio de base de datos mediante Oracle RAC.
Debemos tener presente una serie de configuraciones y recomendaciones previamente mencionadas en el documento que deben ser configuradas cuando deseamos enlistar una base de datos Oracle RAC en una transacción distribuida.
Es recomendable que siempre se pueda validar que la base de datos que deseamos enlistar tenga soporte a transacciones distribuidas por el estándar XA y asimismo su driver.
Por ejemplo: La base de datos MySQL permite el uso de transacciones distribuidas XA pero a la fecha de la creación de este documento, el driver (versión 6.8.3) de .NET  implementado por MySQL para su base de datos actualmente no tiene el soporte para transacciones distribuidas XA.



Con esta previa validación, podemos asegurar que nuestro proyecto pueda ser viable.



Francisco Riccio. Es un IT Architect en IBM Perú e instructor de cursos oficiales de certificación Oracle. Está reconocido por Oracle como un Oracle ACE y certificado en productos de Oracle Application & Base de Datos.