Miguel Angel Morán

Machines take me by surprise with great frecuency...

January 2008 - Artículos

Servicios de Workflow: Tracking personalizado, Tracking SQL tracking y Persistencia SQL

¡Q tranx niños! Desde mi humilde opinión de los principales beneficios que ofrece el motor de ejecución de Windows Workflow Foundation es que posee la capacidad de proveer servicios que tienen como objetivo el simplificar las tareas comunes a los cuales se enfrenta un desarrollador de flujos de trabajo.

El primero de los servicios que cubriremos en nuestro estudio ese servicio denominado "Tracking Services" que puede ser utilizados para una gran cantidad de objetivos.

Podemos definir a Tracking Services como el servicio que proporciona automáticamente el rastreo y/o seguimiento de la ejecución de cualquier instancia de un flujo de trabajo. Esta característica es particularmente importante debido a que en los flujos de trabajo muchas veces nos interesa saber qué actividades fueron ejecutadas y  en general el camino que se siguió el motor de ejecución para completar determinado  flujo de trabajo.     

El panorama en el cual a nosotros podemos hacer uso de esta característica es realmente amplio: Por ejemplo: Cuando utilizamos a WorkFlow Foundation como un proveedor de servicios de orquestación en un panorama de Enterprise Service Bus dentro de una arquitectura orientada servicios  es sumamente importante conocer los  servicios que fueron invocados desde la orquestación y los usuarios que utilizaron el sistema, en este escenario podemos utilizar los Tracking  services para monitorear la actividad que se está generando el momento de ejecutar los flujos de trabajo involucrados en orquestación de los servicios, en otras palabras la utilización de estos servicios será efectivamente en los escenarios de auditoría de monitoreo de flujos de trabajo.

Para poder acceder a los servicios que ofrece Tracking services hacemos uso del espacio de nombres System.Workflow.Runtime.Tracking que contiene las clases que nos permitirán generar ya sea a nuestra propia solución de monitoreo y seguimiento  de los flujos de trabajo o bien podemos utilizar soluciones que ya vienen listas para usarse como el SQL tracking service.

Antes de continuar los adentraremos un poco al arquitectura de Tracking Services.

Básicamente existen tres componentes principales dentro del arquitectura de los Tracking services y estos son:

1. Tracking Profiles (perfiles de seguimiento)

Los perfiles de seguimiento representan la manera con la cuál es posible identificar los orígenes y las fuentes de los eventos que deseamos capturar dentro del monitoreo del flujo de trabajo. Estos perfiles son indispensables al momento de generar nuestra propia solución de seguimiento. Existen tres tipos de eventos o sucesos a los cuales nos podemos suscribir para monitorearlos

a) Workflow Events (Eventos del flujo de trabajo):

Estos eventos surgen a nivel de instancia y son equivalentes a los eventos a los cuales nos suscribimos al momento de invocar un flujo de trabajo. Como ejemplo de estos flujos tenemos los eventos:  Created, Terminated, Suspended etc.

b) Activity Events (eventos de las actividades)

Estos eventos son generados por las actividades que están siendo ejecutadas dentro de la instancia del flujo de trabajo.

c) User Events (Eventos de usuario):

Existen ocasiones en las cuales es necesario obtener información adicional sobre lo que está sucediendo en un determinado flujo de trabajo y que no necesariamente corresponde a la situación de la instancia del mismo flujo de trabajo o de alguna de sus actividades sino que es necesario generar un suceso personalizado para capturar determinado comportamiento del flujo de trabajo.

Para cada uno de los eventos descritos previamente existe una clase específica que guarda información sobre los mismos. Nos referimos a las clases WorkflowTrackingRecord ActivityTrackingRecord y UserTrackingRecord respectivamente

 2. Tracking Runtime (Motor de ejecución de los servicios de seguimiento)

El motor de ejecución de los servicios de seguimiento de los flujos de trabajo se encarga de iniciarlo servicios de monitoreo que han sido declarados antes de la invocación de la instancia del flujo de trabajo que deseamos monitorear para hacer esto utiliza la información encontrada dentro de los perfiles de seguimiento.

3. Channels (Canales)

Los  canales de monitoreo se usan para enviar los registros asociados a la instancia del flujo de trabajo cuando se encuentra un tracking point que es la clase genérica que recibe información de lo que está sucediendo dentro del flujo de trabajo.

 Crearemos a continuación la implementación básica de un servicio de seguimiento personalizado.

1)  Iniciaremos con una forma a la que llamaremos FormaInicio, en la cual colocaremos dos etiquetas, una de las cuales usaremos como el receptor de la información que generan las actividades del flujo de trabajo, y otra como los mensajes que genera el servicio de seguimiento (Tracking Service) que generaremos en este ejemplo.


2) Añadiremos a continuación a la solución un flujo de trabajo secuencial y agregaremos una actividad del tipo codeActivity


3) En el código de codeActivity1 simplemente escribiremos en la etiqueta un mensaje con el siguiente código

C# 

        private void codeActivity1_ExecuteCode(object sender, EventArgs e)

        {

            Programa.FormaInicio.lblMensaje.Text += "MENSAJE DESDE LA CODEACTIVITY1\n";          

           

        }

            }

 


4) Posteriormente vamos a invocar la ejecución del flujo de trabajo en el manejador de evento del clic del botón de la forma y generaremos la suscripción al evento WorkFlowCompleted del flujo de trabajo con el código que aparece a continuación

C# 

  private void button1_Click(object sender, EventArgs e)

        {

            WorkflowRuntime MotorWF = new WorkflowRuntime();

            Type Tipo = typeof(Workflow1);

            WorkflowInstance InstanciaWF = MotorWF.CreateWorkflow(Tipo);

            InstanciaWF.Start();

            MotorWF.WorkflowCompleted +=

                new EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);

 

        }

 

        void MotorWF_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)

        {

            MessageBox.Show("¡El flujo de trabajo fue finalizado!");

        }

}

 

 

 
Al momento de ejecutar el código previamente mostrado en la etiqueta que recibe la información generada desde flujo de trabajo deberá aparecer el mensaje "MENSAJE DESDE LA CODEACTIVITY1"

5) Hemos llegado al momento en el cuál propiamente iniciaremos el desarrollo del servicio de tracking. Añadiremos una nueva clase a nuestro proyecto que denominaremos ServicioSeguimiento. Esta clase deberá heredar de la clase TrackingService que es la clase abstracta base que provee la interfase entre un servicio de seguimiento y el motor de ejecución de los servicios de seguimiento que mencionamos anteriormente en los conceptos de arquitectura de este capítulo.

Para generar exitosamente una clase que pueda ser consumida por el motor de seguimiento es necesario sobreescribir (hacer override) los siguientes miembros de la clase base TrackingService.

  • GetProfile(Guid),
  • GetProfile(Type, Version)
  • TryGetProfile
  • GetTrackingChannel
  • TryReloadProfile.

 

El motor de ejecución de los servicios de seguimiento solicita un objeto del tipo TrackingChannel para cada una de las instancias que tienen un TrackingProfile asignado y usa este TrackingChannel para mandar los registros asociados a dar una instancia de flujo de trabajo.  Dentro de la definición del TrackingProfile se debe de especificar cuáles serán los eventos que serán monitoreados y cuya información es recibida por un TrackingRecord que como vimos anteriormente, puede tener como clases concretas los tipos:  ActivityTrackingRecord,  UserTrackingRecord o WorkflowTrackingRecord.

Posteriormente la infraestructura de WF manda a llamar el método TryReloadProfile para verificar si se debe recargar el Profile. Este proceso permite a un cliente o a un servicio cambiar un profile de seguimiento cambiar dinámicamente.

 C#

 

   public class ServicioSeguimiento : TrackingService

    {

        protected override bool TryGetProfile(Type workflowType, out TrackingProfile profile)

        {

            //El  objetivo de este método es que el motor de seguimiento 

            //dependiendo del tipo de WF el servicio puede utilizar diferentes tracking

            //profiles. Aunque es necesario hacer el override en este ejemplo utilizaremos

            //para que el ejemplo compile en esta muestra usaremos

            //el mismo profile para cualquier WF

 

 

            profile = ObtenerProfile();

          return true;

        }

 

        protected override TrackingProfile GetProfile(Guid workflowInstanceId)

        {

            // No será implementado para este ejemplo

            throw new NotImplementedException("No implementado");

        }

 

 

 

        protected override TrackingProfile GetProfile(Type workflowType, Version profileVersionId)

        {

          return ObtenerProfile();

        

        }

 

      

        protected override bool TryReloadProfile(Type workflowType, Guid workflowInstanceId, out TrackingProfile profile)

        {

            // En este caso siempre regresamos falso indicando que no hay nuevos profiles

            profile = null;

            return false;

        }

 

        protected override TrackingChannel GetTrackingChannel(TrackingParameters parametros)

        {

            //El motor de WF llama este método para obtener el canal para la instancia de monitoreo

        

            return new CanaldeSeguimiento(parametros);

        }

 

        private TrackingProfile ObtenerProfile()

        {

                // Creamos un  Tracking Profile

            TrackingProfile profile = new TrackingProfile();

            profile.Version = new Version("3.0.0");

 

       

 

            // En este caso monitorearemos específicamente actividades, no otro tipo de eventos

            ActivityTrackPoint PuntoSeguimiento = new ActivityTrackPoint();

            ActivityTrackingLocation LocacionActividad = new ActivityTrackingLocation(typeof(Activity));

            LocacionActividad.MatchDerivedTypes = true;

       

            //Con este código se registran todos los posibles estados

            IEnumerable<ActivityExecutionStatus> estados = Enum.GetValues(typeof(ActivityExecutionStatus)) as IEnumerable<ActivityExecutionStatus>;

            foreach (ActivityExecutionStatus estado in estados)

            {

                LocacionActividad.ExecutionStatusEvents.Add(estado);

            }

 

            PuntoSeguimiento.MatchingLocations.Add(LocacionActividad);

            profile.ActivityTrackPoints.Add(PuntoSeguimiento);

            return profile;

        }

  

 

         }

 

 
 

Como podemos ver en el código presentado anteriormente el método GetTrackingChannel devuelve un objeto del tipo TrackingChannel donde en este caso específicamente estaremos enviando a la forma la información que ha sido monitoreada automáticamente al momento de ser ejecutado el flujo de trabajo.

6) A continuación añadiremos a nuestro programa una nueva clase denominada CanaldeSeguimiento que heredará de TrackingChannel cuyo objetivo será manejar para persistirlos. En nuestro ejemplo simplemente se envía la información recopilada a la etiqueta que se encuentra en la forma.

De esta clase es necesario sobre escribir los métodos heredados desde la clase abstracta Send y InstanceCompletedOrTerminated para lograr un código como el siguiente:

 

public class CanaldeSeguimiento : TrackingChannel

    {

        private TrackingParameters Parametros = null;

 

        protected CanaldeSeguimiento()

        {

        }

 

        public CanaldeSeguimiento(TrackingParameters parametros)

        {

            this.Parametros = parametros;

        }

 

        //Este es el método que se ejecuta para realizar el registro de la actividad generada por el WF

        protected override void Send(TrackingRecord Registro)

        {

            ActivityTrackingRecord RegistroActividad = (ActivityTrackingRecord)Registro;

          

           Programa.FormaInicio.lblSeguimiento.Text+="Hora: " + RegistroActividad.EventDateTime.ToString();

           Programa.FormaInicio.lblSeguimiento.Text += "Fecha: " + RegistroActividad.QualifiedName.ToString();

           Programa.FormaInicio.lblSeguimiento.Text += "Tipo: " + RegistroActividad.ActivityType;

           Programa.FormaInicio.lblSeguimiento.Text += "Estado: " + RegistroActividad.ExecutionStatus.ToString();

          

        }

 

       

        //Se llama cuando se termina de ejecutar la instancia

        protected override void InstanceCompletedOrTerminated()

        {

            MessageBox.Show("Se terminó la instancia");

        }

 

      

    }

 

Hemos descrito entonces los pasos básicos para generar un servicio de seguimiento personalizado para cualquier instancia de un flujo de trabajo, que monitorea la ejecución de actividades y en este caso muestra la información a la etiqueta definida en nuestra forma principal oct.

Lo único que falta para que nuestro Tracking Service personalizado funcione es agregar el servicio al momento de que se invocan las islas de los flujos de trabajo es decir, en el  debemos de modificar nuestra invocación con la siguiente línea de código

  private void button1_Click(object sender, EventArgs e)

        {

            WorkflowRuntime MotorWF = new WorkflowRuntime();

        //Añadimos el servicio que acabamos de crear

          MotorWF.AddService(new ServicioSeguimiento());

            Type Tipo = typeof(Workflow1);

            WorkflowInstance InstanciaWF = MotorWF.CreateWorkflow(Tipo);

            InstanciaWF.Start();

            MotorWF.WorkflowCompleted +=

                new EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);

 

        }

 

        void MotorWF_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)

        {

            MessageBox.Show("¡El flujo de trabajo fue finalizado!");

        }

}

 

 

 

Al momento de ejecutar el código anterior y oprimir el botón de invocación del flujo de trabajo podemos observar cómo el motor de ejecución de Workflow Foundation ejecuta los diferentes métodos definidos en las clases ServicioSeguimiento y CanaldeSeguimiento y muestra en la forma el resultado del seguimiento del flujo de trabajo


Como pudimos observar los ejemplo anterior podemos hacer un seguimiento muy específico de todas las situaciones que están ocurriendo durante la ejecución de nuestro flujo de trabajo para llevar a una auditoría muy a detalle de lo que sucede en el WF; sin embargo no siempre es necesario programar lo que hemos visto previamente sino que WF incluye una solución ya lista para usarse basada en SQL Server que se denomina SQLTracking y que hace lo mismo que vimos anteriormente pero su persistencia es evidentemente el servidor de base de datos SQL Server y nosotros lucharemos que programar absolutamente nada porque todo ya está hecho. Cabe señalar que ésta implementación utiliza la misma técnica y usa como clase de bases las mismas clases que utilizamos para nuestro tracking service personalizado.

Para poder utilizar esta funcionalidad es necesario tener instalada correctamente una instancia de SQL Server 2005 (Express o cualquier versión comercial) y ejecutar unos scripts que se distribuyen con la instalación del .NET Framework 3.0 y que se encuentran en C:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN

Estos scripts son:

  • Tracking_Schema.sql Crea la estructura de SQLTracking en la BDD.
  • Logic_Schema.sql, Crea la lógica de negocio de SQLTracking en la BDD.

 

 

Modificando nuestro ejemplo previo agregaremos ahora el servicio SqlTrackingService que como podemos ver contiene un constructor sobrecargado en el cual le pasaremos como parámetro la cadena de conexión que indica la instancia y el nombre de la base de datos de SQL Server.

 

private void button1_Click(object sender, EventArgs e)

        {

            WorkflowRuntime MotorWF = new WorkflowRuntime();

 

            MotorWF.AddService(new ServicioSeguimiento());

 

            //Agregamos servicio SQLTrackingService;

            MotorWF.AddService(new

            SqlTrackingService(@"Data Source=.\sqlexpress;" +

            "Initial Catalog=Tracking;Integrated Security=sspi"));

 

 

            Type Tipo = typeof(Workflow1);

            WorkflowInstance InstanciaWF = MotorWF.CreateWorkflow(Tipo);

            InstanciaWF.Start();

            MotorWF.WorkflowCompleted +=

                new EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);

 

        }

 

 

 

Podemos ahora ir al Management Studio de SQL Server y ejecutar el siguiente query que nos mostrará la efectividad del SqlTrackingService:

T-SQL



SELECT  TrackingWorkflowEvent.Description as Evento,

WorkflowInstanceEvent.EventDateTime as HoraInicial,

WorkflowInstance.WorkflowInstanceId as InstanciaWF,

Type.TypeFullName as NombreWF

FROM   WorkflowInstanceEvent

INNER JOIN TrackingWorkflowEvent ON

WorkflowInstanceEvent.TrackingWorkflowEventId = TrackingWorkflowEvent.TrackingWorkflowEventId AND

WorkflowInstanceEvent.TrackingWorkflowEventId = TrackingWorkflowEvent.TrackingWorkflowEventId

INNER JOIN WorkflowInstance ON

WorkflowInstance.WorkflowInstanceInternalId=WorkflowInstanceEvent.WorkflowInstanceInternalId

INNER JOIN Type ON

Type.TypeId = WorkflowInstance.WorkflowTypeId

 

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


El query anterior nos mostrará el siguiente resultado

 

 

 

 

Persistencia

 

 

Existe otro concepto muy importante dentro de los flujos de trabajo y que también es provisto mediante los servicios de ejecución del motor de flujos de trabajo de Workflow Foundation es la persistencia. Este concepto se le denomina persistencia y es utilizada siempre que se requiere recordar el estado de una determinada instancia de un flujo de trabajo para posteriormente ocuparla en otro momento del tiempo. Este tipo de flujos de trabajo son muy utilizados en los flujos humanos es decir aquellos en los cuales intervienen diferentes miembros una organización y cada uno completa una parte de las actividades específicas dentro del flujo de trabajo pero no lo hacen todos al mismo tiempo sino que cada quien interviene con sus actividades del flujo en momentos discontinuos. El motor de flujos de trabajo sobre una solución extremadamente simple y fácil de utilizar para implementar este tipo de flujo de trabajo también denominados flujos de trabajo de ejecución larga (Long running workflows)

Los servicios de persistencia de Workflow Foundation se encargan de guardar el estado de cualquier instancia de los flujos de trabajo en lo hacen de una manera transparente automática al momento de que ocurre algún evento que haga que el flujo de trabajo entre en un estado de suspensión es decir toda la información referente al flujo se serializa en un medio persistente (de ahí el nombre)

Modificaremos ahora el proyecto previamente usado para verificar el funcionamiento de los servicios de persistencia de WF.

1) Lo primero que haremos será modificar nuestro flujo de trabajo agregando una actividad del tipo SuspendActivity y  otra CodeActivity después de la primera, cuyo código será: Programa.FormaInicio.lblMensaje.Text += "MENSAJE DESDE LA CODEACTIVITY2\n";


2) Modificaremos también la forma principal del proyecto (frmInicio) y le agregaremos una caja de texto, así como un botón adicional, de tal manera que quede parecida a la siguiente:


3) Modificaremos ahora la invocación del flujo de trabajo en el primer botón para añadir el servicio de SqlWorkflowPersistenceService que se encuentra dentro del espacio de nombres System.Workflow.Runtime.Hosting, así mismo agregaremos un manejador de eventos para que el host se suscriba al evento que generará la SuspendActivity y que notificará al host de dicha suspensión  tal modo que la inicialización del motor del flujo de trabajo deberá parecerse a la siguiente:

 

 

  private void button1_Click(object sender, EventArgs e)

        {

            WorkflowRuntime MotorWF = new WorkflowRuntime();

 

            MotorWF.AddService(new ServicioSeguimiento());

 

            //Agregamos servicio SQLTrackingService;

            MotorWF.AddService(new

            SqlTrackingService(@"Data Source=.\sqlexpress;" +

            "Initial Catalog=WF;Integrated Security=sspi"));

 

            //Agregamos servicio de persistencia

            MotorWF.AddService(new

              SqlWorkflowPersistenceService (@"Data Source=.\sqlexpress;" +

            "Initial Catalog=WF;Integrated Security=sspi"));

 

 

            Type Tipo = typeof(Workflow1);

            WorkflowInstance InstanciaWF = MotorWF.CreateWorkflow(Tipo);

 

              MotorWF.WorkflowCompleted +=

                new EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);

 

            //Nos suscribimos a la suspensión del flujo de trabajo por la SuspendActivity

// que añadimos al WF.

 

            MotorWF.WorkflowSuspended +=

                new EventHandler<WorkflowSuspendedEventArgs>(MotorWF_WorkflowSuspended);

 

            InstanciaWF.Start();

  

        }

 

 


4) En el manejador de de eventos determinado por la función MotorWF_WorkflowSuspended agregaremos el siguiente código para obtener el id de instancia del WF que usaremos posteriormente para solicitar al motor de WF que reactive el flujo de trabajo persistido. Haremos esto mediante el siguiente código.

C#

  void MotorWF_WorkflowSuspended(object sender, WorkflowSuspendedEventArgs e)

        {

 

            e.WorkflowInstance.Unload();

            MessageBox.Show("WF Persistido!!!");

            //Agregamos el id de instancia a caja de texto para hacer la reactivacion

            this.textBox1.Text = e.WorkflowInstance.InstanceId.ToString();

        }

 

 
 

5)Para finalizar, al nuevo botón que acabamos de agregar le añadiremos el siguiente código que invoca a los servicios de persistencia para reactivar el flujo de trabajo previamente persistido (debido a que fue suspendido y descargado en el paso previo) utilizando como su identificador el id de la instancia que se encuentra en la caja de texto.

C#

  private void button2_Click(object sender, EventArgs e)

        {

 

 

            WorkflowRuntime MotorWF = new WorkflowRuntime();

 

            MotorWF.AddService(new

             SqlWorkflowPersistenceService(@"Data Source=.\sqlexpress;" +

           "Initial Catalog=WF;Integrated Security=sspi"));

            MotorWF.StartRuntime();

 

            MotorWF.WorkflowCompleted +=

               new EventHandler<WorkflowCompletedEventArgs>(MotorWF_WorkflowCompleted);

 

 

             WorkflowInstance InstanciaWF = MotorWF.GetWorkflow(new Guid(this.textBox1.Text));

 

             InstanciaWF.Resume();

 

        }

 

 

 
 


6) Al ejecutar el flujo de trabajo podemos observar que la instancia es suspendida después de que se ejecuta la primera actividad de código, y posteriormente la caja de texto adquiere el GUID que representa la instancia del WF.

 


7) Al oprimir el botón con el texto Reactivar WF se reactivará el flujo de trabajo persistido mediante SQLPersistenceServices y el motor de ejecución de WF completará el proceso  de ejecución del flujo de trabajo.

 

 

 

Bueno pues ojalá les haya latido el artículo. El código fuente completo de este artículo está aqui

No se despeguen de este blog porque vienen cosas interesantes.

Antes de irme a dormir, algunas cosas: 

a) Estamos organizando algo muy padre para juntarnos y platicar sobre tecnología aquí en la Ciudad de México. Muy pronto más detalles.

b) Ultimamente he tenido el gusto de recibir bastante correspondencia de ustedes, mis queridisimos lectores (jajaja chale ke gay) el caso es que me da mucho gusto que me escriban :) pero les recomiendo que lo hagan en los foros de este espacio para que las dudas y las respuestas puedan estar disponibles para todos, de todos modos les seguiré contestando pero si son dudas netamente técnicas (como la mayoría de los mails que he recibido estos dias) les pediría que lo pongan acá en los foros, ah ahora que me acuerdo hay gente que me comenta que no recibe el mail de confirmación cuando se registran. El buen Victor Alameda seguramente pronto verificará que onda con el sistema.

 Un abrazo, coman sano, con y sin albur.

Maic.