“Tracking Services en Windows Workflow Foundation”
Uno 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#
1:
2:
3: private void codeActivity1_ExecuteCode(object sender, EventArgs e)
4:
5: { 6:
7: Programa.FormaInicio.lblMensaje.Text += "MENSAJE DESDE LA CODEACTIVITY1\n";
8:
9: }
10:
11: }
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
Maic.