La forma de flujo de trabajo: Descripción de Windows Workflow Foundation
David Chappell
Chappell & Associates
Abril de 2009
Descargar este artículo
Introducción a Windows Workflow Foundation
Todos los que escriben código quieren crear software excelente. Si ese software es una aplicación de servidor, parte de ser excelente es escalar bien, controlar cargas grandes sin consumir demasiados recursos. Una gran aplicación también debe ser lo más fácil posible de entender, tanto para sus creadores como para las personas que lo mantienen.
La consecución de estos dos objetivos no es fácil. Los enfoques que ayudan a escalar las aplicaciones tienden a dividirlos, dividiendo su lógica en fragmentos independientes que pueden ser difíciles de entender. Sin embargo, escribir lógica unificada que reside en un único ejecutable puede hacer que el escalado de la aplicación sea todo, pero imposible. Lo que se necesita es una manera de mantener la lógica de la aplicación unificada, lo que hace que sea más comprensible, a la vez que se deja escalar la aplicación.
Lograr esto es un objetivo principal de Windows Workflow Foundation (WF). Al admitir la lógica creada mediante flujos de trabajo, WF proporciona una base para crear aplicaciones unificadas y escalables. Junto con esto, WF también puede simplificar otros desafíos de desarrollo, como coordinar el trabajo paralelo, realizar un seguimiento de la ejecución de un programa, etc.
WF se lanzó por primera vez con .NET Framework 3.0 en 2006 y, a continuación, se actualizó en .NET Framework 3.5. Estas dos primeras versiones eran útiles, especialmente para proveedores de software independientes (ISV), pero no se han convertido en tecnologías estándar para desarrolladores empresariales. Con la versión de WF que forma parte de .NET Framework 4, sus creadores pretenden cambiar esto. Un objetivo principal de esta versión más reciente es convertir WF en una parte estándar del kit de herramientas de programación para todos los desarrolladores de .NET.
Al igual que cualquier tecnología, la aplicación de WF requiere comprender lo que es, por qué es útil y cuándo tiene sentido usarlo. El objetivo de esta introducción es aclarar estas cosas. No aprenderá a escribir aplicaciones WF, pero obtendrá un vistazo a lo que ofrece WF, por qué es la manera en que es y cómo puede mejorar la vida de un desarrollador. En otras palabras, comenzará a comprender el flujo de trabajo.
El desafío: Escribir lógica de aplicación unificada y escalable
Una manera sencilla de escribir un programa es crear una aplicación unificada que se ejecute en un único proceso en una sola máquina. En la figura 1 se muestra esta idea.
Figura 1: La lógica de la aplicación se puede crear como un todo unificado y, a continuación, ejecutarse en un subproceso determinado en un proceso que se ejecuta en una sola máquina.
El pseudocódigo simple de este ejemplo muestra los tipos de cosas que suelen hacer las aplicaciones:
- Mantenga el estado, que aquí se representa mediante una variable de cadena simple.
- Obtenga la entrada desde el mundo exterior, por ejemplo, mediante la recepción de una solicitud de un cliente. Una aplicación sencilla podría simplemente leer desde la consola, mientras que un ejemplo más común podría recibir una solicitud HTTP desde un explorador web o un mensaje SOAP de otra aplicación.
- Enviar salida al mundo exterior. En función de cómo se compila, la aplicación podría hacerlo a través de HTTP, un mensaje SOAP, escribiendo en la consola o de alguna otra manera.
- Proporcione rutas de acceso alternativas a través de la lógica mediante instrucciones de flujo de control como if/else y while.
- Realice el trabajo ejecutando el código adecuado en cada punto de la aplicación.
En el enfoque unificado que se muestra aquí, la lógica de la aplicación pasa toda su vida ejecutándose en un subproceso dentro de un proceso determinado en una sola máquina. Este enfoque sencillo tiene varias ventajas. Para una cosa, la lógica se puede implementar de una manera sencilla y unificada. Esto ayuda a las personas que trabajan con el código a comprenderlo y también hace que el orden permitido de los eventos sea explícito. En la figura 1, por ejemplo, es evidente que la primera solicitud del cliente debe preceder a la segunda: el flujo de control del programa lo requiere. Cuando llega la segunda solicitud, no es necesario comprobar que la primera solicitud ya se ha controlado, ya que no hay ninguna otra ruta de acceso a través de la aplicación.
Otra ventaja de este enfoque es que trabajar con el estado de la aplicación es fácil. Ese estado se mantiene en la memoria del proceso y, dado que el proceso se ejecuta continuamente hasta que se realiza su trabajo, el estado siempre está disponible: el desarrollador solo tiene acceso a variables normales. ¿Qué podría ser más sencillo?
Sin embargo, este estilo de programación básico tiene limitaciones. Cuando la aplicación necesita esperar la entrada, ya sea desde un usuario en la consola, un cliente de servicios web o algo más, normalmente solo se bloqueará. Tanto el subproceso como el proceso que usa se conservarán hasta que llegue la entrada, sin embargo, el tiempo que tarda. Dado que los subprocesos y procesos son recursos relativamente escasos, las aplicaciones que se mantienen en cualquiera de ellas cuando solo esperan que la entrada no se escale muy bien.
Un enfoque más escalable consiste en apagar la aplicación cuando está esperando la entrada y, a continuación, reiniciarla cuando llegue esa entrada. Este enfoque no desperdicia recursos, ya que la aplicación no se mantiene en un subproceso o en un proceso cuando no los necesita. Esto también permite que la aplicación se ejecute en diferentes procesos en diferentes máquinas en momentos diferentes. En lugar de bloquearse en un único sistema, como en la figura 1, la aplicación se puede ejecutar en una de varias máquinas disponibles. Esto también ayuda a la escalabilidad, ya que el trabajo se puede distribuir más fácilmente entre diferentes equipos. En la figura 2 se muestra cómo se ve esto.
Figura 2: La lógica de la aplicación se puede dividir en fragmentos, todo el estado común que se puede ejecutar en diferentes máquinas.
Esta aplicación de ejemplo contiene la misma lógica que antes, pero ahora se divide en fragmentos independientes. Cuando se recibe la primera solicitud del cliente, se carga y ejecuta el fragmento adecuado. Una vez que se ha controlado esta solicitud y se ha enviado una respuesta de vuelta, este fragmento se puede descargar; no es necesario permanecer en memoria. Cuando llega la segunda solicitud del cliente, el fragmento que lo controla se carga y se ejecuta. Como se muestra en la figura 2, este fragmento puede ejecutarse en un subproceso diferente en un proceso diferente que se ejecuta en una máquina diferente del primer fragmento. Una vez que se controla la solicitud, este segundo fragmento también se puede descargar, liberando la memoria que estaba usando.
Un ejemplo común de una tecnología que funciona de esta manera es ASP.NET. Un desarrollador implementa una aplicación ASP.NET como un conjunto de páginas, cada una que contiene parte de la lógica de la aplicación. Cuando llega una solicitud, se carga, se ejecuta la página correcta y, a continuación, se descarga de nuevo.
Una aplicación integrada en este estilo puede usar recursos de máquina de forma más eficaz que una creada con el enfoque más sencillo mostrado anteriormente, por lo que se escalará mejor. Sin embargo, hemos pagado por esta escalabilidad mejorada con complejidad. Para una cosa, los distintos fragmentos de código deben compartir el estado de alguna manera, como se muestra en la figura 2. Dado que cada fragmento se carga a petición, se ejecuta y, a continuación, se cierra, este estado debe almacenarse externamente, como en una base de datos u otro almacén de persistencia. En lugar de tener acceso a variables normales, como el escenario que se muestra en la figura 1, un desarrollador ahora debe hacer cosas especiales para adquirir y guardar el estado de la aplicación. En ASP.NET aplicaciones, por ejemplo, los desarrolladores pueden escribir el estado directamente en una base de datos, acceder al objeto Session o usar algún otro enfoque.
Otro costo de esta escalabilidad mejorada es que el código ya no proporciona una vista unificada de la lógica general del programa. En la versión que se muestra en la figura 1, el orden en que el programa espera la entrada es obvio; solo hay una ruta de acceso posible a través del código. Sin embargo, con la versión de la figura 2, este flujo de control no es evidente. De hecho, el fragmento de código que controla la segunda solicitud del cliente podría necesitar comenzar comprobando que la primera solicitud ya se ha realizado. Para una aplicación que implementa cualquier tipo de proceso empresarial significativo, comprender e implementar correctamente el flujo de control en varios fragmentos puede ser difícil.
La situación es esto: La escritura de aplicaciones unificadas facilita la vida del desarrollador y el código es sencillo de entender, pero el resultado no se escala bien. Escribir aplicaciones fragmentadas que comparten el estado externo, como ASP.NET aplicaciones, permite la escalabilidad, pero hace que la administración del estado sea más difícil y pierda el flujo de control unificado. Lo que nos gustaría es una manera de hacer ambas cosas: escribir lógica de negocios escalable con administración de estado simple, pero todavía tiene una vista unificada del flujo de control de la aplicación.
Esto es exactamente lo que proporciona el flujo de trabajo. En la sección siguiente se muestra cómo.
La solución: la forma de flujo de trabajo
Comprender cómo una aplicación WF resuelve estos problemas (y otros) requiere recorrer los conceptos básicos de cómo funciona WF. A lo largo del camino, veremos por qué esta tecnología puede mejorar la vida para los desarrolladores en un número sorprendentemente grande de casos.
Creación de lógica de aplicación unificada
Una aplicación basada en flujo de trabajo creada con WF realiza los mismos tipos de cosas que una aplicación normal: mantiene el estado, obtiene la entrada de y envía la salida al mundo exterior, proporciona flujo de control y ejecuta código que realiza el trabajo de la aplicación. Sin embargo, en un flujo de trabajo de WF, todas estas cosas se realizan mediante actividades. En la figura 3 se muestra cómo se ve esto, con el enfoque de código unificado que se muestra junto con para la comparación.
Figura 3: En un flujo de trabajo de WF, todas las actividades realizan todo el trabajo de un programa.
Como se muestra en la figura 3, cada flujo de trabajo tiene una actividad más externa que contiene todas las demás. Aquí, esta actividad más externa se denomina Secuencia y, al igual que un programa normal, puede tener variables que mantienen su estado. Dado que Sequence es una actividad compuesta, también puede contener otras actividades.
En este ejemplo sencillo, el flujo de trabajo comienza con una actividad ReceiveMessage que obtiene la entrada del mundo exterior. Esto va seguido de una actividad If, que (sin duda) implementa una rama. Si también es una actividad compuesta, que contiene otras actividades (etiquetadas X e Y aquí) que ejecutan el trabajo realizado en cada rama. La actividad If va seguida de una actividad SendMessage que envía alguna salida al mundo más allá de este flujo de trabajo. Aparece otra actividad ReceiveMessage siguiente, que obtiene más entrada y, a continuación, va seguida de una actividad While. Mientras contiene otra actividad, Z, que realiza el trabajo de este bucle while. Todo el flujo de trabajo finaliza con una actividad SendMessage final, enviando el resultado final del programa.
Todas estas actividades corresponden funcionalmente a varias partes de un programa típico, ya que los colores coincidentes de la figura 2 sugieren. Pero en lugar de usar elementos de lenguaje integrados, como hace un programa tradicional, cada actividad de un flujo de trabajo de WF es realmente una clase. El entorno de ejecución de WF realiza la ejecución del flujo de trabajo, un componente que sabe cómo ejecutar actividades. En la figura 4 se muestra esta idea.
Figura 4: El entorno de ejecución de WF ejecuta actividades en el orden determinado por el flujo de trabajo.
Cuando comienza a ejecutar un flujo de trabajo, el entorno de ejecución de WF ejecuta primero la actividad más externa, que en este ejemplo es una secuencia. A continuación, ejecuta la primera actividad dentro de esa, que aquí es ReceiveMessage, seguida de la siguiente actividad, etc. Las actividades que se ejecutan exactamente en cualquier situación determinada dependen de la ruta de acceso que se realiza a través del flujo de trabajo. Por ejemplo, la obtención de un tipo de entrada en la primera actividad ReceiveMessage puede provocar que la actividad If ejecute la actividad X, mientras que otro tipo de entrada podría provocar que se ejecute la actividad Y. Es como cualquier otro programa.
Es importante comprender que el entorno de ejecución de WF no sabe nada sobre los aspectos internos de las actividades que se están ejecutando. No se puede saber si de receiveMessage. Lo único que sabe cómo hacer es ejecutar una actividad y, a continuación, ejecutar la siguiente. El tiempo de ejecución puede ver los límites entre las actividades, pero, como veremos, es algo útil.
Un corolario importante de esto es que WF no define ningún lenguaje concreto para describir flujos de trabajo, todo depende de las actividades que un desarrollador elija usar. Para facilitar la vida, WF incluye una biblioteca de actividades base (BAL) que proporciona un amplio conjunto de actividades útiles. (Todas las actividades de ejemplo usadas aquí se extraen de bal, de hecho). Pero los desarrolladores pueden crear cualquier otra actividad que les guste. Incluso pueden optar por ignorar el BAL por completo.
Hay una pregunta obvia aquí: ¿Por qué ir a todos estos problemas? La creación de un programa con actividades es diferente de lo que usan los desarrolladores, por lo que ¿por qué alguien debería molestarse? ¿Por qué no escribir código normal?
La respuesta, por supuesto, es que este enfoque puede ayudarnos a crear un mejor código. Observe, por ejemplo, que la forma de flujo de trabajo proporciona al desarrollador un flujo de control unificado. Al igual que en el caso simple que se muestra en la figura 1, la lógica principal del programa se define en una secuencia coherente. Esto facilita la comprensión y, dado que la lógica no se divide en fragmentos, no es necesario realizar comprobaciones adicionales. El propio flujo de trabajo expresa el flujo de control permitido.
Esto representa la mitad de nuestro objetivo: crear lógica de aplicación unificada. Pero las aplicaciones wf también pueden lograr la segunda mitad, creando aplicaciones escalables con administración de estado simple. En la sección siguiente se explica cómo el flujo de trabajo hace posible esto.
Proporcionar escalabilidad
Para ser escalable, una aplicación de servidor no se puede bloquear en un solo proceso en una sola máquina. Sin embargo, dividir explícitamente la lógica de la aplicación en fragmentos, como en ASP.NET páginas, divide lo que debe ser un flujo de control unificado. También obliga al programador a trabajar con el estado explícitamente. Lo que realmente nos gustaría es una manera de dividir automáticamente nuestra lógica en fragmentos que se pueden ejecutar en diferentes procesos en diferentes máquinas. También nos gustaría tener el estado de la aplicación administrado para nosotros, por lo que todo lo que tenemos que hacer es tener acceso a variables.
Esto es exactamente lo que proporcionan los flujos de trabajo. En la figura 5 se muestran los aspectos básicos de cómo WF logra estas cosas.
Figura 5: El tiempo de ejecución de WF descarga un flujo de trabajo mientras espera la entrada y, a continuación, lo carga de nuevo una vez que llega la entrada.
Al igual que cualquier aplicación, un flujo de trabajo de WF bloquea la espera de entrada. En la figura 5, por ejemplo, el flujo de trabajo se bloquea en la segunda actividad ReceiveMessage esperando la segunda solicitud del cliente (paso 1). El tiempo de ejecución de WF observa esto, por lo que almacena el estado del flujo de trabajo y una indicación de dónde debe reanudarse el flujo de trabajo en un almacén de persistencia (paso 2). Cuando llega la entrada para este flujo de trabajo (paso 3), el tiempo de ejecución de WF encuentra su estado persistente y, a continuación, vuelve a cargar el flujo de trabajo, seleccionando la ejecución donde se dejó (paso 4). Todo esto sucede automáticamente: el desarrollador de WF no necesita hacer nada. Dado que el entorno de ejecución de WF puede ver en el flujo de trabajo, puede controlar todos estos detalles.
Una ventaja obvia de este enfoque es que el flujo de trabajo no se bloquea en la memoria bloqueando un subproceso y usando un proceso mientras espera la entrada. Otra ventaja es que un flujo de trabajo persistente puede volver a cargarse en una máquina distinta de la que se estaba ejecutando originalmente. Debido a esto, es posible que diferentes partes del flujo de trabajo terminen ejecutándose en sistemas diferentes, como se muestra en la figura 6.
Figura 6: Un flujo de trabajo puede ejecutarse en subprocesos diferentes, en procesos diferentes y en máquinas diferentes durante su duración.
En este ejemplo, supongamos que el flujo de trabajo controla la primera solicitud de cliente de un proceso en la máquina A. Cuando el segundo ReceiveMessage hace que el flujo de trabajo bloquee la espera de entrada, el tiempo de ejecución de WF descargará el estado del flujo de trabajo en un almacén de persistencia, como se acaba de describir. Cuando llega la segunda solicitud del cliente, es posible que este flujo de trabajo se vuelva a cargar en un proceso de la máquina B. En lugar de estar bloqueado en un subproceso específico de un proceso específico en un equipo específico, se puede mover un flujo de trabajo de WF según sea necesario. Y el desarrollador obtiene esta agilidad de forma gratuita, lo proporciona automáticamente WF.
Vale la pena señalar que el tiempo de ejecución de WF no le importa cuánto tiempo debe esperar el flujo de trabajo para esperar la entrada. Es posible que llegue unos segundos después de que el flujo de trabajo se conserve, unos minutos más tarde o incluso unos meses más tarde. Siempre que el almacén de persistencia todavía se mantenga en el estado del flujo de trabajo, ese flujo de trabajo se puede reiniciar. Esto hace que WF sea una tecnología atractiva para crear aplicaciones que implementen procesos de larga duración. Piense en una aplicación que admita un proceso de contratación, por ejemplo, que abarque todo, desde la programación de entrevistas iniciales hasta la integración de un nuevo empleado en la organización. Este proceso puede durar semanas o meses, por lo que la administración del estado de la aplicación mediante WF tiene buen sentido. Aun así, aunque WF puede ser bastante útil con este tipo de proceso de larga duración, es importante comprender que esto no es su único propósito. Cualquier aplicación que requiera lógica unificada y escalable puede ser una buena candidata para WF.
De hecho, eche otro vistazo a la figura 6. ¿No se parece mucho a la figura 2, que mostró cómo una aplicación fragmentada (por ejemplo, una creada únicamente con ASP.NET) ha logrado la escalabilidad? De hecho, ¿no tiene la figura 6 también una fuerte similitud con la figura 1, que mostró una aplicación unificada creada con un flujo de control lineal? WF logra ambas cosas: el flujo de control de la aplicación se expresa de forma comprensible, unificada y la aplicación puede escalar, ya que no está bloqueado en un único proceso en una sola máquina. Esta es la belleza del flujo de trabajo.
Y eso no es todo; el uso de WF también tiene otras ventajas. Puede facilitar la coordinación del trabajo paralelo, por ejemplo, ayuda con el seguimiento del progreso de una aplicación, etc. En la siguiente sección se examinan estos aspectos de la tecnología.
Otras ventajas de la forma de flujo de trabajo
Un flujo de trabajo de WF consta de actividades ejecutadas por el entorno de ejecución de WF. Aunque comprender el mundo de WF lleva algún esfuerzo, escribir lógica de aplicación en este estilo facilita la programación de varios desafíos comunes, como se describe a continuación.
Coordinación del trabajo paralelo
WF BAL incluye actividades que corresponden a instrucciones de lenguaje de programación conocidas. Por este motivo, puede escribir flujos de trabajo que se comporten de forma muy similar a los programas normales. Sin embargo, la presencia del entorno de ejecución de WF también permite crear actividades que proporcionan más que un lenguaje de programación convencional. Un ejemplo importante de esto es usar un flujo de trabajo para facilitar la coordinación del trabajo paralelo.
No se confunda: el enfoque aquí no es escribir código paralelo que se ejecute simultáneamente en un procesador de varios núcleos. En su lugar, piense en una aplicación que, por ejemplo, necesita llamar a dos servicios web y, a continuación, esperar ambos resultados antes de continuar. Ejecutar las llamadas en paralelo es claramente más rápida que hacerlos secuencialmente, pero escribir código tradicional que no es sencillo. Y aunque .NET Framework proporciona varios enfoques para realizar estas llamadas de forma asincrónica, ninguno de ellos es especialmente sencillo. Esta es otra situación en la que el flujo de trabajo puede brillar: WF facilita esto. En la figura 7 se muestra cómo.
Figura 7: Actividades contenidas en una ejecución de actividad paralela en paralelo.
En esta ilustración se muestra un flujo de trabajo sencillo que es muy similar al ejemplo anterior. La gran diferencia es que ahora invoca dos servicios web y, a continuación, espera una respuesta de ambos antes de continuar. Para ejecutar estas llamadas en paralelo, el creador del flujo de trabajo encapsulaba ambos pares de actividades SendMessage y ReceiveMessage dentro de una actividad Paralela. Parallel es una parte estándar de la biblioteca de actividades base de WF y hace exactamente lo que su nombre sugiere: ejecuta las actividades que contiene en paralelo. En lugar de hacer que el desarrollador se ocupe de la complejidad de manejar esto, el tiempo de ejecución de WF y la actividad Parallel realizan el trabajo pesado. Todo lo que tiene que hacer el desarrollador es organizar las actividades según sea necesario para resolver su problema.
Proporcionar seguimiento automático
Dado que el entorno de ejecución de WF puede ver los límites entre las actividades de un flujo de trabajo, sabe cuándo comienza y finaliza cada una de esas actividades. Dado esto, proporcionar un seguimiento automático de la ejecución del flujo de trabajo es sencillo. En la figura 8 se muestra esta idea.
Figura 8: El entorno de ejecución de WF puede realizar un seguimiento automático de la ejecución de un flujo de trabajo.
Como se muestra en este ejemplo, el entorno de ejecución de WF puede escribir un registro de la ejecución de un flujo de trabajo en un almacén de seguimiento. Una opción es registrar actividades, con un registro escrito cada vez que una actividad comienza y finaliza la ejecución. En el momento que se muestra en la ilustración, por ejemplo, el flujo de trabajo está a punto de empezar a ejecutar la actividad If, por lo que el tiempo de ejecución de WF está escribiendo un evento que indica esto. También es posible realizar un seguimiento de variables específicas, es decir, el estado del flujo de trabajo, registrar sus valores en varios puntos de la ejecución del flujo de trabajo.
Al igual que con otros aspectos de WF, este registro automático no requiere esencialmente ningún trabajo en la parte del desarrollador. Simplemente puede indicar que el seguimiento debe ocurrir, especificando el nivel en el que está interesado, y WF se encarga del resto.
Crear actividades personalizadas reutilizables
Las actividades de BAL de WF proporcionan una variedad de funciones útiles. Como ya se muestra, por ejemplo, este conjunto integrado incluye actividades para el flujo de control, el envío y la recepción de mensajes, el trabajo en paralelo y mucho más. Pero la creación de una aplicación real normalmente requerirá la creación de actividades que realicen tareas específicas de esa aplicación.
Para que esto sea posible, WF permite crear actividades personalizadas. Por ejemplo, las actividades etiquetadas como X, Y y Z en los flujos de trabajo mostrados anteriormente son de hecho actividades personalizadas, ya que la figura 9 hace explícita.
Figura 9: Las actividades personalizadas permiten que un flujo de trabajo realice tareas específicas de la aplicación.
Las actividades personalizadas pueden ser sencillas, realizar solo una tarea o pueden ser actividades compuestas que contienen lógica arbitrariamente compleja. Y si son actividades personalizadas simples o complejas, se pueden usar de muchas maneras diferentes. Por ejemplo, una aplicación empresarial creada con WF podría implementar lógica específica de la aplicación como una o varias actividades personalizadas. Como alternativa, un proveedor de software que usa WF podría proporcionar un conjunto de actividades personalizadas para facilitar la vida de sus clientes. Por ejemplo, Windows SharePoint Services permite a los desarrolladores crear aplicaciones basadas en WF que interactúan con personas a través de las listas estándar de SharePoint. Para facilitar esto, SharePoint incluye actividades personalizadas para escribir información en una lista.
Las actividades personalizadas se pueden escribir directamente en el código, mediante C# o Visual Basic u otro lenguaje. También se pueden crear combinando actividades existentes, lo que permite algunas opciones interesantes. Por ejemplo, una organización podría crear actividades personalizadas de nivel inferior para sus desarrolladores más cualificados y, a continuación, empaquetarlas en funciones empresariales de nivel superior para que las personas menos técnicas las usen. Estas actividades de nivel superior pueden implementar cualquier lógica de negocios necesaria, todo encapsulado perfectamente en un cuadro reutilizable.
Otra manera de pensar en esto es ver un conjunto específico de actividades ejecutadas por el entorno de ejecución de WF como lenguaje. Si una organización crea un grupo de actividades personalizadas que se pueden reutilizar para resolver problemas específicos en varias aplicaciones, lo que realmente hacen es crear un tipo de lenguaje específico del dominio (DSL). Una vez hecho esto, puede ser posible que los usuarios menos técnicos creen aplicaciones WF con estos fragmentos empaquetados previamente de funcionalidad personalizada. En lugar de escribir código nuevo para implementar las funciones de la aplicación, el nuevo software útil podría crearse únicamente mediante el montaje de actividades existentes. Este estilo de DSL, definido en el flujo de trabajo, puede mejorar significativamente la productividad del desarrollador en algunas situaciones.
Hacer que los procesos sean visibles
La creación de aplicaciones con un lenguaje de programación tradicional significa escribir código. La creación de aplicaciones con WF no suele ser bastante bajo. En su lugar, al menos el flujo de control principal de un flujo de trabajo se puede ensamblar gráficamente. Para permitir esto, WF incluye un diseñador de flujo de trabajo que se ejecuta dentro de Visual Studio. En la figura 10 se muestra un ejemplo.
Figura 10: El diseñador de flujos de trabajo, que se ejecuta dentro de Visual Studio, permite que un desarrollador cree un flujo de trabajo arrastrando y quitando actividades.
Como se muestra en este ejemplo, las actividades disponibles para un desarrollador de WF aparecen a la izquierda. Para crear un flujo de trabajo, puede ensamblar estas actividades en la superficie de diseño para crear cualquier lógica que requiera la aplicación. Si es necesario, el diseñador de flujos de trabajo de WF también se puede volver a hospedar en otros entornos, lo que permite que otros usuarios usen esta herramienta dentro de sus propias ofertas.
Para algunos desarrolladores, este enfoque gráfico hace que la creación de aplicaciones sea más rápida y sencilla. También hace que la lógica principal de la aplicación sea más visible. Al proporcionar una imagen sencilla de lo que sucede, el diseñador de flujos de trabajo de WF puede ayudar a los desarrolladores a comprender más rápidamente la estructura de una aplicación. Esto puede ser especialmente útil para las personas que deben mantener las aplicaciones implementadas, ya que aprender una nueva aplicación lo suficientemente bien como para cambiarla puede ser un proceso lento.
¿Pero qué ocurre con las actividades personalizadas? ¿Todavía no es necesario escribir código? La respuesta es sí y, por tanto, WF también permite usar Visual Studio para crear actividades personalizadas en C#, Visual Basic y otros lenguajes. Para comprender cómo funciona esto, es importante comprender cómo representa el diseñador WF las distintas partes de un flujo de trabajo. En la figura 11 se muestra cómo se hace normalmente esto.
Figura 11: El estado y el flujo de control de un flujo de trabajo se describen normalmente en XAML, mientras que las actividades personalizadas se pueden escribir en código.
La composición de un flujo de trabajo de WF (las actividades que contiene y cómo están relacionadas esas actividades) normalmente se representa mediante el lenguaje de marcado de aplicaciones (XAML) eXtensible. Como se muestra en la figura 11, XAML proporciona una manera basada en XML para describir el estado del flujo de trabajo como variables y expresar las relaciones entre las actividades del flujo de trabajo. Aquí, por ejemplo, el XAML de la secuencia de actividad de flujo de trabajo más externa contiene una actividad ReceiveMessage seguida de una actividad If, como cabría esperar.
Esta actividad If contiene las actividades personalizadas X e Y. En lugar de crearse en XAML, estas actividades se escriben como clases de C#. Aunque es posible crear algunas actividades personalizadas únicamente en XAML, la lógica más especializada normalmente se escribe directamente en el código. De hecho, aunque no es el enfoque habitual, un desarrollador también es libre de escribir flujos de trabajo completamente en el código, el uso de XAML no es estrictamente necesario.
Uso de Windows Workflow Foundation: algunos escenarios
Comprender la mecánica de WF es una parte esencial de comprender el flujo de trabajo. Sin embargo, no es suficiente: también debe comprender cómo se pueden usar los flujos de trabajo en las aplicaciones. En consecuencia, en esta sección se examina cómo y por qué se puede usar WF en algunas situaciones típicas.
Creación de un servicio de flujo de trabajo
La creación de lógica de negocios como servicio a menudo tiene sentido. Supongamos, por ejemplo, que se debe tener acceso al mismo conjunto de funcionalidades desde un explorador a través de ASP.NET, desde un cliente de Silverlight y desde una aplicación de escritorio independiente. Implementar esta lógica como un conjunto de operaciones que se pueden invocar desde cualquiera de estos clientes, es decir, como servicio, es probable que sea el mejor enfoque. La exposición de la lógica como servicio también hace que sea accesible para otra lógica, lo que a veces puede facilitar la integración de aplicaciones. (Esta es la idea principal detrás de la noción de arquitectura orientada a servicios, SOA).
Para los desarrolladores de .NET en la actualidad, la tecnología insignia para crear servicios es Windows Communication Foundation (WCF). Entre otras cosas, WCF permite a los desarrolladores implementar lógica de negocios accesible mediante REST, SOAP y otros estilos de comunicación. Y para algunos servicios, WCF puede ser todo lo que se necesita. Si va a implementar un servicio en el que cada operación sea independiente, por ejemplo, se puede llamar a cualquier operación en cualquier momento, sin necesidad de ordenación, compilar esos servicios como servicios WCF sin procesar es correcto. El creador de un servicio cuyas operaciones solo exponen los datos podría ser capaz de salir con solo usar WCF, por ejemplo.
Sin embargo, para situaciones más complejas, en las que las operaciones de un servicio implementan un conjunto relacionado de funcionalidad, WCF solo podría no ser suficiente. Piense en las aplicaciones que permiten a los clientes reservar reservas de vuelos o hacer compras en línea o llevar a cabo algún otro proceso empresarial. En casos como estos, puede optar por usar WF para implementar la lógica del servicio. Esta combinación incluso tiene un nombre: la lógica implementada mediante WF y expuesta a través de WCF se conoce como servicio de flujo de trabajo. La figura 12 ilustra la idea.
Figura 12: Un servicio de flujo de trabajo usa WCF para exponer la lógica basada en WF.
Como se muestra en la ilustración, WCF permite exponer uno o varios puntos de conexión que los clientes pueden invocar a través de SOAP, REST o algo más. Cuando el cliente llama a la operación inicial en este servicio de ejemplo, la solicitud se controla mediante la primera actividad ReceiveMessage del flujo de trabajo. Una actividad If aparece a continuación y cuál de sus actividades personalizadas contenidas se ejecuta depende del contenido de la solicitud del cliente. Una vez completada la instrucción If, se devuelve una respuesta a través de SendMessage. La segunda solicitud del cliente, invocando otra operación, se controla de forma similar: Se acepta mediante receiveMessage, procesada por la lógica del flujo de trabajo y luego se responde mediante un SendMessage.
¿Por qué crear lógica de negocios orientada a servicios de esta manera es una buena idea? La respuesta es obvia: permite crear una aplicación unificada y escalable. En lugar de requerir que todas las operaciones contengan comprobaciones, ¿es legal invocarme ahora mismo?, este conocimiento se inserta en la propia lógica de flujo de trabajo. Esto hace que la aplicación sea más fácil de escribir y, al igual que importante, más fácil de entender para las personas que finalmente deben mantenerla. Y en lugar de escribir su propio código para controlar la escalabilidad y la administración de estado, el entorno de ejecución de WF hace estas cosas automáticamente.
Un servicio de flujo de trabajo también obtiene todas las ventajas descritas anteriormente, al igual que cualquier otra aplicación WF. Estos incluyen lo siguiente:
- La implementación de servicios que realizan trabajos paralelos es sencilla: simplemente coloque las actividades en una actividad paralela.
- El tiempo de ejecución proporciona el seguimiento.
- Según el dominio del problema, puede ser posible crear actividades personalizadas reutilizables para su uso en otros servicios.
- El flujo de trabajo se puede crear gráficamente, con la lógica de proceso directamente visible en el diseñador de flujos de trabajo de WF.
El uso de WF y WCF juntos como este, la creación de servicios de flujo de trabajo, no era tan fácil en versiones anteriores de WF. Con .NET Framework 4, esta combinación de tecnología se combina de forma más natural. El objetivo es crear lógica de negocios en este estilo lo más sencillo posible.
Ejecución de un servicio de flujo de trabajo con "Dublín"
Todavía no se ha tratado un problema importante con los flujos de trabajo: ¿Dónde se ejecutan? El entorno de ejecución de WF es una clase, tal como son las actividades. Todas estas cosas deben ejecutarse en algún proceso de host, pero ¿qué proceso es esto?
La respuesta es que los flujos de trabajo de WF se pueden ejecutar en prácticamente cualquier proceso. Puede crear su propio host, incluso reemplazando algunos de los servicios básicos de WF (como la persistencia) si lo desea. Muchas organizaciones han hecho esto, especialmente los proveedores de software. Sin embargo, la creación de un proceso de host realmente funcional para WF, completo con funcionalidades de administración, no es lo más sencillo del mundo. Y para las organizaciones que quieren gastar su lógica de negocios de creación de dinero en lugar de la infraestructura, evitar este esfuerzo tiene sentido.
Una opción más sencilla es hospedar un flujo de trabajo de WF en un proceso de trabajo proporcionado por Internet Information Server (IIS). Aunque esto funciona, solo proporciona una solución sin huesos. Para facilitar la vida a los desarrolladores de WF, Microsoft proporciona un código de tecnología denominado "Dublín". Implementado como extensiones para IIS y el Servicio de activación de procesos de Windows (WAS), un objetivo principal de "Dublín" es hacer que IIS y WAS sean más atractivos como host para los servicios de flujo de trabajo. En la figura 13 se muestra el aspecto de un servicio de flujo de trabajo cuando se ejecuta en un entorno de "Dublín".
Figura 13: "Dublín" proporciona administración y mucho más para los servicios de flujo de trabajo.
Aunque WF incluye mecanismos para conservar el estado, el seguimiento y mucho más de un flujo de trabajo, solo proporciona los conceptos básicos. "Dublín" se basa en la compatibilidad intrínseca de WF para ofrecer un entorno más totalmente útil y manejable. Por ejemplo, junto con un almacén de persistencia basado en SQL Server y un almacén de seguimiento, "Dublin" proporciona una herramienta de administración para trabajar con estos almacenes. Esta herramienta, implementada como extensiones para el Administrador de IIS, permite a sus usuarios examinar y administrar (por ejemplo, finalizar) flujos de trabajo persistentes, controlar cuánto seguimiento se realiza, examinar registros de seguimiento, etc.
Junto con sus adiciones a la funcionalidad existente de WF, "Dublin" también agrega otros servicios. Por ejemplo, "Dublín" puede supervisar las instancias de flujo de trabajo en ejecución, reiniciando automáticamente cualquier error. El objetivo principal de Microsoft con "Dublín" es claro: proporcionar un conjunto útil de herramientas e infraestructura para administrar y supervisar los servicios de flujo de trabajo hospedados en IIS/WAS.
Uso de un servicio de flujo de trabajo en una aplicación de ASP.NET
Probablemente sea justo decir que la mayoría de las aplicaciones de .NET usan ASP.NET. Convertir el flujo de trabajo en la forma estándar, significa hacer que WF sea una opción atractiva para ASP.NET desarrolladores.
En versiones anteriores de WF, el rendimiento del flujo de trabajo no era lo suficientemente bueno para un número significativo de aplicaciones de ASP.NET. Sin embargo, para la versión de .NET Framework 4, los diseñadores de WF han rediseñado partes importantes de la tecnología, lo que aumenta significativamente el rendimiento. Esta versión, junto con la llegada de "Dublín", hace que las aplicaciones basadas en WF (especialmente los servicios de flujo de trabajo) sean una opción más viable para ASP.NET desarrolladores. En la figura 14 se muestra una imagen sencilla de cómo se ve esto.
Figura 14: La lógica de negocios de una aplicación ASP.NET se puede implementar como un servicio de flujo de trabajo
En este escenario, ASP.NET páginas implementan solo la interfaz de usuario de la aplicación. Su lógica se implementa completamente en un servicio de flujo de trabajo. Para el servicio de flujo de trabajo, la aplicación ASP.NET es solo otro cliente, mientras que en la aplicación ASP.NET, el servicio de flujo de trabajo es similar a cualquier otro servicio. No es necesario tener en cuenta que este servicio se implementa mediante WF y WCF.
Una vez más, sin embargo, la gran pregunta es, ¿Por qué lo harías? ¿Por qué no solo escribir la lógica de ASP.NET aplicación de la manera habitual? ¿Qué hace el uso de WF (y WCF) usted? En este punto, la mayoría de las respuestas a esas preguntas son probablemente obvias, pero todavía merece la pena repetir:
- En lugar de distribuir la lógica de la aplicación en muchas páginas de ASP.NET diferentes, esa lógica se puede implementar en un único flujo de trabajo unificado. Especialmente para los grandes sitios, esto puede facilitar considerablemente la compilación y el mantenimiento de la aplicación.
- En lugar de trabajar explícitamente con el estado en la aplicación de ASP.NET, quizás mediante el objeto Session o algo más, el desarrollador puede confiar en el entorno de ejecución de WF para hacerlo. La aplicación ASP.NET solo necesita realizar un seguimiento de un identificador de instancia para cada instancia de flujo de trabajo (lo más probable es que uno por usuario), como almacenarlo en una cookie. Cuando la aplicación proporciona este identificador a "Dublín", la instancia de flujo de trabajo se volverá a cargar automáticamente. A continuación, comienza a ejecutarse donde se dejó, con todo su estado restaurado.
- También se aplican las otras ventajas de WF, incluido el paralelismo más sencillo, el seguimiento integrado, la posibilidad de actividades reutilizables y la capacidad de visualizar la lógica de la aplicación.
Dado que un servicio de flujo de trabajo no está vinculado a un proceso específico en una máquina específica, puede equilibrar la carga en varias instancias de "Dublín". En la figura 15 se muestra un ejemplo de cómo podría tener este aspecto.
Figura 15: Una aplicación de ASP.NET replicada puede usar varias instancias de "Dublín" para ejecutar un servicio de flujo de trabajo.
En este escenario sencillo, las páginas de una aplicación de ASP.NET se replican en tres máquinas de servidor IIS. Aunque estas páginas controlan la interacción con los usuarios, la lógica de negocios de la aplicación se implementa como un servicio de flujo de trabajo que se ejecuta con "Dublín". Se ejecutan dos instancias del entorno "Dublín", cada una en su propia máquina. Cuando se incluye la primera solicitud de un usuario, un equilibrador de carga lo enruta a la instancia superior de IIS. La página ASP.NET que controla esta solicitud llama a una operación en el servicio de flujo de trabajo que implementa este fragmento de lógica de negocios. Esto hace que un flujo de trabajo de WF empiece a ejecutarse en la instancia de "Dublín" en la máquina superior de la figura. Una vez completadas las actividades pertinentes de este flujo de trabajo, la llamada vuelve a la página ASP.NET y el flujo de trabajo se conserva.
La segunda solicitud del usuario se enruta a una instancia diferente de IIS. La página ASP.NET de esta máquina, a su vez, invoca una operación en el flujo de trabajo (persistente) en una máquina diferente de la que controló su primera solicitud. Esto no supone ningún problema: "Dublín" simplemente carga la instancia de flujo de trabajo desde el almacén de persistencia que comparte con su compañero servidor. A continuación, este servicio de flujo de trabajo recargado controla la solicitud del usuario y recoge dónde se ha dejado.
Comprender WF (y WCF) y, a continuación, aprender a crear lógica en este nuevo estilo toma algo de trabajo. Para aplicaciones de ASP.NET sencillas, escalar esta curva de aprendizaje podría no merecer la pena el esfuerzo necesario. Sin embargo, cualquier persona que cree aplicaciones web más grandes con .NET debe considerar al menos la posibilidad de crear su lógica como servicio de flujo de trabajo.
Uso de flujos de trabajo en aplicaciones cliente
El enfoque hasta ahora se ha centrado completamente en el uso de WF para crear aplicaciones de servidor. Sin embargo, aunque esta es la forma en que la tecnología se usa con más frecuencia hoy en día, WF también puede ser útil para las aplicaciones cliente. Sus aspectos de escalabilidad no suelen agregar mucho en este caso, ya que el código que se ejecuta en máquinas cliente normalmente no necesita controlar una gran cantidad de usuarios simultáneos, pero WF todavía puede ser de uso.
Por ejemplo, piense en una aplicación cliente que presenta a su usuario una interfaz gráfica creada mediante Windows Forms o Windows Presentation Foundation. Es probable que la aplicación tenga controladores de eventos para procesar los clics del mouse del usuario, quizás extendiendo su lógica de negocios a través de estos controladores de eventos. Esto es muy parecido a una aplicación de ASP.NET que distribuye su lógica en un grupo de páginas independientes y puede crear los mismos desafíos. El flujo de la aplicación podría ser difícil de distinguir, por ejemplo, y es posible que el desarrollador tenga que insertar comprobaciones para asegurarse de que las cosas se realizan en el orden correcto. Al igual que con una aplicación de ASP.NET, la implementación de esta lógica como un flujo de trabajo de WF unificado puede ayudar a abordar estos problemas.
Otros aspectos de WF también pueden ser útiles en el cliente. Supongamos que la aplicación debe invocar varios servicios web back-end en paralelo, por ejemplo, o puede beneficiarse del seguimiento. Al igual que en el servidor, WF puede ayudar a abordar esos desafíos. Aunque la mayoría de las aplicaciones WF se ejecutan actualmente en servidores, es importante reconocer que esta no es la única opción.
Una mirada más detallada: La tecnología de Windows Workflow Foundation
El objetivo de esta información general no es convertirlo en desarrollador de WF. Sin embargo, saber un poco más sobre la tecnología de WF puede ayudar a decidir cuándo tiene sentido elegir el flujo de trabajo. En consecuencia, esta sección examina con más detalle algunas de las partes más importantes de WF.
Tipos de flujos de trabajo
En .NET Framework 4, los desarrolladores de WF suelen elegir entre dos estilos diferentes de flujo de trabajo eligiendo diferentes actividades externas. Estas actividades son:
- Secuencia: ejecuta actividades en secuencia, una después de otra. La secuencia puede contener actividades If, While y otros tipos de flujo de control. No es posible retroceder, pero la ejecución siempre debe avanzar.
- Diagrama de flujo: ejecuta actividades una después de otra, como una secuencia, pero también permite que el control vuelva a un paso anterior. Este enfoque más flexible, nuevo en la versión de .NET Framework 4 de WF, está más cerca de cómo funcionan los procesos reales y de la forma en que la mayoría de nosotros creemos.
Aunque tanto Sequence como Flowchart pueden actuar como actividades más externas en un flujo de trabajo, también se pueden usar dentro de un flujo de trabajo. Esto permite anidar estas actividades compuestas de maneras arbitrarias.
En sus dos primeras versiones, WF también incluía otra opción para la actividad más externa de un flujo de trabajo denominada State Machine. Como su nombre sugiere, esta actividad permite a un desarrollador crear explícitamente una máquina de estado y, a continuación, hacer que los eventos externos desencadenen actividades en esa máquina de estado. WF en .NET Framework 4 es un gran cambio, sin embargo, uno que requería volver a escribir la mayoría de las actividades en las versiones anteriores y crear un nuevo diseñador. Debido al esfuerzo implicado, los creadores de WF no proporcionarán la actividad State Machine a partir de la versión inicial de .NET Framework 4. (Incluso los recursos de Microsoft no están limitados). Sin embargo, la nueva actividad Diagrama de flujo debe abordar muchas de las situaciones que anteriormente requerían el uso de State Machine.
La biblioteca de actividades base
Un flujo de trabajo puede contener cualquier conjunto de actividades que un desarrollador elija usar. Es totalmente legal, por ejemplo, para que un flujo de trabajo contenga nada más que actividades personalizadas. Aún así, esto no es muy probable. La mayoría de las veces, un flujo de trabajo de WF usará al menos algunas de las que se proporcionan en la biblioteca de actividades base. Entre las actividades bal más útiles proporcionadas por WF en .NET Framework 4 son las siguientes:
- Asignar: asigna un valor a una variable del flujo de trabajo.
- Compensación: proporciona una manera de realizar una compensación, como controlar un problema que se produce en una transacción de larga duración.
- DoWhile: ejecuta una actividad y, a continuación, comprueba una condición. La actividad se ejecutará durante y más, siempre y cuando la condición sea verdadera.
- Diagrama de flujo: agrupa un conjunto de actividades que se ejecutan secuencialmente, pero también permite que el control vuelva a un paso anterior.
- ForEach: ejecuta una actividad para cada objeto de una colección.
- If: crea una rama de ejecución.
- Parallel: ejecuta varias actividades al mismo tiempo.
- Persist: solicita explícitamente el tiempo de ejecución de WF para conservar el flujo de trabajo.
- Pick: permite esperar un conjunto de eventos y, a continuación, ejecutar solo la actividad asociada al primer evento que se va a producir.
- ReceiveMessage: recibe un mensaje a través de WCF.
- SendMessage: envía un mensaje a través de WCF.
- Secuencia: agrupa un conjunto de actividades que se ejecutan secuencialmente. Junto con la acción como actividad externa de un flujo de trabajo, Sequence también es útil dentro de los flujos de trabajo. Por ejemplo, una actividad While solo puede contener una otra actividad. Si esa actividad es Sequence, un desarrollador puede ejecutar un número arbitrario de actividades dentro del bucle while.
- Switch: proporciona una rama de ejecución multireccional.
- Throw: genera una excepción.
- TryCatch: permite crear un bloque try/catch para controlar las excepciones.
- While: ejecuta una sola actividad siempre que una condición sea true.
Esta no es una lista completa: WF en .NET Framework 4 incluye más. Además, Microsoft planea hacer que las nuevas actividades de WF estén disponibles en CodePlex, su sitio de hospedaje para proyectos de código abierto. Poco después de la versión de .NET Framework 4, por ejemplo, busque actividades que permitan a los flujos de trabajo emitir consultas y actualizaciones de bases de datos, ejecutar comandos de PowerShell, etc.
Como sugiere esta lista, bal hace eco en gran medida de la funcionalidad de un lenguaje de programación de uso general tradicional. Esto no debería ser sorprendente, ya que WF está pensado para ser una tecnología de uso general. Como se ha descrito en esta introducción, sin embargo, el enfoque que toma (las actividades ejecutadas por el entorno de ejecución de WF) a veces pueden mejorar la vida para los desarrolladores de aplicaciones.
Flujo de trabajo en .NET Framework 4
La versión 4 de .NET Framework aporta cambios significativos en Windows Workflow Foundation. Las actividades de bal se han vuelto a escribir, por ejemplo, y se han agregado algunas nuevas. Esto aporta ventajas reales(muchos flujos de trabajo ahora se ejecutan mucho más rápido, por ejemplo), pero también significa que los flujos de trabajo creados con versiones anteriores de WF no se pueden ejecutar mediante la versión de .NET 4 del entorno de ejecución de WF. Estos flujos de trabajo más antiguos todavía se pueden ejecutar junto con flujos de trabajo de .NET Framework 4 sin cambios, pero no se necesitan desechar. Además, las actividades creadas con versiones anteriores de WF, incluidos los flujos de trabajo completos, se pueden ejecutar potencialmente dentro de una nueva actividad de interoperabilidad que proporciona WF en .NET Framework 4. Esto permite que la lógica de los flujos de trabajo más antiguos se use en otros nuevos.
Junto con un mejor rendimiento, esta nueva versión de WF también aporta otros cambios interesantes. Por ejemplo, las versiones anteriores de WF incluían una actividad de código que podría contener código arbitrario. Esto permite que un desarrollador agregue prácticamente cualquier funcionalidad deseada a un flujo de trabajo, pero era una solución ligeramente inelegante. En .NET Framework 4, los creadores de WF hicieron que la escritura de actividades personalizadas sea significativamente más sencilla y, por tanto, se ha quitado la actividad De código. Ahora se crea una nueva funcionalidad como una actividad personalizada.
Los cambios en la versión de .NET Framework 4 son los más sustanciales desde la apariencia original de WF en 2006. Sin embargo, todos tienen el mismo objetivo: facilitar a los desarrolladores la creación de aplicaciones eficaces mediante flujos de trabajo.
Conclusión
Windows Workflow Foundation ofrece ventajas reales para muchas aplicaciones. En sus primeras versiones, WF golpeó un acorde principalmente con los proveedores de software. Estas encarnaciones originales de la tecnología eran útiles, pero no eran realmente adecuadas para el uso empresarial estándar. Con .NET Framework 4, los creadores de WF buscan cambiar esto.
Al hacer que WF y WCF funcionen bien, mejorando el rendimiento de WF, proporcionando un mejor diseñador de flujos de trabajo y ofreciendo un proceso de host completo con "Dublín", Microsoft hace que el flujo de trabajo sea más atractivo para una gama más amplia de escenarios. Sus días como jugador especializado en el drama de desarrollo de aplicaciones de Windows están dibujando a un cierre. WF está en camino a la etapa central.
Acerca del autor
David Chappell es principal de Chappell & Associates en San Francisco, California. A través de su discurso, escritura y consultoría, ayuda a las personas de todo el mundo a comprender, usar y tomar mejores decisiones sobre la nueva tecnología.