A maneira de fluxo de trabalho: noções básicas sobre o Windows Workflow Foundation
David Chappell
Chappell & Associates
Abril de 2009
Baixe este artigo
Apresentando o Windows Workflow Foundation
Todos que gravam código querem criar um ótimo software. Se esse software for um aplicativo de servidor, parte de ser ótimo é dimensionar bem, lidar com grandes cargas sem consumir muitos recursos. Um ótimo aplicativo também deve ser o mais fácil de entender, tanto para seus criadores quanto para as pessoas que o mantêm.
Alcançar esses dois objetivos não é fácil. Abordagens que ajudam a dimensionar aplicativos tendem a separá-los, dividindo sua lógica em partes separadas que podem ser difíceis de entender. No entanto, escrever uma lógica unificada que reside em um único executável pode tornar o dimensionamento do aplicativo impossível. O que é necessário é uma maneira de manter a lógica do aplicativo unificada, tornando-a mais compreensível, ao mesmo tempo em que permite que o aplicativo seja dimensionado.
Alcançar esse é um objetivo principal do Windows Workflow Foundation (WF). Ao dar suporte à lógica criada usando fluxos de trabalho, o WF fornece uma base para a criação de aplicativos unificados e escalonáveis. Junto com isso, o WF também pode simplificar outros desafios de desenvolvimento, como coordenar o trabalho paralelo, acompanhar a execução de um programa e muito mais.
O WF foi lançado pela primeira vez com o .NET Framework 3.0 em 2006 e, em seguida, atualizado no .NET Framework 3.5. Essas duas primeiras versões foram úteis, especialmente para ISVs (fornecedores de software independentes), mas não se tornaram tecnologias tradicionais para desenvolvedores empresariais. Com a versão do WF que faz parte do .NET Framework 4, seus criadores pretendem alterar isso. Uma das principais metas desta versão mais recente é tornar o WF uma parte padrão do kit de ferramentas de programação para todos os desenvolvedores do .NET.
Como qualquer tecnologia, aplicar o WF requer entender o que é, por que é útil e quando faz sentido usá-la. O objetivo dessa visão geral é deixar essas coisas claras. Você não aprenderá a escrever aplicativos WF, mas verá o que o WF oferece, por que é assim e como ele pode melhorar a vida de um desenvolvedor. Em outras palavras, você começará a entender a maneira de fluxo de trabalho.
O desafio: escrever lógica de aplicativo unificada e escalonável
Uma maneira simples de escrever um programa é criar um aplicativo unificado executado em um único processo em um único computador. A Figura 1 ilustra essa ideia.
Figura 1: a lógica do aplicativo pode ser criada como um todo unificado e, em seguida, executada em um thread específico em um processo em execução em um único computador.
O pseudocódigo simples neste exemplo mostra os tipos de coisas que os aplicativos geralmente fazem:
- Manter o estado, que aqui é representado por uma variável de cadeia de caracteres simples.
- Obtenha a entrada do mundo exterior, como recebendo uma solicitação de um cliente. Um aplicativo simples pode apenas ler do console, enquanto um exemplo mais comum pode receber uma solicitação HTTP de um navegador da Web ou uma mensagem SOAP de outro aplicativo.
- Enviar saída para o mundo exterior. Dependendo de como ele é criado, o aplicativo pode fazer isso por meio de HTTP, uma mensagem SOAP, gravando no console ou de alguma outra forma.
- Forneça caminhos alternativos por meio da lógica usando instruções de fluxo de controle, como if/else e while.
- Trabalhe executando o código apropriado em cada ponto do aplicativo.
Na abordagem unificada mostrada aqui, a lógica do aplicativo passa a vida inteira em execução em um thread dentro de um processo específico em um único computador. Essa abordagem simples tem várias vantagens. Por um lado, a lógica pode ser implementada de forma simples e unificada. Isso ajuda as pessoas que trabalham com o código a compreendê-lo e também torna a ordem permitida de eventos explícita. Na Figura 1, por exemplo, é evidente que a primeira solicitação do cliente deve preceder a segunda– o fluxo de controle do programa exige isso. Quando a segunda solicitação chega, não é necessário verificar se a primeira solicitação já foi tratada, pois não há outro caminho pelo aplicativo.
Outra vantagem dessa abordagem é que trabalhar com o estado do aplicativo é fácil. Esse estado é mantido na memória do processo e, como o processo é executado continuamente até que seu trabalho seja concluído, o estado está sempre disponível: o desenvolvedor acessa apenas variáveis comuns. O que poderia ser mais simples?
Ainda assim, esse estilo de programação básico tem limitações. Quando o aplicativo precisa aguardar a entrada, seja de um usuário no console, de um cliente de serviços Web ou de outra coisa, ele normalmente apenas bloqueará. Tanto o thread quanto o processo que ele está usando serão mantidos até que a entrada chegue, por mais tempo que leve. Como threads e processos são recursos relativamente escassos, os aplicativos que se mantêm em qualquer um quando estão apenas aguardando entrada não são muito bem dimensionados.
Uma abordagem mais escalonável é desligar o aplicativo quando ele estiver aguardando entrada e reiniciá-lo quando essa entrada chegar. Essa abordagem não desperdiça recursos, pois o aplicativo não está segurando um thread ou um processo quando não precisa deles. Isso também permite que o aplicativo seja executado em processos diferentes em diferentes computadores em momentos diferentes. Em vez de ser bloqueado para um único sistema, como na Figura 1, o aplicativo pode ser executado em um dos vários computadores disponíveis. Isso também ajuda na escalabilidade, já que o trabalho pode ser distribuído com mais facilidade em diferentes computadores. A Figura 2 mostra a aparência disso.
Figura 2: a lógica do aplicativo pode ser dividida em partes, todos compartilhando o estado comum, que podem ser executados em computadores diferentes.
Este aplicativo de exemplo contém a mesma lógica de antes, mas agora está dividido em partes separadas. Quando a primeira solicitação do cliente é recebida, a parte apropriada é carregada e executada. Depois que essa solicitação tiver sido tratada e uma resposta enviada de volta, essa parte poderá ser descarregada. Nada precisa permanecer na memória. Quando a segunda solicitação do cliente chega, a parte que a manipula é carregada e executada. Como a Figura 2 mostra, essa parte pode ser executada em um thread diferente em um processo diferente em execução em um computador diferente da primeira parte. Depois de lidar com a solicitação, essa segunda parte também pode ser descarregada, liberando qualquer memória que estivesse usando.
Um exemplo comum de uma tecnologia que funciona dessa forma é ASP.NET. Um desenvolvedor implementa um aplicativo ASP.NET como um conjunto de páginas, cada uma contendo parte da lógica do aplicativo. Quando uma solicitação chega, a página correta é carregada, executada e descarregada novamente.
Um aplicativo criado nesse estilo pode usar recursos de máquina com mais eficiência do que um criado usando a abordagem mais simples mostrada anteriormente e, portanto, ele dimensionará melhor. No entanto, pagamos por essa escalabilidade aprimorada com complexidade. Por um lado, as várias partes do código devem de alguma forma compartilhar o estado, como mostra a Figura 2. Como cada parte é carregada sob demanda, executada e desligada, esse estado deve ser armazenado externamente, como em um banco de dados ou em outro repositório de persistência. Em vez de apenas acessar variáveis comuns, como o cenário mostrado na Figura 1, agora um desenvolvedor deve fazer coisas especiais para adquirir e salvar o estado do aplicativo. No ASP.NET aplicativos, por exemplo, os desenvolvedores podem gravar o estado diretamente em um banco de dados, acessar o objeto Session ou usar alguma outra abordagem.
Outro custo dessa escalabilidade aprimorada é que o código não fornece mais uma exibição unificada da lógica geral do programa. Na versão mostrada na Figura 1, a ordem na qual o programa espera entrada é óbvia– há apenas um caminho possível pelo código. No entanto, com a versão da Figura 2, esse fluxo de controle não é evidente. Na verdade, a parte do código que manipula a segunda solicitação do cliente pode precisar começar verificando se a primeira solicitação já foi feita. Para um aplicativo que implementa qualquer tipo de processo comercial significativo, entender e implementar corretamente o fluxo de controle em várias partes pode ser desafiador.
A situação é a seguinte: escrever aplicativos unificados facilita a vida do desenvolvedor e o código é simples de entender, mas o resultado não é bem dimensionado. A gravação de aplicativos volumosos que compartilham estado externo, como aplicativos ASP.NET, permite escalabilidade, mas dificulta o gerenciamento de estado e perde o fluxo de controle unificado. O que gostaríamos é uma maneira de fazer as duas coisas: escrever uma lógica de negócios escalonável com gerenciamento de estado simples, mas ainda ter uma exibição unificada do fluxo de controle do aplicativo.
Isso é exatamente o que o fluxo de trabalho fornece. A próxima seção mostra como.
A solução: a maneira de fluxo de trabalho
Entender como um aplicativo WF resolve esses problemas (e outros) requer percorrer os conceitos básicos de como o WF funciona. Ao longo do caminho, veremos por que essa tecnologia pode melhorar a vida dos desenvolvedores em um número surpreendentemente grande de casos.
Criando lógica de aplicativo unificado
Um aplicativo baseado em fluxo de trabalho criado usando wf faz os mesmos tipos de coisas que um aplicativo comum: ele mantém o estado, obtém entrada e envia saída para o mundo exterior, fornece fluxo de controle e executa código que executa o trabalho do aplicativo. Em um fluxo de trabalho do WF, no entanto, todas essas coisas são feitas por atividades. A Figura 3 mostra a aparência disso, com a abordagem de código unificada mostrada junto com a comparação.
Figura 3: em um fluxo de trabalho do WF, todo o trabalho de um programa é executado por atividades.
Como mostra a Figura 3, cada fluxo de trabalho tem uma atividade mais externa que contém todas as outras. Aqui, essa atividade mais externa é chamada de Sequência e, como um programa comum, pode ter variáveis que mantêm seu estado. Como Sequência é uma atividade composta, ela também pode conter outras atividades.
Neste exemplo simples, o fluxo de trabalho começa com uma atividade ReceiveMessage que obtém a entrada do mundo exterior. Isso é seguido por uma atividade If, que (sem surpresa) implementa um branch. Se também for uma atividade composta, contendo outras atividades (rotuladas X e Y aqui) que executam o trabalho executado em cada branch. A atividade If é seguida por uma atividade SendMessage que envia alguma saída para o mundo além desse fluxo de trabalho. Outra atividade ReceiveMessage aparece em seguida, que obtém mais entrada e, em seguida, é seguida por uma atividade While. Enquanto contém outra atividade, Z, isso faz o trabalho disso durante o loop. Todo o fluxo de trabalho termina com uma atividade final do SendMessage, enviando o resultado final do programa.
Todas essas atividades correspondem funcionalmente a várias partes de um programa típico, como sugerem as cores correspondentes na Figura 2. Mas, em vez de usar elementos de linguagem internos, como faz um programa tradicional, cada atividade em um fluxo de trabalho do WF é, na verdade, uma classe. A execução do fluxo de trabalho é executada pelo runtime do WF, um componente que sabe como executar atividades. A Figura 4 ilustra essa ideia.
Figura 4: o runtime do WF executa atividades na ordem determinada pelo fluxo de trabalho.
Quando ele começa a executar um fluxo de trabalho, o runtime do WF executa pela primeira vez a atividade mais externa, que neste exemplo é uma sequência. Em seguida, ele executa a primeira atividade dentro dessa, que aqui é ReceiveMessage, seguida pela próxima atividade e assim por diante. Exatamente quais atividades são executadas em qualquer situação específica dependem de qual caminho é tomado por meio do fluxo de trabalho. Por exemplo, obter um tipo de entrada na primeira atividade ReceiveMessage pode fazer com que a atividade If execute a atividade X, enquanto outro tipo de entrada pode fazer com que ela execute a atividade Y. É como qualquer outro programa.
É importante entender que o runtime do WF não sabe nada sobre os internos das atividades que está executando. Ele não pode informar um Se de um ReceiveMessage. A única coisa que ele sabe fazer é executar uma atividade e, em seguida, executar a próxima. No entanto, o runtime pode ver os limites entre as atividades, o que, como veremos, é uma coisa útil.
Um corolário importante disso é que o WF não define nenhuma linguagem específica para descrever fluxos de trabalho— tudo depende das atividades que um desenvolvedor escolhe usar. Para facilitar a vida, o WF inclui uma BAL (Biblioteca de Atividades Base) que fornece um conjunto amplamente útil de atividades. (Todas as atividades de exemplo usadas aqui são extraidas do BAL, na verdade.) Mas os desenvolvedores são livres para criar outras atividades que quiserem. Eles podem até mesmo optar por ignorar o BAL completamente.
Há uma pergunta óbvia aqui: Por que ir para todo esse problema? Criar um programa usando atividades é diferente do que os desenvolvedores estão acostumados, então por que alguém deve se preocupar? Por que não apenas escrever código comum?
A resposta, é claro, é que essa abordagem pode nos ajudar a criar um código melhor. Observe, por exemplo, que a maneira de fluxo de trabalho fornece ao desenvolvedor um fluxo de controle unificado. Assim como no caso simples mostrado na Figura 1, a lógica principal do programa é definida em um fluxo coerente. Isso facilita a compreensão e, como a lógica não é dividida em partes, não há necessidade de verificações extras. O fluxo de trabalho em si expressa o fluxo de controle permitido.
Isso representa metade da nossa meta: criar uma lógica de aplicativo unificada. Mas os aplicativos WF também podem realizar o segundo semestre, criando aplicativos escalonáveis com gerenciamento de estado simples. A próxima seção explica como a maneira de fluxo de trabalho torna isso possível.
Fornecendo escalabilidade
Para ser escalonável, um aplicativo de servidor não pode ser bloqueado em um único processo em um único computador. No entanto, dividir explicitamente a lógica do aplicativo em partes, como em ASP.NET páginas, divide o que deve ser um fluxo de controle unificado. Também força o programador a trabalhar com o estado explicitamente. O que realmente gostaríamos é de ter nossa lógica dividida automaticamente em partes que podem ser executadas em processos diferentes em diferentes computadores. Também gostaríamos de ter o estado do nosso aplicativo gerenciado para nós, portanto, tudo o que temos que fazer é acessar variáveis.
Isso é exatamente o que os fluxos de trabalho fornecem. A Figura 5 mostra os conceitos básicos de como o WF realiza essas coisas.
Figura 5: o runtime do WF descarrega um fluxo de trabalho enquanto aguarda a entrada e carrega-o novamente quando a entrada chega.
Como qualquer aplicativo, um fluxo de trabalho do WF bloqueia a espera de entrada. Na Figura 5, por exemplo, o fluxo de trabalho é bloqueado na segunda atividade ReceiveMessage aguardando a segunda solicitação do cliente (etapa 1). O runtime do WF observa isso e, portanto, armazena o estado do fluxo de trabalho e uma indicação de onde o fluxo de trabalho deve ser retomado em um repositório de persistência (etapa 2). Quando a entrada chega para esse fluxo de trabalho (etapa 3), o runtime do WF localiza seu estado persistente e recarrega o fluxo de trabalho, pegando a execução de onde parou (etapa 4). Tudo isso acontece automaticamente– o desenvolvedor do WF não precisa fazer nada. Como o runtime do WF pode ver o fluxo de trabalho, ele pode lidar com todos esses detalhes propriamente ditos.
Uma vantagem óbvia dessa abordagem é que o fluxo de trabalho não fica na memória bloqueando um thread e usando um processo enquanto aguarda a entrada. Outra vantagem é que um fluxo de trabalho persistente pode potencialmente ser recarregado em um computador diferente daquele em que ele estava sendo executado originalmente. Por isso, diferentes partes do fluxo de trabalho podem acabar em execução em sistemas diferentes, como mostra a Figura 6.
Figura 6: um fluxo de trabalho pode ser executado em threads diferentes, em processos diferentes e em computadores diferentes durante seu tempo de vida.
Neste exemplo, suponha que o fluxo de trabalho manipule a primeira solicitação de cliente em um processo no computador A. Quando o segundo ReceiveMessage faz com que o fluxo de trabalho bloqueie a espera por entrada, o runtime do WF descarregará o estado do fluxo de trabalho em um repositório de persistência, conforme descrito. Quando a segunda solicitação do cliente chega, é possível que esse fluxo de trabalho seja recarregado em um processo no computador B. Em vez de ser bloqueado para um thread específico em um processo específico em um computador específico, um fluxo de trabalho do WF pode ser movido conforme necessário. E o desenvolvedor obtém essa agilidade gratuitamente, que é fornecida automaticamente pelo WF.
Vale ressaltar que o runtime do WF não se importa quanto tempo o fluxo de trabalho precisa aguardar a entrada. Ele pode chegar alguns segundos após a persistência do fluxo de trabalho, alguns minutos depois ou até alguns meses depois. Desde que o repositório de persistência ainda mantenha o estado do fluxo de trabalho, esse fluxo de trabalho poderá ser reiniciado. Isso torna o WF uma tecnologia atraente para a criação de aplicativos que implementam processos de execução longa. Pense em um aplicativo que dá suporte a um processo de contratação, por exemplo, que abrange tudo, desde agendar entrevistas iniciais até integrar um novo funcionário à organização. Esse processo pode durar semanas ou meses e, portanto, gerenciar o estado do aplicativo usando o WF faz sentido. Ainda assim, embora o WF possa ser bastante útil com esse tipo de processo de longa execução, é importante entender que esse não é seu único propósito. Qualquer aplicativo que exija uma lógica unificada e escalonável pode ser um bom candidato para o WF.
Na verdade, dê outra olhada na Figura 6. Não se parece muito com a Figura 2, que mostrou como um aplicativo volumoso (por exemplo, um criado exclusivamente com ASP.NET) atingiu a escalabilidade? E, de fato, a Figura 6 também não tem uma forte semelhança com a Figura 1, que mostrou um aplicativo unificado criado com um fluxo de controle linear? O WF obtém essas duas coisas: o fluxo de controle do aplicativo é expresso de maneira compreensível e unificada e o aplicativo pode ser dimensionado, pois ele não está bloqueado para um único processo em um único computador. Essa é a beleza da maneira de fluxo de trabalho.
E isso não é tudo; o uso do WF também tem outras vantagens. Isso pode facilitar a coordenação do trabalho paralelo, por exemplo, ajudar a acompanhar o progresso de um aplicativo e muito mais. A próxima seção analisa esses aspectos da tecnologia.
Outros benefícios da maneira de fluxo de trabalho
Um fluxo de trabalho do WF consiste em atividades executadas pelo runtime do WF. Embora entender o mundo do WF tenha algum esforço, escrever a lógica do aplicativo nesse estilo facilita uma série de desafios comuns de programação, conforme descrito a seguir.
Coordenando trabalho paralelo
O BAL do WF inclui atividades que correspondem a instruções de linguagem de programação familiares. Por isso, você pode escrever fluxos de trabalho que se comportam muito parecidos com programas comuns. No entanto, a presença do runtime do WF também permite a criação de atividades que fornecem mais do que uma linguagem de programação convencional. Um exemplo importante disso é usar um fluxo de trabalho para facilitar a coordenação do trabalho paralelo.
Não se confunda: o foco aqui não é escrever código paralelo que é executado simultaneamente em um processador de vários núcleos. Em vez disso, pense em um aplicativo que, digamos, precisa chamar dois serviços Web e aguardar os dois resultados antes de continuar. Executar as chamadas em paralelo é claramente mais rápido do que fazê-las sequencialmente, mas escrever código tradicional que faz isso não é simples. E embora o .NET Framework forneça várias abordagens para fazer essas chamadas de forma assíncrona, nenhuma delas é especialmente simples. Esta é outra situação em que a maneira de fluxo de trabalho pode brilhar: o WF facilita isso. A Figura 7 mostra como.
Figura 7: atividades contidas em uma execução de atividade paralela em paralelo.
Essa figura mostra um fluxo de trabalho simples muito parecido com o exemplo anterior. A grande diferença é que agora ele invoca dois serviços Web e aguarda uma resposta de ambos antes de continuar. Para executar essas chamadas em paralelo, o criador do fluxo de trabalho embrulhou ambos os pares de atividades SendMessage e ReceiveMessage dentro de uma atividade Paralela. Parallel é uma parte padrão da Biblioteca de Atividades Base do WF e faz exatamente o que seu nome sugere: executa as atividades que ele contém em paralelo. Em vez de fazer o desenvolvedor lidar com a complexidade de lidar com isso, o runtime do WF e a atividade Paralela fazem o trabalho pesado. Tudo o que a desenvolvedora precisa fazer é organizar as atividades conforme necessário para resolver o problema.
Fornecendo acompanhamento automático
Como o runtime do WF pode ver os limites entre as atividades de um fluxo de trabalho, ele sabe quando cada uma dessas atividades começa e termina. Considerando isso, fornecer o acompanhamento automático da execução do fluxo de trabalho é simples. A Figura 8 ilustra essa ideia.
Figura 8: o runtime do WF pode acompanhar automaticamente a execução de um fluxo de trabalho.
Como este exemplo mostra, o runtime do WF pode gravar um registro da execução de um fluxo de trabalho em um repositório de acompanhamento. Uma opção é registrar atividades em log, com um registro gravado sempre que uma atividade começa e termina a execução. No momento mostrado na figura, por exemplo, o fluxo de trabalho está prestes a começar a executar a atividade If e, portanto, o runtime do WF está gravando um evento indicando isso. Também é possível acompanhar variáveis específicas, ou seja, o estado do fluxo de trabalho, registrando seus valores em vários pontos na execução do fluxo de trabalho.
Assim como acontece com outros aspectos do WF, esse registro automático exige essencialmente nenhum trabalho por parte do desenvolvedor. Ele pode apenas indicar que o acompanhamento deve acontecer, especificando o nível em que ele está interessado, e wf cuida do resto.
Criando atividades personalizadas reutilizáveis
As atividades no BAL do WF fornecem uma variedade de funções úteis. Como já mostrado, por exemplo, esse conjunto interno inclui atividades para fluxo de controle, envio e recebimento de mensagens, trabalho em paralelo e muito mais. Mas a criação de um aplicativo real geralmente exigirá a criação de atividades que executam tarefas específicas a esse aplicativo.
Para tornar isso possível, o WF permite a criação de atividades personalizadas. Por exemplo, as atividades rotuladas X, Y e Z nos fluxos de trabalho mostrados anteriormente são de fato atividades personalizadas, pois a Figura 9 torna explícita.
Figura 9: as atividades personalizadas permitem que um fluxo de trabalho execute tarefas específicas do aplicativo.
As atividades personalizadas podem ser simples, executando apenas uma tarefa ou podem ser atividades compostas que contêm lógica arbitrariamente complexa. E sejam elas simples ou complexas, as atividades personalizadas podem ser usadas de várias maneiras diferentes. Por exemplo, um aplicativo de negócios criado usando o WF pode implementar a lógica específica do aplicativo como uma ou mais atividades personalizadas. Como alternativa, um fornecedor de software que usa o WF pode fornecer um conjunto de atividades personalizadas para facilitar a vida de seus clientes. Por exemplo, o Windows SharePoint Services permite que os desenvolvedores criem aplicativos baseados em WF que interagem com pessoas por meio das listas padrão do SharePoint. Para facilitar isso, o SharePoint inclui atividades personalizadas para gravar informações em uma lista.
As atividades personalizadas podem ser escritas diretamente no código, usando C# ou Visual Basic ou outra linguagem. Eles também podem ser criados combinando atividades existentes, o que permite algumas opções interessantes. Por exemplo, uma organização pode criar atividades personalizadas de nível inferior para seus desenvolvedores mais qualificados e empacotá-las em funções de negócios de nível superior para pessoas menos técnicas usarem. Essas atividades de nível superior podem implementar qualquer lógica de negócios necessária, tudo perfeitamente encapsulado em uma caixa reutilizável.
Outra maneira de pensar sobre isso é exibir um conjunto específico de atividades executadas pelo runtime do WF como um idioma. Se uma organização criar um grupo de atividades personalizadas que podem ser reutilizados para resolver problemas específicos em vários aplicativos, o que eles estão realmente fazendo é criar um tipo de DSL (linguagem específica do domínio). Depois que isso for feito, talvez seja possível que pessoas menos técnicas criem aplicativos WF usando essas partes pré-empacotadas de funcionalidade personalizada. Em vez de escrever um novo código para implementar as funções do aplicativo, um novo software útil pode ser criado apenas com a montagem de atividades existentes. Esse estilo de DSL, definido da maneira de fluxo de trabalho, pode melhorar significativamente a produtividade do desenvolvedor em algumas situações.
Tornando os processos visíveis
Criar aplicativos com uma linguagem de programação tradicional significa escrever código. A criação de aplicativos com WF geralmente não é um nível tão baixo. Em vez disso, pelo menos o fluxo de controle principal de um fluxo de trabalho pode ser montado graficamente. Para permitir isso, o WF inclui um designer de fluxo de trabalho executado dentro do Visual Studio. A Figura 10 mostra um exemplo.
Figura 10: O designer de fluxo de trabalho, em execução no Visual Studio, permite que um desenvolvedor crie um fluxo de trabalho arrastando e soltando atividades.
Como este exemplo mostra, as atividades disponíveis para um desenvolvedor do WF aparecem à esquerda. Para criar um fluxo de trabalho, ela pode montar essas atividades na superfície de design para criar qualquer lógica necessária pelo aplicativo. Se necessário, o designer de fluxo de trabalho do WF também pode ser hospedado novamente em outros ambientes, permitindo que outras pessoas usem essa ferramenta dentro de suas próprias ofertas.
Para alguns desenvolvedores, essa abordagem gráfica torna a criação de aplicativos mais rápida e fácil. Ele também torna a lógica principal do aplicativo mais visível. Ao fornecer uma imagem simples do que está acontecendo, o designer de fluxo de trabalho do WF pode ajudar os desenvolvedores a entender mais rapidamente a estrutura de um aplicativo. Isso pode ser especialmente útil para as pessoas que devem manter aplicativos implantados, pois aprender um novo aplicativo bem o suficiente para alterá-lo pode ser um processo demorado.
Mas e as atividades personalizadas? Ainda não há necessidade de escrever código? A resposta é sim e, portanto, o WF também permite usar o Visual Studio para criar atividades personalizadas em C#, Visual Basic e outros idiomas. Para entender como isso funciona, é importante entender como o designer do WF representa as várias partes de um fluxo de trabalho. A Figura 11 mostra como isso geralmente é feito.
Figura 11: o estado e o fluxo de controle de um fluxo de trabalho normalmente são descritos em XAML, enquanto atividades personalizadas podem ser escritas em código.
A composição de um fluxo de trabalho do WF , as atividades que ele contém e como essas atividades estão relacionadas, normalmente é representada usando a XAML (Linguagem de Marcação de Aplicativo eXtensible). Como mostra a Figura 11, o XAML fornece uma maneira baseada em XML de descrever o estado do fluxo de trabalho como variáveis e expressar as relações entre as atividades do fluxo de trabalho. Aqui, por exemplo, o XAML para a sequência de atividade de fluxo de trabalho mais externa contém uma atividade ReceiveMessage seguida por uma atividade If, exatamente como você esperaria.
Esta atividade Se contiver as atividades personalizadas X e Y. Em vez de serem criadas em XAML, no entanto, essas atividades são escritas como classes C#. Embora seja possível criar algumas atividades personalizadas apenas no XAML, uma lógica mais especializada normalmente é escrita diretamente no código. Na verdade, embora não seja a abordagem usual, um desenvolvedor também é livre para gravar fluxos de trabalho inteiramente no código– o uso de XAML não é estritamente necessário.
Usando o Windows Workflow Foundation: alguns cenários
Entender a mecânica do WF é uma parte essencial da compreensão da maneira de fluxo de trabalho. No entanto, não é suficiente: você também precisa entender como os fluxos de trabalho podem ser usados em aplicativos. Dessa forma, esta seção tem uma visão arquitetônica de como e por que o WF pode ser usado em algumas situações típicas.
Criando um serviço de fluxo de trabalho
Criar lógica de negócios como um serviço geralmente faz sentido. Suponha, por exemplo, que o mesmo conjunto de funcionalidades deve ser acessado de um navegador por meio de ASP.NET, de um cliente Silverlight e de um aplicativo de área de trabalho autônomo. Implementar essa lógica como um conjunto de operações que podem ser invocadas de qualquer um desses clientes, ou seja, como um serviço, provavelmente será a melhor abordagem. Expor a lógica como um serviço também a torna acessível a outras lógicas, o que às vezes pode facilitar a integração de aplicativos. (Essa é a ideia central por trás da noção de SOA, arquitetura orientada a serviços.)
Para desenvolvedores do .NET atualmente, a principal tecnologia para a criação de serviços é o WCF (Windows Communication Foundation). Entre outras coisas, o WCF permite que os desenvolvedores implementem uma lógica de negócios acessível usando REST, SOAP e outros estilos de comunicação. E para alguns serviços, o WCF pode ser tudo o que é necessário. Se você estiver implementando um serviço em que cada operação esteja sozinha, por exemplo, qualquer operação pode ser chamada a qualquer momento, sem pedidos necessários, compilar esses serviços como serviços brutos do WCF é muito bom. O criador de um serviço cujas operações expõem apenas dados pode muito bem ser capaz de se safar apenas usando o WCF, por exemplo.
Para situações mais complexas, no entanto, em que as operações em um serviço implementam um conjunto relacionado de funcionalidade, o WCF sozinho pode não ser suficiente. Pense em aplicativos que permitem que os clientes reservem reservas de voo ou façam compras online ou realizem algum outro processo de negócios. Em casos como esses, você pode muito bem optar por usar o WF para implementar a lógica do serviço. Essa combinação até tem um nome: a lógica implementada usando o WF e exposta por meio do WCF é conhecida como um serviço de fluxo de trabalho. A Figura 12 ilustra a ideia.
Figura 12: um serviço de fluxo de trabalho usa o WCF para expor a lógica baseada em WF.
Como a figura mostra, o WCF permite expor um ou mais pontos de extremidade que os clientes podem invocar por meio de SOAP, REST ou outra coisa. Quando o cliente chama a operação inicial neste serviço de exemplo, a solicitação é tratada pela primeira atividade ReceiveMessage do fluxo de trabalho. Uma atividade If aparece em seguida e qual de suas atividades personalizadas contidas é executada depende do conteúdo da solicitação do cliente. Quando o If é concluído, uma resposta é retornada por meio de SendMessage. A segunda solicitação do cliente, invocando outra operação, é tratada de maneira semelhante: ela é aceita por um ReceiveMessage, processado pela lógica do fluxo de trabalho e, em seguida, respondeu ao uso de um SendMessage.
Por que criar uma lógica de negócios orientada a serviços dessa forma é uma boa ideia? A resposta é óbvia: permite a criação de um aplicativo unificado e escalonável. Em vez de exigir que todas as operações contenham verificações, é legal me invocar agora?— esse conhecimento está inserido na própria lógica de fluxo de trabalho. Isso facilita a gravação do aplicativo e, tão importante quanto, mais fácil de entender para as pessoas que devem, eventualmente, mantê-lo. E em vez de escrever seu próprio código para lidar com a escalabilidade e o gerenciamento de estado, o runtime do WF faz essas coisas para você.
Um serviço de fluxo de trabalho também obtém todos os benefícios descritos anteriormente, assim como qualquer outro aplicativo WF. Estes incluem o seguinte:
- Implementar serviços que fazem trabalho paralelo é simples: basta descartar atividades em uma atividade Paralela.
- O acompanhamento é fornecido pelo runtime.
- Dependendo do domínio do problema, pode ser possível criar atividades personalizadas reutilizáveis para uso em outros serviços.
- O fluxo de trabalho pode ser criado graficamente, com a lógica do processo diretamente visível no designer de fluxo de trabalho do WF.
Usar o WF e o WCF juntos assim , criando serviços de fluxo de trabalho, não foi tão fácil em versões anteriores do WF. Com o .NET Framework 4, essa combinação de tecnologia se reúne de maneira mais natural. O objetivo é tornar a criação da lógica de negócios nesse estilo o mais simples possível.
Executando um serviço de fluxo de trabalho com "Dublin"
Um grande problema com fluxos de trabalho ainda não foi discutido: onde eles são executados? O runtime do WF é uma classe, assim como as atividades. Todas essas coisas devem ser executadas em algum processo de host, mas que processo é esse?
A resposta é que os fluxos de trabalho do WF podem ser executados em praticamente qualquer processo. Você é livre para criar seu próprio host, até mesmo substituindo alguns dos serviços básicos do WF (como persistência), se desejar. Muitas organizações fizeram isso, especialmente fornecedores de software. No entanto, criar um processo de host verdadeiramente funcional para o WF, completo com recursos de gerenciamento, não é a coisa mais simples do mundo. E para organizações que querem gastar seu dinheiro construindo lógica de negócios em vez de infraestrutura, evitar esse esforço faz sentido.
Uma opção mais simples é hospedar um fluxo de trabalho do WF em um processo de trabalho fornecido pelo IIS (Servidor de Informações da Internet). Embora isso funcione, ele fornece apenas uma solução de ossos nus. Para facilitar a vida dos desenvolvedores do WF, a Microsoft está fornecendo uma tecnologia chamada "Dublin". Implementado como extensões para o IIS e o Serviço de Ativação de Processo do Windows (WAS), um objetivo principal de "Dublin" é tornar o IIS e o WAS mais atraentes como host para serviços de fluxo de trabalho. A Figura 13 mostra a aparência de um serviço de fluxo de trabalho quando ele está em execução em um ambiente "Dublin".
Figura 13: "Dublin" fornece gerenciamento e muito mais para serviços de fluxo de trabalho.
Embora o próprio WF inclua mecanismos para persistir o estado, o acompanhamento e muito mais de um fluxo de trabalho, ele fornece apenas as noções básicas. "Dublin" baseia-se no suporte intrínseco do WF para oferecer um ambiente mais totalmente útil e gerenciável. Por exemplo, juntamente com um repositório de persistência baseado em SQL Server e um repositório de acompanhamento, "Dublin" fornece uma ferramenta de gerenciamento para trabalhar com esses repositórios. Essa ferramenta, implementada como extensões para o Gerenciador do IIS, permite que seus usuários examinem e gerenciem fluxos de trabalho persistentes (por exemplo, encerrar), controle a quantidade de acompanhamento feita, examine os logs de acompanhamento e muito mais.
Juntamente com suas adições à funcionalidade existente do WF, "Dublin" também adiciona outros serviços. Por exemplo, "Dublin" pode monitorar a execução de instâncias de fluxo de trabalho, reiniciando automaticamente qualquer um que falhar. A meta principal da Microsoft com "Dublin" é clara: fornecer um conjunto útil de ferramentas e infraestrutura para gerenciar e monitorar serviços de fluxo de trabalho hospedados no IIS/WAS.
Usando um serviço de fluxo de trabalho em um aplicativo ASP.NET
Provavelmente é justo dizer que a maioria dos aplicativos .NET usa ASP.NET. Tornar o fluxo de trabalho mainstream, então, significa tornar o WF uma opção atraente para desenvolvedores ASP.NET.
Em versões anteriores do WF, o desempenho do fluxo de trabalho não era bom o suficiente para um número significativo de aplicativos ASP.NET. Para a versão do .NET Framework 4, no entanto, os designers do WF re-arquitetaram partes importantes da tecnologia, aumentando significativamente o desempenho. Esta versão, juntamente com o advento de "Dublin", torna os aplicativos baseados em WF, particularmente serviços de fluxo de trabalho, uma opção mais viável para desenvolvedores ASP.NET. A Figura 14 mostra uma imagem simples de como isso se parece.
Figura 14: uma lógica de negócios de um aplicativo ASP.NET pode ser implementada como um serviço de fluxo de trabalho
Nesse cenário, ASP.NET páginas implementam apenas a interface do usuário do aplicativo. Sua lógica é implementada inteiramente em um serviço de fluxo de trabalho. Para o serviço de fluxo de trabalho, o aplicativo ASP.NET é apenas outro cliente, enquanto para o aplicativo ASP.NET, o serviço de fluxo de trabalho se parece com qualquer outro serviço. Ele não precisa estar ciente de que esse serviço é implementado usando WF e WCF.
Mais uma vez, porém, a grande questão é: Por que você faria isso? Por que não apenas escrever a lógica do aplicativo ASP.NET da maneira usual? O que o uso do WF (e do WCF) compra para você? A essa altura, a maioria das respostas a essas perguntas provavelmente são óbvias, mas ainda vale a pena reiterar:
- Em vez de espalhar a lógica do aplicativo em muitas páginas de ASP.NET diferentes, essa lógica pode ser implementada em um único fluxo de trabalho unificado. Especialmente para sites grandes, isso pode tornar o aplicativo significativamente mais fácil de criar e manter.
- Em vez de trabalhar explicitamente com o estado no aplicativo ASP.NET, talvez usando o objeto Session ou algo mais, o desenvolvedor pode contar com o runtime do WF para fazer isso. O aplicativo ASP.NET só precisa acompanhar um identificador de instância para cada instância de fluxo de trabalho (provavelmente uma por usuário), como armazená-la em um cookie. Quando o aplicativo fornece esse identificador para "Dublin", a instância de fluxo de trabalho será recarregada automaticamente. Em seguida, ele começa a executar de onde parou, com todo o seu estado restaurado.
- Os outros benefícios do WF também se aplicam, incluindo paralelismo mais fácil, acompanhamento interno, o potencial de atividades reutilizáveis e a capacidade de visualizar a lógica do aplicativo.
Como um serviço de fluxo de trabalho não está vinculado a um processo específico em um computador específico, ele pode ser balanceado por carga em várias instâncias de "Dublin". A Figura 15 mostra um exemplo de como isso pode ser.
Figura 15: um aplicativo ASP.NET replicado pode usar várias instâncias de "Dublin" para executar um serviço de fluxo de trabalho.
Nesse cenário simples, as páginas de um aplicativo ASP.NET são replicadas em três computadores de servidor IIS. Enquanto essas páginas lidam com a interação com os usuários, a lógica de negócios do aplicativo é implementada como um serviço de fluxo de trabalho em execução com "Dublin". Duas instâncias do ambiente "Dublin" estão em execução, cada uma em seu próprio computador. Quando a primeira solicitação de um usuário entra, um balanceador de carga o encaminha para a instância superior do IIS. A página ASP.NET que manipula essa solicitação chama uma operação no serviço de fluxo de trabalho que implementa essa parte da lógica de negócios. Isso faz com que um fluxo de trabalho do WF comece a ser executado na instância "Dublin" no computador superior da figura. Depois que as atividades relevantes neste fluxo de trabalho forem concluídas, a chamada retornará à página ASP.NET e o fluxo de trabalho será mantido.
A segunda solicitação do usuário é roteada para uma instância diferente do IIS. A página ASP.NET neste computador, por sua vez, invoca uma operação no fluxo de trabalho (persistente) em um computador diferente daquele que lidou com sua primeira solicitação. Isso não é problema: "Dublin" carrega apenas a instância de fluxo de trabalho do repositório de persistência que compartilha com seu servidor. Esse serviço de fluxo de trabalho recarregado manipula a solicitação do usuário, retomando de onde parou.
Noções básicas sobre o WF (e o WCF), aprender a criar lógica nesse novo estilo requer algum trabalho. Para aplicativos ASP.NET simples, escalar essa curva de aprendizado pode não valer o esforço necessário. Qualquer pessoa que crie aplicativos Web maiores usando o .NET, no entanto, deve pelo menos considerar a possibilidade de criar sua lógica como um serviço de fluxo de trabalho.
Usando fluxos de trabalho em aplicativos cliente
O foco até agora tem sido inteiramente no uso do WF para criar aplicativos de servidor. No entanto, embora seja assim que a tecnologia é usada com mais frequência hoje em dia, o WF também pode ser útil para aplicativos cliente. Seus aspectos de escalabilidade normalmente não adicionam muito nesse caso, pois o código executado em computadores cliente geralmente não precisa lidar com muitos usuários simultâneos, mas o WF ainda pode ser útil.
Por exemplo, pense em um aplicativo cliente que apresenta ao usuário uma interface gráfica criada usando o Windows Forms ou o Windows Presentation Foundation. O aplicativo provavelmente terá manipuladores de eventos para processar os cliques do mouse do usuário, talvez espalhando sua lógica de negócios entre esses manipuladores de eventos. Isso é muito parecido com um aplicativo ASP.NET espalhando sua lógica em um grupo de páginas separadas e pode criar os mesmos desafios. O fluxo do aplicativo pode ser difícil de discernir, por exemplo, e o desenvolvedor pode precisar inserir verificações para garantir que as coisas sejam feitas na ordem correta. Assim como acontece com um aplicativo ASP.NET, implementar essa lógica como um fluxo de trabalho unificado do WF pode ajudar a resolver essas preocupações.
Outros aspectos do WF também podem ser úteis no cliente. Suponha que o aplicativo precise invocar vários serviços Web de back-end em paralelo, por exemplo, ou pode se beneficiar do acompanhamento. Assim como no servidor, o WF pode ajudar a resolver esses desafios. Embora a maioria dos aplicativos WF atualmente seja executada em servidores, é importante reconhecer que essa não é a única opção.
Uma visão mais detalhada: a tecnologia do Windows Workflow Foundation
O objetivo dessa visão geral não é torná-lo um desenvolvedor do WF. Ainda assim, saber um pouco mais sobre a tecnologia do WF pode ajudar a decidir quando faz sentido escolher a maneira de fluxo de trabalho. Dessa forma, esta seção analisa mais detalhadamente algumas das partes mais importantes do WF.
Tipos de fluxos de trabalho
No .NET Framework 4, os desenvolvedores de WF normalmente escolhem entre dois estilos diferentes de fluxo de trabalho escolhendo diferentes atividades externas. Essas atividades são:
- Sequência: executa atividades em sequência, uma após a outra. A sequência pode conter atividades if, atividades While e outros tipos de fluxo de controle. No entanto, não é possível retroceder. No entanto, a execução deve sempre avançar.
- Fluxograma: executa atividades uma após a outra, como uma sequência, mas também permite que o controle retorne a uma etapa anterior. Essa abordagem mais flexível, nova na versão do .NET Framework 4 do WF, está mais próxima de como os processos reais funcionam e da maneira como a maioria de nós pensa.
Embora sequência e fluxograma possam atuar como atividades mais externas em um fluxo de trabalho, eles também podem ser usados em um fluxo de trabalho. Isso permite que essas atividades compostas sejam aninhadas de maneiras arbitrárias.
Em suas duas primeiras versões, o WF também incluiu outra opção para a atividade mais externa de um fluxo de trabalho chamada State Machine. Como o nome sugere, essa atividade permite que um desenvolvedor crie explicitamente um computador de estado e, em seguida, tenha atividades de gatilho de eventos externos nesse computador de estado. O WF no .NET Framework 4 é uma grande mudança, no entanto, uma que exigiu a redação da maioria das atividades nas versões anteriores e a criação de um novo designer. Devido ao esforço envolvido, os criadores do WF não fornecerão a atividade do State Machine da versão inicial do .NET Framework 4. (Mesmo os recursos da Microsoft não estão sem limite.) Ainda assim, a nova atividade flowchart deve resolver muitas das situações necessárias anteriormente usando o State Machine.
A biblioteca de atividades base
Um fluxo de trabalho pode conter qualquer conjunto de atividades que um desenvolvedor opte por usar. É totalmente legal, por exemplo, que um fluxo de trabalho não contenha nada além de atividades personalizadas. Ainda assim, isso não é muito provável. Na maioria das vezes, um fluxo de trabalho do WF usará pelo menos parte do que é fornecido na Biblioteca de Atividades Base. Entre as atividades bal mais amplamente úteis fornecidas pelo WF no .NET Framework 4 estão as seguintes:
- Atribuir: atribui um valor a uma variável no fluxo de trabalho.
- Compensar: fornece uma maneira de fazer uma compensação, como lidar com um problema que ocorre em uma transação de execução longa.
- DoWhile: executa uma atividade e verifica uma condição. A atividade será executada uma e outra vez, desde que a condição seja verdadeira.
- Fluxograma: agrupa um conjunto de atividades executadas sequencialmente, mas também permite que o controle retorne a uma etapa anterior.
- ForEach: executa uma atividade para cada objeto em uma coleção.
- Se: cria um branch de execução.
- Paralelo: executa várias atividades ao mesmo tempo.
- Persista: solicita explicitamente que o runtime do WF persista o fluxo de trabalho.
- Escolha: permite aguardar um conjunto de eventos e, em seguida, executar apenas a atividade associada ao primeiro evento a ocorrer.
- ReceiveMessage: recebe uma mensagem por meio do WCF.
- SendMessage: envia uma mensagem por meio do WCF.
- Sequência: agrupa um conjunto de atividades executadas sequencialmente. Além de atuar como a atividade mais externa de um fluxo de trabalho, a Sequência também é útil dentro de fluxos de trabalho. Por exemplo, uma atividade While pode conter apenas uma outra atividade. Se essa atividade for Sequence, um desenvolvedor poderá executar um número arbitrário de atividades dentro do loop while.
- Opção: fornece um branch de execução de várias vias.
- Gerar: gera uma exceção.
- TryCatch: permite criar um bloco try/catch para lidar com exceções.
- Enquanto: executa uma única atividade, desde que uma condição seja verdadeira.
Essa não é uma lista completa. O WF no .NET Framework 4 inclui mais. Além disso, a Microsoft planeja disponibilizar novas atividades do WF no CodePlex, seu site de hospedagem para projetos de software livre. Logo após a versão do .NET Framework 4, por exemplo, procure atividades que permitem aos fluxos de trabalho emitir consultas e atualizações de banco de dados, executar comandos do PowerShell e muito mais.
Como esta lista sugere, o BAL ecoa em grande parte a funcionalidade de uma linguagem de programação de uso geral tradicional. Isso não deve ser surpreendente, já que o WF deve ser uma tecnologia de uso geral. Como essa visão geral descreveu, no entanto, a abordagem que ela adota — atividades executadas pelo runtime do WF — às vezes pode melhorar a vida dos desenvolvedores de aplicativos.
Fluxo de trabalho no .NET Framework 4
A versão 4 do .NET Framework traz alterações significativas no Windows Workflow Foundation. As atividades no BAL foram reescritas, por exemplo, e algumas novas foram adicionadas. Isso traz benefícios reais— muitos fluxos de trabalho agora são executados muito mais rapidamente, por exemplo— mas também significa que os fluxos de trabalho criados usando versões anteriores do WF não podem ser executados pela versão do .NET 4 do runtime do WF. Esses fluxos de trabalho mais antigos ainda podem ser executados junto com fluxos de trabalho do .NET Framework 4 inalterados, no entanto, eles não precisam ser jogados fora. Além disso, as atividades criadas usando versões mais antigas do WF, incluindo fluxos de trabalho inteiros, podem potencialmente ser executadas dentro de uma nova atividade de Interoperabilidade fornecida pelo WF no .NET Framework 4. Isso permite que a lógica de fluxos de trabalho mais antigos seja usada em novos.
Juntamente com um melhor desempenho, essa nova versão do WF também traz outras alterações interessantes. Por exemplo, versões anteriores do WF incluíam uma atividade de código que poderia conter código arbitrário. Isso permite que um desenvolvedor adicione praticamente qualquer funcionalidade desejada a um fluxo de trabalho, mas era uma solução ligeiramente deselegante. No .NET Framework 4, os criadores do WF tornaram a gravação de atividades personalizadas significativamente mais simples e, portanto, a atividade de código foi removida. Agora, a nova funcionalidade é criada como uma atividade personalizada.
As alterações na versão do .NET Framework 4 são as mais substanciais desde a aparição original do WF em 2006. No entanto, todos eles têm o mesmo objetivo: facilitar a criação de aplicativos eficazes para desenvolvedores usando fluxos de trabalho.
Conclusão
O Windows Workflow Foundation oferece vantagens reais para muitos aplicativos. Em suas primeiras versões, o WF atingiu um acorde principalmente com fornecedores de software. Essas encarnações originais da tecnologia eram úteis, mas não eram realmente apropriadas para uso empresarial mainstream. Com o .NET Framework 4, os criadores do WF estão procurando mudar isso.
Ao fazer com que o WF e o WCF funcionem bem juntos, melhorando o desempenho do WF, fornecendo um melhor designer de fluxo de trabalho e oferecendo um processo de host completo com "Dublin", a Microsoft está tornando o fluxo de trabalho muito mais atraente para uma gama mais ampla de cenários. Seus dias como um player especializado no drama de desenvolvimento de aplicativos do Windows estão chegando ao fim. O WF está a caminho do centro do palco.
Sobre o autor
David Chappell é diretor da Chappell & Associates em São Francisco, Califórnia. Através de sua fala, escrita e consultoria, ele ajuda as pessoas em todo o mundo a entender, usar e tomar melhores decisões sobre novas tecnologias.