UNIVERSIDADE FEDERAL DO RIO GRANDE DO NORTE CENTRO DE CIÊNCIAS EXATAS E DA TERRA DEPARTAMENTO DE INFORMÁTICA E MATEMÁTICA APLICADA PROGRAMA DE PÓS-GRADUAÇÃO PÓS GRADUAÇÃO EM SISTEMAS E COMPUTAÇÃO MESTRADO ACADÊMICO ACAD EM SISTEMAS E COMPUTAÇÃO Detecção e Recuperação de Falhas para a Máquina de Redução de Grafos PEWS-AM PEWS José Sueney de Lima Natal-RN, Brasil Fevereiro, 2014 José Sueney de Lima Detecção e Recuperação de Falhas para a Máquina de Redução de Grafos PEWS-AM Dissertação de Mestrado apresentada ao Programa de Pós-Graduação em Sistemas e Computação do Departamento de Informática e Matemática Aplicada da Universidade Federal do Rio Grande do Norte, como requisito para a obtenção do grau de Mestre em Ciências da Computação Universidade Federal do Rio Grande do Norte – UFRN Centro de Ciências Exatas e da Terra – CCET Departamento de Informática e Matemática Aplicada – DIMAp Programa de Pós-graduação em Sistemas de Computação – PPgSC Orientador: Umberto Souza da Costa Natal-RN, Brasil Fevereiro, 2014 Dissertação de Mestrado sob o título Detecção e Recuperação de Falhas para a Máquina de Redução de Grafos PEWS-AM apresentada por José Sueney de Lima e aceita pelo Programa de Pós-Graduação em Sistemas e Computação do Departamento de Informática e Matemática Aplicada da Universidade Federal do Rio Grande do Norte, sendo aprovada por todos os membros da banca examinadora abaixo especificada: _____________________________ Umberto Souza da Costa - Doutor Presidente DIMAp – Departamento de Informática e Matemática Aplicada UFRN – Universidade Federal do Rio Grande do Norte _____________________________ Martin Alejandro Musicante - Doutor Examinador DIMAp – Departamento de Informática e Matemática Aplicada UFRN – Universidade Federal do Rio Grande do Norte _____________________________ Regina Maria Motz Carrano - Doutora Examinador URep – Universidad de la República Natal-RN, Brasil, em 28 de fevereiro de 2014. Dedicatória Este trabalho é dedicado a todos os que acreditaram e participaram direta ou indiretamente desta caminhada. Agradecimentos Agradeço primeiramente a Deus por ter me dado a oportunidade de realizar esse trabalho. Aos meus pais por sempre estarem ao meu lado nos momentos difíceis dessa caminhada. Aos meus amigos, que sempre me deram forças e me incentivaram a continuar quando pensava em desistir. E ao meu orientador Umberto, que se mostrou bastante compreensivo e paciente, me guiando durante a realização deste trabalho. Enfim, a todos os outros aqui não citados, quero agradecer de coração. Resumo Serviços Web são unidades de software que permitem o acesso a um ou mais recursos, dando suporte à implantação de processos de negócios na Web. Eles permitem a interação através de interfaces bem-definidas, utilizando-se de protocolos padrões da Web, tornando possível a comunicação entre entidades implementadas em diferentes tipos de plataformas. Em virtude dessas características, Serviços Web podem ser integrados com o objetivo de formar aplicações mais robustas, com baixo acoplamento entre serviços, através de composições. Serviços Web estão sujeitos a falhas, situações indesejadas que podem comprometer o processo de negócio parcialmente ou mesmo totalmente. Falhas podem ocorrer tanto na concepção de composições quanto na execução das mesmas. Em virtude disso, é essencial a criação de mecanismos que tornem a execução das composições de serviços mais robusta e tratem falhas. Especificamente, propomos o suporte à recuperação de falhas em composições de serviços descritas na linguagem PEWS e executadas sobre PEWS-AM, uma implementação criada a partir da noção de grafos. Para o suporte à recuperação de falhas em PEWS-AM, estendemos as especificações PEWS e adaptamos as regras de tradução e redução de grafos desta máquina. Estas contribuições foram realizadas tanto no modelo da máquina abstrata quanto no nível da implementação. Palavras-chaves: Serviços Web, Falhas, Recuperação de Falhas, PEWS Abstract Web services are software units that allow access to one or more resources, supporting the deployment of business processes in the Web. They use well-defined interfaces, using web standard protocols, making possible the communication between entities implemented on different platforms. Due to these features, Web services can be integrated as services compositions to form more robust loose coupling applications. Web services are subject to failures, unwanted situations that may compromise the business process partially or completely. Failures can occur both in the design of compositions as in the execution of compositions. As a result, it is essential to create mechanisms to make the implementation of service compositions more robust and to treat failures. Specifically, we propose the support for fault recovery in service compositions described in PEWS language and executed on PEWS-AM, an graph reduction machine. To support recovery failure on PEWS-AM, we extend the PEWS language specification and adapted the rules of translation and reduction of graphs for this machine. These contributions were made both in the model of abstract machine as at the implementation level. Key-Words: Web Services, Failures, Failure Recovery, PEWS Lista de ilustrações Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura Figura 1 – 2 – 3 – 4 – 5 – 6 – 7 – 8 – 9 – 10 – 11 – 12 – 13 – 14 – 15 – 16 – 17 – 18 – 19 – 20 – 21 – 22 – 23 – 24 – 25 – 26 – 27 – 28 – 29 – 30 – 31 – 32 – 33 – 34 – 35 – Padrão de Interação entre entidades da arquitetura de Serviços Web . Pilha de protocolos de Serviços Web [20] . . . . . . . . . . . . . . . . ¯ Ō) para grafos . . . . . . . . . . . . . . . Tradução da operação S(I, Tradução da operação P 1 || P 2 para grafos . . . . . . . . . . . . . . . Tradução da operação P 1 ; P 2 para grafos . . . . . . . . . . . . . . . Tradução de uma escolha não-determinística para grafos . . . . . . . Tradução de uma estrutura condicional simples para grafos . . . . . . Tradução de uma estrutura de repetição para grafos . . . . . . . . . . Transformação de entrada e saída de serviços . . . . . . . . . . . . . Transformação de uma escolha não-determinística . . . . . . . . . . . Transformação de uma estrutura de repetição . . . . . . . . . . . . . Demonstração do uso do Retry . . . . . . . . . . . . . . . . . . . . . Demonstração do uso do Rebinding . . . . . . . . . . . . . . . . . . . Rebinding - Geração de Vértices e Arestas . . . . . . . . . . . . . . . Rebinding - Diminuição do número de vértices . . . . . . . . . . . . . Rebinding - Diminuição do número de arestas . . . . . . . . . . . . . Demonstração do uso do Restructure . . . . . . . . . . . . . . . . . . Exemplo de composição de serviços com caminhos alternativos . . . . Aplicação do Restructure . . . . . . . . . . . . . . . . . . . . . . . . Classes TInt e TString . . . . . . . . . . . . . . . . . . . . . . . . . . Hierarquia de classes do pacote pews.grafos . . . . . . . . . . . . . . a) Formação do grafo sem o Sync; b) Formação do grafo com o Sync; Hierarquia de classes do pacote pews.grafos na Máquina Estendida . Exemplo de final de subcomposição . . . . . . . . . . . . . . . . . . . Exemplo aninhamento de blocos . . . . . . . . . . . . . . . . . . . . . Exemplo de saída do arquivo texto com aninhamento de + . . . . . . Forma de Impressão dos vértices . . . . . . . . . . . . . . . . . . . . Figuras geradas pela execução de uma composição . . . . . . . . . . . Lista de operações dos Serviços Web . . . . . . . . . . . . . . . . . . Resposta gerada pelo Serviço RespostaUsuarioFigura . . . . . . . . . Geração da imagem da composição pelo GraphViz . . . . . . . . . . . Exemplo de entrada e saída de um código no GraphViz . . . . . . . . Exemplo de inserção de atributos para vértices e arestas . . . . . . . Exemplo de ligação entre vértices normais e vértices de clusters . . . Exemplo de ligação entre vértices e clusters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 23 38 39 40 41 41 42 44 46 46 49 54 55 56 56 60 62 63 68 70 72 73 77 80 81 86 88 90 90 92 113 114 115 115 Sumário 1 INTRODUÇÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 1.1 Objetivos e Contribuições . . . . . . . . . . . . . . . . . . . . . . . . . . 18 1.2 Estrutura do Documento . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2 FUNDAMENTAÇÃO TEÓRICA . . . . . . . . . . . . . . . . . . . . 21 2.1 Service-Oriented Computing . . . . . . . . . . . . . . . . . . . . . . . . 21 2.1.1 Conceitos Básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.1.2 Pilha de Protocolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.2 Falhas em Serviços Web . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.2.1 Classificação de Falhas . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.2.2 Recuperação de Falhas . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 2.3 PEWS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 2.3.1 A Linguagem de Composição . . . . . . . . . . . . . . . . . . . . . . . . 33 2.3.2 Máquina de Redução de Grafos . . . . . . . . . . . . . . . . . . . . . . . 36 2.3.2.1 Regras de Tradução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.3.2.2 Regras de Redução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 3 RECUPERAÇÃO DE FALHAS EM PEWS-AM . . . . . . . . . . . . 47 3.1 Classificação de Falhas . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.2 Reinvocação do Serviço . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 3.2.1 Alterações para suporte ao Retry . . . . . . . . . . . . . . . . . . . . . . 49 3.2.2 Alterações na Máquina de Redução de Grafos . . . . . . . . . . . . . . . 50 3.3 Substituição do Serviço . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 3.3.1 Alterações da gramática para o suporte ao Rebinding . . . . . . . . . . 54 3.3.2 Alterações da Máquina de Redução de Grafos . . . . . . . . . . . . . . . 55 3.4 Reorganização do Processo . . . . . . . . . . . . . . . . . . . . . . . . . 60 3.4.1 Alterações da gramática para o suporte ao Restructure . . . . . . . . . 61 3.4.2 Alterações na Máquina de Redução de Grafos . . . . . . . . . . . . . . . 62 4 IMPLEMENTAÇÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.1 A Máquina de Redução Original . . . . . . . . . . . . . . . . . . . . . . 67 4.2 A Máquina de Redução Estendida . . . . . . . . . . . . . . . . . . . . . 71 4.3 Otimizações realizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 4.3.1 Geração de um arquivo texto . . . . . . . . . . . . . . . . . . . . . . . . 79 4.3.2 Correção na redução de vértices de blocos . . . . . . . . . . . . . . . . . 79 4.3.3 Inserção do tipo STRING . . . . . . . . . . . . . . . . . . . . . . . . . . 81 4.3.4 Mudança de semântica de Unfolding e Unfold . . . . . . . . . . . . . . . 81 4.4 4.4.1 Vizualização de Transformações e Reduções . . . . . . . . . . . . . . . . 83 Uso do GraphViz na Máquina de Redução Estendida . . . . . . . . . . . 83 5 5.1 5.2 5.3 5.4 5.4.1 5.4.2 5.4.3 5.4.4 5.5 EXPERIMENTAÇÃO . . . . . . . . . Descrição do Estudo de Caso . . . . . . Especificação da Composição em PEWS Casos de Teste . . . . . . . . . . . . . . Resultados obtidos . . . . . . . . . . . . Caso 1 . . . . . . . . . . . . . . . . . . Caso 2 . . . . . . . . . . . . . . . . . . Caso 3 . . . . . . . . . . . . . . . . . . Caso 4 . . . . . . . . . . . . . . . . . . Conclusão dos Experimentos . . . . . . 6 CONCLUSÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 89 90 92 94 94 95 96 99 100 Referências . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 APÊNDICE A – Código do arquivo Pews.jacc . . . . . . . . . . . . 111 APÊNDICE B – Uso do GraphViz em PEWS-AM . . . . . . . . . . 113 APÊNDICE C – Atributos e métodos das classes da Máquina Estendida PEWS-AM . . . . . . . . . . . . . . . . . 117 APÊNDICE D – Tabela de Revisão Bibliográfica dos Trabalhos . . 123 17 1 INTRODUÇÃO Nos dias atuais, a infraestrutura oferecida pela Web faz com que muitas organizações a adotem como meio para publicar seus serviços, com o objetivo de dar suporte a seus processos de negócio. Elas buscam velocidade de desenvolvimento, interoperabilidade, escalabilidade, reuso e facilidade de acesso. Estes princípios definem o paradigma SOC (Service-Oriented Computing) [26]. A unidade básica em SOC são os serviços, que representam aplicações fornecidas pelos provedores e permitem a composição de processos de negócio pela descoberta e invocação através da rede. Serviços podem ter suas fucionalidades integradas, formando aplicações através de composições de serviços. SOC possibilita a construção de aplicações robustas e o suporte à heterogeneidade dos serviços, uma vez que a comunicação entre eles independe da plataforma ou tecnologia nas quais estes foram construídos. Serviços Web são a mais promissora implementação de SOC [20], sendo estes unidades de software que fornecem um conjunto de operações através de interfaces bem definidas, sendo acessíveis através de protocolos padrões da Web. Serviços Web têm por objetivo permitir a interoperabilidade e ter uma representação uniforme entre os serviços presentes na Web. O modelo de Serviços Web propõe o suporte a interações de forma sistemática e utiliza-se de modelos de infra-estrutura existente [10]. Um dos grandes desafios na utilização dos Serviços Web é a integração de métodos de gerenciamento para suporte a falhas [37]. Aplicações devem garantir a confiabilidade na execução, devendo apresentar mecanismos que garantam a eficácia do processo de negócio e possíveis soluções para ocorrência de falhas. O tratamento dessas falhas não é algo trivial, visto o ambiente dinâmico em que são implantados os serviços. A dinamicidade dos Serviços Web faz com que eles possam trabalhar durante intervalos de tempo longos ou simplesmente tornem-se indisponíveis. Falhas podem ocorrer em diversos momentos da execução de uma composição, tornando inviável a adoção de ações humanas para detecção e tratamento. Diversos são os motivos que podem causar uma falha em um determinado Serviço Web, como queda no serviço, passagem de dados errônea, entre outros. Portanto, são necessários meios providos pelo designer de uma composição de serviços que garantam a confiabilidade e a segurança do processo. Muitos trabalhos [35], [17], [41], [43], [44], [5] propõem diferentes formas para abordar falhas em composições de serviços. Algumas dessas abordagens são: verificação através de monitores específicos que detectam anormalidades na execução de serviços; criação e alimentação de um banco que contém padrões de erros que, ao serem detectados, acusam uma falha e disparam manipuladores de exceções para esses padrões; tentativas de 18 Capítulo 1. INTRODUÇÃO reconexão com o mesmo serviço em caso de falha ou troca deste por um outro equivalente; criação de execuções alternativas para substituir trechos da composição que contém falhas, entre outros. A maioria das abordagens criam modelos teóricos de tratamento de falhas, sem a especificação de protótipos. Uma das vantagens do nosso trabalho é a adoção e aplicação de uma abordagem num protótipo real de um sistema de execução de Serviços Web. 1.1 Objetivos e Contribuições Propomos neste trabalho a implementação de mecanismos de recuperação de falhas, implementados como uma extensão de um sistema de execução de composições de serviços, especificadas na linguagem de composições PEWS. O sistema de execução descrito corresponde a uma máquina de redução de grafos, chamada PEWS-AM [8]. Na versão estendida da máquina de redução de grafos, o designer da composição pode optar pela execução da máquina no formato antigo (sem recuperação de falhas) ou utilizá-la com os mecanismos implementados. Sendo este o objetivo principal, utilizaremos a seguinte metodologia: 1. Extensão da linguagem PEWS para a especificação dos métodos de recuperação de falhas; 2. Implementação dos mecanismos de recuperação de falhas na máquina de redução PEWS-AM, permitindo a futura inserção de outros mecanismos de detecção e recuperação; 3. Integração da máquina de redução de grafos com o Graphiz [13], um software de vizualização de grafos abstratos, capaz de representar informações estruturadas, a fim de gerar uma representação gráfica da execução das composições de serviços; 4. Revisão bibliográfica sobre as abordagens utilizadas para o tratamento de falhas em Serviços Web por diversos trabalhos; O trabalho realizado tem as seguintes contribuições: levantamento e análise de mecanismos de falhas aplicados a Serviços Web; extensão da linguagem PEWS para permitir ao designer da composição especificar ações de correção; suporte à recuperação de falhas durante a execução de Serviços Web; geração de modelos gráficos do fluxo de execução de composições de serviços. 1.2. Estrutura do Documento 19 1.2 Estrutura do Documento Este documento está organizado da seguinte maneira: no Capítulo 2, apresentaremos os principais conceitos relacionados a SOC, falhas em serviços, a linguagem PEWS e a máquina de redução de grafos proposta em [8]; no Capítulo 3, apresentaremos os mecanismos de recuperação de falhas aplicados a PEWS, dando uma visão geral da proposta e mostrando separadamente cada abordagem a ser implementada; no Capítulo 4, daremos foco à implementação propriamente dita, mostrando o ambiente, as ferramentas utilizadas, além das alterações feitas na máquina original para o suporte aos mecanismos propostos; no Capítulo 5, propomos um estudo de caso para o teste da implementação, onde vários cenários de falha são executados; por fim, no Capítulo 6, fazemos a conclusão do trabalho, mostrando quais metas foram alcançadas, as limitações do trabalho e possíveis extensões a serem realizadas. 21 2 FUNDAMENTAÇÃO TEÓRICA Neste capítulo, apresentaremos a fundamentação do nosso trabalho. Na primeira parte, faremos uma revisão sobre a Computação Orientada a Serviços, mostrando os principais conceitos relacionados a esse paradigma. Em seguida, apresentaremos o conceito de falhas e as classificações comumente encontradas na literatura no contexto de Serviços Web. Por fim, apresentaremos a linguagem de composição PEWS e suas características principais. Abordaremos, também, as máquinas de redução de grafos, dando ênfase à máquina de redução PEWS-AM. 2.1 Service-Oriented Computing A Computação Orientada a Serviços (do inglês Service Oriented Computing) propõe o desenvolvimento de aplicações massivamente distribuídas, interoperáveis e de baixo custo. Ela traz como vantagens o fraco acoplamento entre serviços, desenvolvimento baseado em padrões, integração de aplicações, disponibilização de recursos sem a necessidade do conhecimento da implementação do serviço, entre outras [25]. Segundo este paradigma, as aplicações podem ser desenvolvidas utilizando unidades de software de diferentes provedores, potencialmente executadas sobre plataformas heterogêneas. 2.1.1 Conceitos Básicos Para que existam formas de consolidar as pesquisas e esforços atuais em SOC, é necessário o conhecimento sobre alguns dos principais pontos deste paradigma [27]: fundamentos, composições de serviços, gestão e monitoramento de serviços. A entidade básica do paradigma SOC é o serviço. Este é um módulo de software implantando em plataformas de redes acessíveis pela Web, que pode ser invocado através da rede. Eles podem desempenhar funções que vão desde responder pedidos simples a executar processos de negócio sofisticados, sendo autônomos e de baixo acoplamento [27]. Os Serviços Web são módulos de software que possuem interfaces que descrevem uma coleção de operações [20] e permitem implantar e dar acesso a funções de negócio através da Web, sendo a implementação mais bem sucedida de SOC. A descrição de um serviço define o conjunto de operações disponibilizadas por sua interface (aspecto que esconde os detalhes de implementação). Esta interface é definida em XML e oculta detalhes de implementação, permitindo sua utilização de forma independente em relação ao hardware ou à plataforma de software. A descrição do serviço deve ser fornecida pelo provedor, sendo esta essencial para o solicitante desse serviço. A intera- 22 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA ção entre o provedor e o utilizador de um serviço pode ser intermediada por um registro de serviços. A Figura 1 apresenta o diagrama de interação entre estas três entidades. Figura 1 – Padrão de Interação entre entidades da arquitetura de Serviços Web O provedor de serviço é a entidade que disponibiliza o Serviço na Web, ou seja, o proprietário responsável pela descrição do serviço. O solicitante de serviço é o usuário que deseja utilizar determinado serviço presente na rede, ou seja, a entidade que deseja invocar um serviço e iniciar uma interação. Já o registro de serviços é um repositório que é usado para pesquisa de serviços, onde provedores publicam as descrições de seus serviços e solicitantes fazem uma busca para obter informações disponíveis no registro (descrição e informações de comunicação). O registro de serviços não é uma entidade obrigatória na arquitetura, visto que solicitantes podem já conhecer todas as informações referentes ao provedor, não necessitando da consulta ao registro para conseguir o vínculo. As operações de interação entre as três entidades citadas acima são: publish, realizada entre o provedor e o registro, onde o primeiro publica informações do seu serviço em um determinado repositório, sendo o UDDI [23] o tipo de diretório de serviços mais utilizado; discovey, que é realizada entre o requisitante e o registro, onde o primeiro faz uma busca pelas descrições dos serviços no registro através de sua descrição WSDL [23]; e binding, realizada entre um solicitante e um provedor, onde o primeiro solicita o vínculo com o serviço para o acesso às suas funcionalidades, sendo SOAP [23] o protocolo característico dessa operação. Segundo [20], o ciclo de vida dos Serviços Web é constituído pelas seguintes fases: construção, que inclui o desenvolvimento e teste, além da definição da interface e da implementação do serviço; implantação, que inclui a publicação do serviço em um ambiente de execução; início, fase responsável pela ocorrência da disponibilidade para operações de vínculo, estando já totalmente implantado e operacional; e gerenciamento, que inclui a administração do aplicativo de Serviço Web, cobrindo características como: segurança, disponibilidade, desempenho, etc. 2.1.2 Pilha de Protocolos A implementação do padrão de interação entre as entidades participantes do desenvolvimento orientado a serviços se baseia em uma pilha de protocolos. Esta pilha é 2.1. Service-Oriented Computing 23 estruturada em camadas, onde as camadas mais baixas dão suporte às camadas superiores. Numa visão geral sobre a arquitetura de Serviços Web, podemos observar a pilha conceitual [20] e os detalhes existentes em sua estrutura de acordo com a figura 2. Figura 2 – Pilha de protocolos de Serviços Web [20] Camada de Rede Serviços precisam ser disponibilizados na rede para que possam ser invocados por seus solicitantes, e esta funcionalidade é dada pela primeira camada da pilha de protocolos (rede). O protocolo HTTP (Hypertext Transfer Protocol) [11] é o mais utilizado para essa comunicação. Podem ser utilizados critérios (desempenho, disponibilidade, etc.) para escolher a tecnologia, permitindo que os Serviços Web aproveitem a infra-estrutura existente de alto nível de redes, sendo isso transparente ao desenvolvedor do serviço. Outros protocolos podem ser utilizados no nível de rede, com o FTP (File Transfer Protocol) [30], SMTP (Simple Mail Transfer Protocol) [29], entre outros, em virtude de que a escolha do protocolo dependerá de requisitos da aplicação. Camada de Mensagens A camada de mensagens é responsável por fornecer meios para troca de mensagens através da linguagem XML e do protocolo SOAP [6], sendo o mesmo simples, padronizado para documentos em mensagens XML, e por dar suporte a operações feitas entre as entidades dos Serviços Web, além da troca de mensagens em ambientes distribuídos. Segundo [20], SOAP pode ser usado em combinação com uma variedade de protocolos de rede (HTTP, SMTP, FTP, etc). Descrição de Serviços A camada de descrição de serviço é responsável por descrever documentos utilizando WSDL, definindo a interface e mecanismos de interação. A descrição do serviço é 24 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA a chave para tornar a arquitetura de Serviços Web de baixo acoplamento, reduzindo a quantidade de compartilhamento necessário e a programação personalizada. A descrição do serviço, combinada com a infra-estrutura SOAP, encapsula detalhes da comunicação entre o solicitante e o provedor de serviço [20]. Segundo [10], WSDL e SOAP são a base dos Serviços Web, pois resolvem os problemas básicos subjacentes ao desenvolvimento dos mesmos; dessa forma, podemos concluir que as três primeiras camadas garantem um nível de operabilidade básico para os Serviços Web. Publicação de Serviços A camada de publicação de serviços é responsável por tornar um serviço visível na rede. Uma descrição de serviço pode ser publicada usando uma variedade de mecanismos. Um deles é a publicação direta, onde o provedor de serviços envia a descrição do serviço diretamente ao solicitante do serviço, ocorrendo após dois parceiros de negócios concordarem com os termos de realização de negócios eletrônicos através da Web. Outra forma é a publicação dinâmica, onde os documentos WSDL são enviados para um repositório e podem ser recuperados pelos solicitantes. A principal tecnologia utilizada é o UDDI (Universal Description, Discovery and Integration) [23], que permite localizar, descrever e registrar serviços, amplamente utilizada no compartilhamento de informações pelos usuários. Descoberta de Serviços A camada de descoberta de serviço tem sua função dependente da camada anterior. Solicitantes de serviços podem encontrar Serviços Web durante duas fases diferentes do ciclo de vida de uma aplicação – tempo de design e tempo de execução. Qualquer mecanismo que permita acesso à descrição do serviço, tornando-o disponível para o aplicativo em tempo de execução, pode ser considerado um método de descoberta. Bons mecanismos de descoberta precisam suportar consultas que encontrem interfaces através de vários parâmetros, como: tipo (com base num modelo WSDL), informações de binding (protocolos), taxonomia do serviço, informações de negócios, entre outros [20]. Composição de Serviços Por fim, a camada de composição de serviço descreve como são realizadas as comunicações entre serviços. Nessa camada, existe uma variedade de linguagens que podem ser usadas para descrever a interação entre serviços. Entre as linguagens de composição de serviços, destacamos WSCI (Web Service Choreography Interface) [3], BPML (Bussiness Process Modeling Language) [33], BPEL (Bussiness Process Execution Language) [18] e PEWS (Path Expressions for Web Services) [4]. A linguagem de composição PEWS será 2.1. Service-Oriented Computing 25 apresentada em detalhes em uma próxima seção deste capítulo, dada sua importância no contexto do trabalho proposto. Serviços Web podem ser combinados em processos de alto nível através das composições de serviços. Nesse sentido, é necessário que haja um tipo de interação entre os serviços para que eles realizem o processo de negócio. Os dois padrões de interação mais utilizados atualmente são os padrões de orquestração e coreografia de serviços. A orquestração é um tipo de modelagem de composição de serviços em que existe um processo central que comanda todas as ações de comunicação, sendo chamado de processo de negócio executável (orquestrador) [28]. O processo central é responsável por comandar toda a lógica de negócio e a ordem de execução das tarefas. As orquestrações devem ser dinâmicas, flexíveis e adaptáveis, a fim de atender à lógica de negócios. Por outro lado, a coreografia é uma modelagem que descreve uma colaboração entre uma coleção de serviços com um objetivo comum, acompanhando tipicamente sequências de mensagens que ocorrem entre os Serviços Web, sendo um processo mais colaborativo [28]. Em uma coreografia, não há um processo central que controle toda a lógica de negócio, ou seja, é um tipo de interação distribuída e descentralizada, onde cada serviço sabe como agir na ocorrência de determinados eventos, pois o conhecimento é espalhado pelo sistema. Coreografias também são chamadas de processos de negócios abstratos. Camadas Transversais Existem três camadas transversais presentes na pilha de Serviços Web: Qualidade de Serviço, Gerenciamento e Segurança. Essas características devem estar presentes em todas as camadas horizontais, desde a camada de rede até a camada de fluxo de serviços, em virtude das necessidades impostas pela adaptação dos Serviços Web na integração com a internet [1]. A qualidade de serviço se preocupa com a forma como o serviço é provido ao solicitante, buscando, através da verificação de requisitos não-funcionais, responder às necessidades dos clientes. Alguns desses requisitos são: acessibilidade, desempenho, disponibilidade e custo. A segurança desperta uma atenção especial, principalmente quando relacionada a um meio tão dinâmico quanto a Web. É necessário garantir a integridade e privacidade das mensagens enviadas, bem como a autenticidade das transações realizadas entre solicitantes e provedores. O gerenciamento de Serviços Web, segundo [1], tem a função de fornecer informações acerca do estado da infraestutura dos Serviços Web, sendo capaz de controlar e configurar todos os componentes em todas as camadas da arquitetura, sendo dividida em duas áreas distintas: gestão de infraestrutura (controle da infraestrutura, desde mecanismos que disponibilizam a descrição de um serviço até os que gerem o tráfego de mensagens na camada de transporte) e gestão dos Serviços Web (controle do desempenho, disponibilidade, consistência, eventos e métricas que disponibilizam informações aos solicitantes, 26 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA possibilitando escolher os melhores provedores de serviços). 2.2 Falhas em Serviços Web Serviços Web apresentam-se hoje como uma solução para suprir as necessidades de processos de negócio via Web. Uma das causas da atratividade dos Serviços Web é a sua dinamicidade, entretanto, este é um fato que também pode gerar a ocorrência de falhas. Falhas são manifestações que se originam de um ou mais erros e fazem com que um serviço apresente um comportamento diferente do padrão esperado [21]. Em Serviços Web, falhas podem ocorrer em diversas fases do desenvolvimento de aplicações, desde o design até o tempo de execução. Considerando a pilha de protocolos, falhas podem se originar desde a camada de rede até a camada de composição de serviços. 2.2.1 Classificação de Falhas Em [32], é feita uma revisão sobre os motivos de falhas em composições de Serviços Web, divididas em duas categorias, relacionadas a serviços: falhas funcionais e falhas não-funcionais. As falhas funcionais envolvem problemas como mal funcionamento do serviço (erros ocorridos no próprio serviço, ou seja, devido ao seu processamento interno errôneo), indisponibilidade do serviço, problemas de compatibilidade de software (formatos de entrada e saídas diferentes), mudança de contextos (mudança de dispositivo de acesso, por exemplo), etc. As falhas não-funcionais são causadas por time-outs de respostas, atraso da rede, entradas e tipos de dados inesperados, etc. Já em [37], uma outra classificação é dada a falhas ocorridas em Serviços Web, onde as mesmas são divididas em oito categorias. Apesar de existirem mais categorizações de falhas, utilizaremos a classificação apresentada neste último trabalho como principal referencial nesta proposta, por julgar sua abordagem mais especifica. A seguir, apresentamos um resumo das categorias de falhas consideradas em [37]: • Disponibilidade – Usuários podem encontrar falhas na conexão com o serviço. Duas causas podem ser consideradas para este tipo de falha: (i) o serviço chamado não existe; ou (ii) o servidor de aplicação não responde à requisição do requerente. No primeiro caso, poderá ocorrer um erro de service not found, devido ao serviço não existir. Esse erro pode ter ocorrido devido à digitação errada da URL do serviço pelo usuário, ou pelo fato do provedor ter mudado o endereço de acesso ao serviço, dentre inúmeras outras falhas. Já no segundo caso, um erro de time-out pode ocorrer por uma indisponibilidade do serviço, podendo o mesmo estar sobrecarregado em virtude de um alto número de acessos; 2.2. Falhas em Serviços Web 27 • Concorrência – Serviços disponíveis para acessos na rede podem ter diferentes níveis de escalabilidade. Um serviço pode ser utilizado por mais de um cliente, em qualquer momento, e isto pode causar problemas de simultaneidade. Outro tipo de cenário é quando um serviço é usado como um recurso que precisa ser adquirido. • Dependência– Serviços atômicos são independentes de outros serviços, entretanto, ao serem colocados para trabalhar em conjunto em uma composição, criam uma relação de dependência em relação ao processo de negócio. Isso torna a execução de determinados serviços dependentes, por exemplo, da saída de outros serviços. Esse fato pode gerar problemas já na formação da composição, quando, por exemplo, um designer não faz uma análise detalhada das entradas e saídas dos serviços, ligando serviços que possuem tipos incompatíveis. Isso pode tornar a composição inoperável, ou até mesmo errônea, no caso em que isso não seja detectado e o serviço realize o processamento com os dados errados; • Inconsistência – Um provedor de serviços pode decidir alterar os descritores de alguns dos seus serviços. Essas mudanças podem afetar a forma de acesso. Se o descritor é alterado durante o tempo de execução, um cliente que já está usando o serviço pode obter resultados inesperados devido ao novo descritor. Essa alteração também pode fazer um serviço se comportar de forma diferente do que é proposto a fazer; • Composição – Esse tipo de falha pode ocorrer no design da composição. Durante a concepção de uma composição, serviços que oferecem informações incompatíveis entre si são forçados a trabalhar juntos; • Parciais – Esse tipo de falha pode ocorrer quando, por exemplo, durante a execução paralela de vários serviços, cuja sincronização é feita no final da execução destes, um dos serviços não responde, sendo que a composição é feita de forma a considerar, após determinado tempo, apenas as respostas dos serviços finalizados. Apesar da composição ter sido finalizada, os dados recebidos estão incompletos, o que pode gerar falhas na intepretação dos dados; • Outras falhas – Essa última categoria engloba outras falhas que não podem ser categorizadas com a classificação acima. Podemos citar: falhas de QoS (tempo de resposta alto, custo alto, etc.) , falhas de não-cumprimento de contrato de serviços (serviços desrespeitam restrições que estavam designadas no contrato entre o requisitante e o provedor), etc; 2.2.2 Recuperação de Falhas Segundo [42] e [39], existem dois métodos possíveis de recuperação de falhas: o método backward e o método forward. O método backward tem o objetivo de trazer a 28 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA execução a um estado de correção anterior à ocorrência da falha, onde podemos citar os métodos transacionais [37]. Já o método forward tenta levar a execução para um estado seguro e confiável a partir do ponto onde ocorreu o erro, onde encontramos as redes de auto-cura [39]. Métodos transacionais seguem a propriedade característica de transações, que é a propriedade ACID [37], cujas características são especificadas abaixo: • Atomicidade – Tem a idéia de indivisibilidade, ou seja, a execução é única, não podendo ser dividida em partes; • Consistência – A execução cumpre todas as regras invariantes do sistema sem a violação das mesmas; • Isolamento – A execução atual não interfere em outra execução; • Durabilidade – Operações realizadas não podem ser desfeitas, mesmo quando há falha depois da confirmação da execução; Transações podem ser classificadas como rasas ou aninhadas. Uma transação rasa é uma transação normal que irá ser concluída apenas quando o objetivo principal for atingido, seguindo estritamente as características ACID. Em composições de Serviços Web, seguir as características ACID não é uma tarefa fácil. Por exemplo, uma falha na execução pode comprometer o processo de negócio de forma que todo o processo anterior tenha que ser desfeito. Isso viola a característica da durabilidade, uma vez que esta não permite “desfazer” operações. Em outro caso, considerando que o princípio da atomicidade refere-se à composição inteira, e não a um serviço individual, a execução só será válida se todos os serviços cumprirem suas tarefas. Por isso, as transações aninhadas podem obter melhores resultados no âmbito de Serviços Web. Elas tem a característica de dividir a transação completa (neste caso, a composição) em sub-transações. Neste caso, a atomicidade é relacionada com serviços presentes na composição, não estando a execução correta associada apenas ao término da execução da composição inteira. Apesar disso, de acordo com [39], abordagens transacionais não são adequadas para a composição de Serviços Web por duas razões principais: (i) gerenciar transações ao longo de um sistema distribuído não é uma tarefa fácil, e (ii) abordagens baseadas em transações geralmente envolvem bloqueio de recursos, não sendo viáveis em um ambiente de Serviços Web. Em um contexto de Serviços Web, redes de auto-cura são capazes de se recuperar de falhas decorrentes de serviços individuais e também da composição através de alguma heurística que monitora o comportamento do sistema e realiza ações, com o objetivo de levá-lo a um estado em que o fluxo de execução possa continuar. Interações ponto-aponto entre clientes e provedores a cada execução tornam o ambiente bastante dinâmico e propício à ocorrência de diversos tipos de falhas. Nesse contexto, são utilizados algoritmos 2.2. Falhas em Serviços Web 29 de detecção de falhas, que podem utilizar dois tipos de estratégias: detecção estática e detecção dinâmica de erros. Na detecção estática, erros devem ser identificados antes da execução. Em [24], é proposta uma análise automatizada que se utiliza de redes de Petri [22]. Entretanto, diversos erros não podem ser detectados usando essa estratégia, como erros de disponibilidade (não é possível prever se um serviço acionado na composição estará disponível durante a execução) e erros de QoS (serviços podem não cumprir o que foi prometido durante a fase de construção dos contratos). Já a estratégia de detecção dinâmica permite que falhas sejam detectadas durante a execução da composição. Uma proposta foi dada em [44], onde restrições de QoS foram usadas como forma de garantir a estabilidade ao compor Serviços Web. Entretanto, esta estratégia parece estar mais preocupada com a qualidade não-funcional do serviço, e não com a realização do fluxo de execução propriamente. Observamos que as características das redes de auto-cura são mais adaptáveis no âmbito de Serviços Web do que os métodos transacionais. Muitos trabalhos [35], [17], [41], [43], [44], [5] foram realizados para tentar lidar com a ocorrência de falhas sobre Serviços Web. Para um melhor entendimento deste trabalho, é importante que tenhamos um conhecimento prévio sobre o estado da arte de mecanismos de recuperação de falhas em Serviços Web. Uma breve revisão sobre os trabalhos realizados na área é mostrada a seguir. Em [35], é realizada uma melhoria em um framework que se propõe a tratar da recuperação de falhas de negócios através do monitoramento das orquestrações expressas em BPEL [18]. Os desenvolvedores ficam responsáveis por criar as orquestrações em BPEL, dar um conjunto de pré e pós-condições para invocações de serviços (contratos de serviços), e especificar um conjunto de propriedades de regularidade (invariantes representadas por interações requeridas e proibidas entre serviços). Um mecanismo de compensação é então acrescentado ao programa BPEL para a criação de planos de recuperação, um conjunto de passos para execução de novas atividades que dê ao sistema a possibilidade de tratar erros e retornar a um estado de processamento esperado pelo cliente. No pré-processamento, o programa BPEL é enriquecido com as informações de compensação, criadas a partir dos contratos e das propriedades de regularidade. Quando a orquestração é executada, monitores de serviços são executados em paralelo com a aplicação do usuário, onde estes monitores podem parar a execução da aplicação caso detectem algum erro, vindo da violação dos contratos ou do não-cumprimento das propriedades de regularidade. A partir do atual estado de erro, são retornados planos de recuperação. No caso de haver mais de um plano possível de recuperação para um determinado estado, o framework dá a possibilidade do usuário escolher qual plano será utilizado de acordo com algum critério (por exemplo, o plano mais curto, o plano de menor custo, etc.). O trabalho realizado em [17] propõe uma abordagem de auto-cura construída com 30 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA um conjunto de módulos dedicados que fazem parte de uma interface de controle, garantindo o monitoramento do comportamento do Serviço Web, a captura e a recuperação de erros. Dois passos são identificados nessa abordagem: (i) Modelagem do Serviço Web usando dois comportamentos, chamados comportamento operacional (ilustra a lógica de negócios que sustenta o funcionamento de um Serviço Web) e de comportamento de controle (expõe as ações a serem executadas, juntamente com as suas devidas restrições), e (ii) Monitoramento da execução de um Serviço Web usando a interface de controle que fica entre os dois comportamentos citados anteriormente. Os módulos presentes na interface de controle contemplam interpretação, monitoramento, resolução e adaptação, sendo eles, respectivamente: MAM (Mapping Module), um repositório de dados e schemas XML que resulta do mapeamento entre os comportamentos de controle e operacional; CMM (Conversation Management Module), responsável por instanciar, gerenciar e verificar as mensagens de conversação que são trocadas entre os dois comportamentos; ERM (Error Recovery Module), receptor de alertas de erros submetidos pelo CMM e também responsável por tomar ações de correção; e TMM (Transition Management Module), responsável por armazenar as transições dos comportamentos (transição de um estado para o outro no mesmo comportamento) e informações sobre as operações de negócios (restrições, descrição funcional, implementação e execução). Quando o ERM recebe um alerta, ele recupera o estado de execução do MAM, bem como os detalhes da mensagem de falha, indicando que existe um problema de execução reportado pelo CMM. Dessa forma, o CMM sincroniza com o MAM e o TMM para recuperar informações relacionadas ao cenário atual e as operações no estado de controle que são afetadas por este erro. O ERM consulta a sua base de padrões comparando a mensagem recebida com padrões “normais” e padrões “errôneos”, de modo que o aspecto relacionado com este erro é detectado. Se o padrão já está no banco de dados, o ERM consulta sua base para ver se um erro semelhante já foi tratado e resolvido. Em caso afirmativo, o ERM envia uma solução para o CMM e TMM para implantação. Caso contrário, o ERM seleciona o aspecto associado. Em seguida, envia a solução para o módulo de TMM aplicá-lo. No caso em que o padrão de erro não exista, o ERM acrescenta esse novo padrão para a base de padrões e envia um alerta para o desenvolvedor do Serviço Web, pedindo a sua atribuição (novo padrão) a um ou muitos aspectos. Após a aplicação da solução, o ERM atualiza os seus casos bases. Em [41] e [43] são discutidas técnicas de recuperação baseadas em OWL-S (Ontology Web Language). Em [43] são propostos três novos mecanismos semânticos: ReplaceBy-Equivalent (troca de um serviço por outro equivalente, utilizando mecanismos do OWL-S), Advanced Back & Forward Recovery (mecanismo que realiza um roolback das ações realizadas, onde cada ação representa um “filho” e a ação anterior representa um “pai”) e Automatic Compensation (desfaz o efeito de ações realizadas anteriormente). Já em [41], é apresentada uma abordagem para especificação de manipulação de exceções e 2.2. Falhas em Serviços Web 31 recuperação de Serviços Web semânticos baseados em OWL-S. A ideia da abordagem é fornecer mecanismos que permitam ao designer descrever situações que possam levar a um estado errôneo. Dois tipos de restrições são definidas: hard constraints (violações de restrições, ou seja, não-cumprimento de invariantes que podem afetar a eficácia do processo de negócio) e soft constraints (eventos que não necessariamente afetam a eficácia, mas necessitam ser tratados para o bom funcionamento do processo). Para o tratamento de soft constraints, são usados os eventHandlers, entidades criadas para tratar determinadas ocorrências, sem a necessidade da terminação do processo que o gerou. Já para o tratamento de hard constraints, são criados os CV-handlers (manipuladores de violação de restrição, do inglês constraint violation handlers), que associam violações de condições a ações de tratamento apropriadas, finalizando o processo no qual a restrição foi desrespeitada. Um exemplo simples é mostrado abaixo [43]: 1. CompositeProcess(BuyItem) 2. CV-Handler(BuyItemStarted + 1min) { 3. compensate; retry(3); 4. } 5. } Neste exemplo, um processo CompositeProcess tem um CV-handler que é disparado quando uma determinada ação demora mais de um minuto a responder, a partir de sua chamada (BuyItemStarted + 1min). A partir daí, duas ações de recuperação podem ser tomadas (compensate() e retry(3)). O framework visa fornecer bases sólidas para uma rápida recuperação, tendo pretensões futuras de introduzir políticas de recuperação, capazes de fornecer regras genéricas que facilitam o processo de recuperação e também regras específicas de contexto. Em [44] é proposto um algoritmo para produzir uma solução ótima que permite adaptação dinâmica para processos de negócios baseado em três propriedades: delay, custo e benefício. Processos de negócios são construídos com a utilização de grafos, onde cada vértice representa um serviço, arestas representam a ordem de execução e cada serviço possui diferentes níveis de provimento (características diferentes das propriedades, como por exemplo, menor delay e maior custo). São propostos dois algoritmos para resolução de falhas: o primeiro usa um caminho de backup para que o predecessor de um serviço que falhou possa escolher um outro caminho no grafo e continuar o processo de negócio; o segundo utiliza um método de substituição de caminho para reconstruir um novo processo. Para a construção do grafo com os possíveis caminhos do processo de negócio, são utilizadas algumas notações: uma classe de serviços (S ), representando uma coleção de Serviços Web individuais (s) com uma funcionalidade comum e diferentes propriedades 32 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA não-funcionais; um nível de provimento do serviço (l), que contém características das propriedades delay, custo e benefício, representadas por e(s,l), c(s,l) e b(s,l); e uma restrição em relação ao delay total do processo (R). O primeiro algoritmo é usado na seguinte situação: no caso de haver um processo em execução, caminhos alternativos devem ser utilizados caso o caminho ótimo esteja indisponível. O segundo algoritmo é utilizado em outra situação: para novas instâncias de processos, os caminhos que levam a uma situação de falha devem ser retirados do grafo. Caso processos de negócios anteriores tenham sido executados e foram detectados caminhos que levavam a uma falha, esses caminhos devem ser excluídos. Em [5], são propostas duas formas para recuperação de falhas em composição de Serviços Web, chamadas de DPD (Defensive Design Project) e SRTM (Service RunTime Monitoring). DPD é uma estratégia que consiste em criar o fluxo do processo de tal modo a dar-lhe possibilidades de se recuperar de certos tipos de falhas que são muito comuns em tempo de execução. Determinadas falhas podem ser facilmente tratadas com o uso de um processo de design inteligente . A aplicação desta técnica por ser exemplificada com uma falha comum em sistemas dinâmicos: a ocorrência de time-outs. De acordo com o tipo de falha, podemos realizar um projeto de design que possar tomar diferentes atitudes: • Quedas de serviços tendem, algumas vezes, a ser momentâneas. Se isso realmente aconteceu, uma nova solicitação pode ser tentada e a comunicação pode ser estabelecida entre o solicitante e o provedor; • Mudanças de nome são difíceis de serem detectadas. Portanto, consideramos que o serviço não mais existe e podemos tentar a comunicação com outro serviço que possa participar da composição sem prejuízos ao solicitante. Já SRTM utiliza um monitor externo, que deve ser capaz de verificar o cumprimento das características funcionais e não-funcionais do contrato (especificação dos deveres dos serviços na composição) estabelecido entre os solicitantes e os provedores. Os autores investigam como monitorar composições de serviços dinâmicos através dos contratos, e propõem duas implementações de monitoramento: a primeira oferece todos os recursos de uma linguagem de programação, onde é explorada uma linguagem orientada a objetos; a segunda está mais preocupada com com a especificação e oferece construções, a fim de especificar asserções (dados ou informações tidas como verdadeiras). Três tipos de falhas são utilizadas como base para o estudo: time-outs, erros externos e violação de contratos. Por fim, o método descreve três métodos que podem ser úteis para realização a recuperação de um sistema. Os três métodos propostos para recuperação de falhas são: Reinvocação de Serviços (Retry), Substituição de serviço (Rebinding) e Reorganização do Processo (Reestructure). 2.3. PEWS 33 No Apêndice D deste documento, apresentamos uma tabela que contém um resumo sobre os trabalhos levantados e alguns critérios de diferenciação entre os mesmos. Apesar de serem parte da revisão bibliográfica deste documento, os trabalhos [32] e [37] assumem um caráter mais classificatório de falhas e suas características não se encaixam nos itens definidos pela tabela. Dessa forma, os mesmos foram omitidos do apêndice. 2.3 PEWS Interfaces de Serviços Web são geralmente escritas usando WSDL, que tem a capacidade de especificar serviços estaticamente. Entretanto, quando desejamos atribuir determinadas características a um conjunto de serviços (por exemplo, sequência de execução, interações entre serviços, manipulação de dados entre serviços), necessitamos de outros formalismos que dêem suporte a esses fatores [4]. Em [4], foi proposta uma linguagem de composição de serviços baseada em Predicate Path Expressions [2], sendo a mesma denominada PEWS (Path Expression for Web Services). Esta linguagem pode complementar linguagens de descrição de interfaces de serviços (tais como WSDL), sendo usada não só na especificação de Serviços Web simples ou numa composição, mas também como uma linguagem de implementação deles [4]. Ainda segundo [4], PEWS serve como um guia para a implementação de um sistema e de todas as operações envolvidas no mesmo. As path expressions são usadas para restringir as sequências de operações e condições para a execução de operações de serviços através de uma especificação simples. Segundo [2], o uso de predicados aumenta o nível de expressividade, permitindo um maior controle de acesso ao objeto que está sendo manipulado. 2.3.1 A Linguagem de Composição No contexto de PEWS, predicados são extensões que podem ser aplicadas a path expressions com o intuito de aumentar sua expressividade. Podemos ver o aumento no nível de expressividade com o seguinte exemplo: usando apenas path expressions, desejamos que um uma ação a ocorra zero ou mais vezes, sendo seguida de uma operação paralela simples entre b e c. Através do operador de sequência “.”, do operador de paralelismo “||” e do operador que indica zero ou mais vezes “∗ ”, podemos representar essa expressão da seguinte forma: a∗ . (b || c) Com esse nível de expressividade, não podemos especificar, por exemplo, condições para execução das operações. O uso de predicados nos permite especificar diversas características às path expressions. Por exemplo, desejamos que ou a operação b ou a operação 34 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA c seja executada, de acordo com a avaliação de um condição P, onde se P retorna um valor true, b é executado, e se P retorna um valor false, c é executado. Vejamos o exemplo abaixo: a∗ . ([P]b || [not P] c) Como vemos acima, houve um aumento na expressividade da path expression com a extensão dada pelo uso de predicados, sendo esta a proposta de PEWS. A sintaxe PEWS é definida pela seguinte gramática [4]: (1) interface = JportType def∗ pathK (2) path = JopnameK | Jpath ‘.’ pathK | Jpath ‘+’ pathK Jpath ‘k’ pathK Jpath∗ K J‘{’ path ‘}’K J‘[’ pred ‘]’ pathK (3) pred = J ‘true’ K | J ‘false’ K | J ‘not’ pred K | J pred boolOp predK | J arith-expr relOp arith-expr K (4) def = J ‘def’ var ‘=’ arith-expr K (5) portType = J operation+ K (6) operation = J opname ‘(’ opArg ‘)’ K (7) opArg = J‘in:’ msgName K | J ’out:‘ msgName K | J‘in-out:’ msgName ‘,’ msgName K | J‘out-in:’ msgName ‘,’ msgName K (8) msgName = (9) opName = - (10) arith-expr = JvarK | Jarith-expr arithOp arith-exprK | J‘now()’K J‘act (’ opname ‘).val’K | J ‘act (’ J ‘term (’ opname ‘).time’ K | opname ‘).val’K | J‘term (’ opname ‘).time’K (11) boolOp = (12) relOp = (13) arithOp = (14) var = - Os símbolos não terminais que contém um ‘-’ no lado direito da transformação não têm sua especificação definida aqui. Entretanto, podemos ter uma noção do significado 2.3. PEWS 35 dos mesmos pela lógica da gramática. Por exemplo, boolOp indica operadores booleanos, ou seja, operador de conjunção (and) e disjunção (or), enquanto arithOp representa os operadores aritméticos, ou seja, soma (+), subtração (-), e assim por diante. Uma interface tem sua definição como sendo um conjunto de uma ou mais operações (portType), uma sequência de definições (def), seguido de uma path expression (path). O elemento portType é o responsável por descrever uma ou mais interface de chamada de serviços, ou seja, os bindings de uma ou mais operações (representadas por operation+), onde são descritos o nome do serviço e os argumentos de entrada e de saída referentes a este serviço (indicado por (6) e (7)). Já na definição def, é possível declarar variáveis inteiras para que sejam usadas posteriormente nos predicados, onde o valor das variáveis é obtido pela avaliação de expressões aritméticas que envolvem contadores predefinidos, sendo eles: req, act e term [4]. Cada um deles possui um par de inteiros, val e time, onde o primeiro representa o próprio contador, e o segundo indica o momento em que o contador foi modificado. A semântica destes contadores está fora do escopo deste trabalho. A abstração path pode representar: chamadas a serviços (JopnameK), operações seqüenciais (Jpath . pathK), escolhas (Jpath + pathK), operações paralelas (Jpath || pathK), zero ou mais operações sequenciais de um objeto (Jpath∗ K), repetições paralelas (J{path}K) e operações com predicados (J[pred] pathK), conceituadas a seguir: • JopnameK – Representa uma chamada a um determinado serviço, de nome opname; • Jpath1 . path2K – Representa uma sequência de operações, onde as operações presentes em path2 só terão sua execução iniciada no momento em que todas as operações de path1 finalizarem; • Jpath1 + path2K – Escolha entre a execução de path1 e path2, cuja decisão pode ser determinada por predicados; • Jpath1 || path2K – Operação paralela entre path1 e path2, onde as operações de ambos podem ser executadas simultaneamente; • Jpath∗ K – Representa a execução sequencial de zero ou mais vezes das operações definidas por path; • J{path}K – Repetição paralela da operação path; Para ilustrar o uso de alguns desses operadores, apresentaremos um exemplo adaptado, retirado de [4]. Como o uso de contadores pré-definidos está fora do escopo deste trabalho, adaptamos o exemplo para que o uso destes contadores seja dispensável. 36 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA Exemplo: Consideremos um armazém, cujas operações englobam receber um pedido, enviar uma conta, receber um pagamento e enviar o recibo, representados respectivamente por order, bill, payment e receipt. Suponha que, após o recebimento do pedido, o pagamento deve ser feito dentro de 48 horas após a conta ser enviada. Se o prazo não for respeitado, todo o processo é abortado. Considerando tpay como a variável que contém o intervalo de tempo entre o recebimento do pedido e o pagamento, uma possível path expression, utilizando a sintaxe de PEWS mostrada acima, é a seguinte: (order.bill.([tpay ≤ 48h] payment.receipt + [tpay > 48h] abortOperation))∗ As operações order e bill são realizadas sequencialmente. Logo após, são utilizados predicados antes da execução das operações payment.receipt e abortOperation, representando uma escolha. Sendo eles avaliados, as operações executadas corresponderão à guarda cujo resultado da avaliação retornar true. Se o valor da variável tpay é menor ou igual a 48h, as operações payment e receipt são executadas; caso contrário, o operação abortOperation (que representa o cancelamento do processo) é executada. PEWS possui uma variante chamada XPEWS [4], que é a versão de PEWS na linguagem XML. PEWS possui um front-end e um back-end, onde o front-end é um editor PEWS implementado como uma extensão para a plataforma Eclipse que inclui um verificador de tipos e um gerador para transformação de PEWS em XPEWS. O back-end tem a capacidade de gerar classes Java a partir de um programa XPEWS e adicionar restrições comportamentais a documentos WSDL. Não entraremos em detalhes sobre a sintaxe de XPEWS, pois não é o foco do nosso trabalho. 2.3.2 Máquina de Redução de Grafos Uma das formas de representar tanto Serviços Web como uma composição deles é através do uso de grafos, usados muitas vezes para definir processos em workflow [31]. Vértices podem representar os serviços propriamente ditos, enquanto as arestas podem ser responsáveis por representar as relações de dependências de execução dos serviços. Para a execução de composições utilizando esta estrutura, podem ser utilizadas as máquinas de redução de grafos, uma técnica que utiliza regras pré-definidas para a transformação da estrutura inicial dos grafos (em termos de serviços, uma composição) através de reduções, a fim de chegar a um ponto em que a estrutura não possa mais ser transformada por nenhuma das regras pré-definidas [19]. No contexto de máquinas de redução de grafos, foi proposto em [8] um modelo de uma máquina abstrata e uma implementação real da mesma, chamada PEWS-AM, que tem a capacidade de transformar especificações de orquestrações de serviços num modelo de grafos e executá-la através de uma variante da linguagem PEWS. A máquina é definida 2.3. PEWS 37 através de uma especificação PEWS, onde são aplicadas um conjunto de regras de tradução para a formação de uma grafo. Sobre este grafo, são aplicadas um conjunto de regras de redução. A partir da especificação da orquestração, as regras de tradução transformam o estado atual do grafo, realizando reduções de tal forma que o grafo não possa ser mais reduzido. No ato da realização das reduções, os processos de negócios relacionados aos serviços também são executados. 2.3.2.1 Regras de Tradução Para podermos especificar composições, é necessário o uso de uma gramática. Dessa forma, utilizaremos uma variante de PEWS, proposta em [8]. A linguagem usada tem uma estrutura que é representada pela gramática abaixo: P ¯ Ō) | P1 || P2 | P1 ; P2 | [E1 ]P1 + [E2 ]P2 ::= S(I, | E IF [E1 ]P1 | unf old | unf olding P1 ::= id | n | t | E1 + E2 | E1 - E2 | E1 * E2 | E1 / E2 | − E1 | E1 and E2 | E1 or E2 | not E1 | E1 == E2 | E1 ≤ E2 | E1 < E2 O símbolos não-terminais P e E representam, respectivamente, os serviços de uma orquestração e as expressões aritméticas e booleanas da variante da linguagem PEWS. A partir desta gramática, é possível realizar as seguintes operações: ¯ Ō) – Chamadas a serviços, com I¯ representando as variáveis de entrada do • S(I, serviço e Ō representando as variáveis de saída; • P 1 || P 2 – Paralelismo entre serviços, onde as operações realizadas em P 1 ocorrem simultaneamente as de P 2 , respeitadas algumas restrições que serão mostradas posteriormente; • P 1 ; P 2 – Sequência entre serviços, onde qualquer operação em P 2 só é executada após o término de todas as operações de P 1 ; • [E 1 ]P 1 + [E 1 ]P 2 – Escolhas não determinísiticas, onde a execução de P 1 ou P 2 depende da avaliação das expressões aritméticas E 1 e E 2 que retornam valores booleanos; • IF [E 1 ] P 1 – Condicional simples, onde a expressão E 1 é avaliada e, caso seja verdadeira, P1 é executado; 38 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA • U nf olding P 1 – Repetições de P 1 , onde toda a ocorrência do símbolo terminal unfold representa o local onde deve ser repetido o procedimento de P 1 ; • Expressões – Representam os conhecidos identificadores (id), números (n), e as expressões aritméticas, relacionais e booleanas (E1 +E2 , E1 −E2 , E1 < E2 , E1 and E2 , etc.). Para a construção do grafo, precisamos transformar cada uma das estruturas acima num conjunto de vértices e arestas. O conjunto de vértices será representado por V, onde cada vértice é uma entidade que será avaliada de acordo com determinadas regras (chamadas a serviços, expressões condicionais, etc.). As arestas são as estruturas que ligarão vértices e relacionarão os mesmos colocando restrições quanto à avaliação. Representaremos arestas com o símbolo “7→”. Em PEWS-AM, são descritos dois tipos de restrições: restrições de fluxo de controle e restrições de fluxo de dados. As restrições de fluxo de controle determinam o fluxo de execução dos serviços através de arestas de controle, representadas por Ac e as restrições de fluxo de dados representam as dependências de algumas variáveis dos vértices em relação a outros através de arestas de dependência, representadas por Ad . O termo indegree(v) é usado para representar o número de arestas que chegam a v, ou seja, a soma das arestas de controle − indegreec (v) − e as arestas de dependência − indegreed (v). Sendo assim, a definição de grafos é representada por hV, Ac , Ad i, sendo V o conjunto de vértices, Ac as arestas de controle e Ad as arestas de dependência. Para a tradução da linguagem variante de PEWS, é usada a função T . Veremos agora como é feita a tradução de uma composição de serviços P para grafos. A regra de tradução para uma chamada a serviço é feita da seguinte forma: D ¯ Ō)K = {w : S!I, ¯ w0 : S?Ō}, {w 7→ w0 }, ∅ T JS(I, E Uma chamada a serviço é traduzida para um grafo de dois vértices, w e w’, que ¯ e variáveis representam, respectivamente, expressões de entrada para o serviço S (S!I) de saída do serviço S (S?Ō). Uma aresta de controle é criada entre esses dois vértices, garantindo que os dados resultantes da execução da operação S!I¯ serão armazenados nas variáveis da operação Ō. Isso é mostrado na figura 3. ¯ Ō) para grafos Figura 3 – Tradução da operação S(I, 2.3. PEWS 39 Para a tradução de uma operação em paralelo, consideremos T JP1 K = hV 0 , A0c , A0d i e T JP2 K = hV 00 , A00c , A00d i. Dessa forma temos a seguinte tradução: T JP1 ||P2 K = hV 0 ∪ V 00 , A0c ∪ A00c , A0d ∪ A00d ∪ Δd0 ∪ Δd00 i Os vértices resultantes representam a união dos vértices dos dois grafos, P 1 e P 2 . Como eles estão executando em paralelo, não existem arestas de controle adicionais, portanto, o conjunto de arestas de controle é formado pela união das arestas de controle dos dois grafos. Apesar de estarem executando em paralelo, é possível que a execução de um vértice dependa de alguma variável que está presente na operação de algum vértice do outro grafo. Isso gera possíveis arestas de dependência entre esses dois vértices. As possíveis arestas de dependência de dados são definidas por Δd0 e Δd00 , descritos abaixo: Δd0 = {v1 7→ v2 | v1 : S1 ?Ō ∈ V 0 ∧ ¯ ∈ V ars(Ō)) ∨ [(v2 : S2 !I¯ ∈ V 00 ∧ ∃x ∈ V ars(I).x (v2 : E ∈ V 00 ∧ ∃x ∈ V ars(E).x ∈ V ars(Ō))]} Δd0 = {v2 7→ v1 | v2 : S2 ?Ō ∈ V 0 ∧ ¯ ∈ V ars(Ō)) ∨ [(v1 : S1 !I¯ ∈ V 00 ∧ ∃x ∈ V ars(I).x (se v1 : E ∈ V 00 ∧ ∃x ∈ V ars(E).x ∈ V ars(Ō))]} Para que haja uma aresta de dependência de um vértice w para um vértice w0 , é preciso que w seja um vértice de saída de serviço (S?Ō). Já o vértice w0 tem de utilizar alguma dessas variáveis de w, sendo, portanto, um vértice que possui expressões para ¯ que utiliza avaliação. Existem dois possíveis casos: (i) w0 é uma chamada de serviços (S!I) variáveis de saída do vértice w, ou (ii) w0 é uma expressão que é usada em um vértice “+”. A figura 4 mostra como seria graficamente a tradução desta operação. Figura 4 – Tradução da operação P 1 || P 2 para grafos Da mesma forma, para a tradução de uma operação em sequência, consideremos T JP1 K = hV 0 , A0c , A0d i e T JP2 J= hV 00 , A00c , A00d i. Dessa forma temos a seguinte tradução: T JP1 ; P2 K = hV 0 ∪ V 00 , A0c ∪ A00c ∪ Δc, A0d ∪ A00d ∪ Δdi 40 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA Os vértices resultantes representam a união dos vértices dos dois grafos, P1 e P2 . Como eles estão executando em sequência, as atividades de P2 só podem começar a executar quando todas as atividades de P1 tiverem terminado, ou seja, haverá uma aresta de controle dos vértices de P1 para os vértices de P2 que possuem indegreec = 0, indicado por Δc. A criação de arestas de dependência entre os dois grafos é igual a forma de criação do paralelismo entre serviço, mostrado anteriormente: Δc = {v1 7→ v2 | v1 ∈ V 0 , v2 ∈ V 00 , indegreec = 0} Δd0 = {v1 7→ v2 | v1 : S1 ?Ō ∈ V 0 ∧ ¯ ∈ Ō) ∨ [(v2 : S2 !I¯ ∈ V 00 ∧ ∃x ∈ V ars(I).x (v2 : E ∈ V 00 ∧ ∃x ∈ V ars(E).x ∈ Ō)]} Figura 5 – Tradução da operação P 1 ; P 2 para grafos Na tradução de uma escolha não-determinística, consideremos T JP1 K = hV 0 , A0c , A0d i e T JP2 K = hV 00 , A00c , A00d i. Neste caso, temos: T J[E1 ]P1 + [E2 ]P2 K = hV 0 ∪ V 00 ∪ {v : +, v 1 : E 1 , v 2 : E 2 }, A0c ∪ A00c ∪ Δ, A0d ∪ A00d i Os vértices resultantes representam a união dos vértices dos dois grafos, P1 e P2 , com a adição de mais 3 vértices: v, v1 e v2 . O vértice v representa uma escolha nãodeterminística entre os vértices v1 e v2 , que são expressões. Devido a isto, as arestas de controle são representadas pela união das arestas de controle já existentes em P1 e P2 , adicionando-se duas arestas a mais, que saem de v para v1 e v2 (indicados por Δ), além das arestas que partem dos vértices v1 e v2 para, respectivamente, os vértices de V1 e V2 que possuem indegreec = 0. As arestas de dependência são formadas apenas pela união entre as arestas de dependência dos grafos P1 e P2 : Δ = {v 7→ v1 , v 7→ v2 } ∪ Δ0 ∪ Δ00 Δ0 = {v1 7→ w | w ∈ V 0 , indegreec (w) = 0} Δ00 = {v 2 7→ w0 | w0 ∈ V 00 , indegreec (w0 ) = 0} 2.3. PEWS 41 Figura 6 – Tradução de uma escolha não-determinística para grafos A tradução de uma expressão condicional simples é bem parecida com a transformação da escolha não-determinística, mostrada anteriormente. Portanto, seja T JP1 K = hV 0 , A0c , A0d i em: T JIF [E1 ]P1 K = hV ∪ {v : +, v1 : E1 , v2 : f alse}, A0c ∪ {v → 7 v1 , v → 7 v2 } ∪ Δ, A0d i 0 Os vértices resultantes representam a união dos vértices do grafo P1 e mais 3 vértices: v, v1 e v2 . O vértice v representa uma escolha não-determinística entre os vértices v1 e v2 , que são expressões. Entretanto, a expressão de v2 já é avaliada como sendo falsa, ou seja, a mesma está descartada. Devido a isto, as arestas de controle são representadas pela união das arestas de controle já existentes em P1 , adicionando-se duas arestas a mais, que saem de v para v1 e v2 , além das arestas que partem dos vértices v1 para os vértices de V1 que possuem indegreec =0, representadas por Δ, mostrado abaixo. Isto é descrito na figura 7: Δ = {v1 7→ w | w0 ∈ V 0 , indegreec (w0 ) = 0} Figura 7 – Tradução de uma estrutura condicional simples para grafos Por fim, para a tradução de uma estrutura de repetição, consideremos T JP1 K = hV, Ac , Ad i. Vejamos abaixo: T JU nf olding P1 K = h{V [v : µ], Ac ∪ Δ, Ad i Os vértices resultantes representam a união dos vértices do grafo P1 com o vértice especial µ (M u), que representará o início de um laço. As arestas de controle são representadas pela união das arestas de controle já existentes em P1 , adicionando-se arestas 42 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA que saem de µ para os vértices de V que possuem indegreec = 0, representadas por Δ. As arestas de dependência são as arestas já existentes em P1 : Δ = {v 7→ w | w0 ∈ V 0 , indegreec (w0 ) = 0} Figura 8 – Tradução de uma estrutura de repetição para grafos A tradução de um vértice do tipo unf old não envolve a criação de arestas. É apenas criado o vértice que irá representar pontos específicos onde o controle é transferido para o início da iteração, definida pelo vértice µ. 2.3.2.2 Regras de Redução Tendo traduzido a gramática de PEWS para uma notação de grafos, regras de avaliação podem ser aplicadas aos vértices para reduzir o grafo original, produzido pelas regras de tradução. Segundo [8], a máquina abstrata que corresponde ao estado inicial do grafo logo após a transformação PEWS tem a seguinte configuração: hhV, Ac , Ad i , ρ, I, Oi A expressão hV, Ac , Ad i faz referência aos vértices, arestas de controle e arestas de dependência do grafo. São acrescentados mais três elementos: • Um ambiente ρ, responsável por manter a consistência e durabilidade das variáveis, representado por um identificador, o vértice que lhe atribuiu um valor, e o respectivo valor. Uma expressão ρ[x,v,k] indica que a variável x é um identificador cujo valor é k e esse valor foi atribuído pelo vértice v; • Um buffer de parâmetros de entrada I, composto por uma operação, um vértice e uma lista de valores. Uma expressão I [S,v,t̄] indica que o serviço S enviou uma lista de valores t̄ para ser utilizada pelo vértice v; • Um buffer de parâmetros de saida O, composto também por uma operação, um vértice e uma lista de valores. Uma expressão O[S,v,t̄] indica que o serviço S recebeu 2.3. PEWS 43 e irá avaliar uma lista de valores t̄ e, posteriormente, enviará o resultado para ser utilizado pelo vértice v; Os vértices candidatos a serem reduzidos inicialmente são: chamadas e retornos de serviços (S!I¯ e S?Ō), escolhas não-determinísitcas “+” (que comporta também os condicionais) e o vértice de repetição. D E ¯ v2 : S?Ō], Ac [v1 7→ v2 ], Ad . Uma chamada de serviço é definida por V [v1 : S!I, Essa representação corresponde ao primeiro elemento da máquina abstrata. A avaliação dessa entrada é a seguinte: DD E ¯ v2 : S?Ō], Ac [v1 7→ v2 ], Ad , ρ, I, O V [v1 : S!I, E =⇒ DD E E ¯ , onde indegree(v1 ) = 0 V [v2 : S?Ō], Ac , Ad , ρ, I, O[S, v2 , Eval(I)] Como vemos acima, fica explícito o fato de que nenhuma aresta está chegando no vértice v1 , estando o mesmo pronto para ser avaliado, além de que o vértice v2 receberá a ¯ Na redução, as variáveis de entrada presentes em I¯ são avaliadas resposta da chamada S!I. ¯ O vértice v2 receberá o resultado da avaliação das entradas realizaatravés de Eval(I). das por v1 e, portanto aparecerá dentro do parâmetro O. Com a avaliação feita, tanto o vértice avaliado quanto as arestas que saem desse vértice do grafo são retirados deste. Seguindo o mesmo raciocínio, vemos abaixo a avaliação da respectiva saída de serviço e a representação gráfica na figura 9. DD E V [v2 : S?Ō], Ac , Ad , ρ, I[S, v1 , t], O E =⇒ hhV, Ac − {v2 7→c v 0 |v 0 ∈ V }, Ad − {v2 7→d v 0 |v 0 ∈ V }i , ρ0 , I, Oi, onde indegree(v2 ) = 0 ρ0 = ρ[(xi , v2 , ki | 1 ≤ i ≤ n)], Ō =< x1 , ..., xn >, t̄ =< k1 , ..., kn > A configuração usada na saída de S!I¯ é utilizada na entrada de S?Ō. O parâmetro I contém o serviço S, o vértice que está atribuindo esses valores à saída (v1 ) e a avaliação ¯ que se transformaram numa tupla de variáveis t̄. Com esses dos valores de v1 (Eval(I)), dados, é possível avaliar a saída de serviço S?Ō. Como resultado, temos: todos os vértices do grafo V ; todas as arestas de controle e de dependência, com a exclusão das arestas que saíam do vértice v2 ; um novo ambiente de variáveis ρ0 , que contém as variáveis de saída Ō (x1 ,...xn ), o vértice que atribuiu esses valores às variáveis (v2 ) e os respectivos valores 44 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA de Ō(k1 , ...kn , que correspondem respectivamente a x1 , ...xn ). Figura 9 – Transformação de entrada e saída de serviços Na avaliação de uma escolha não-determinística, necessitamos avaliar as expressões de guarda dos predicados. Na máquina de redução, se uma guarda retorna true, não é necessário a avaliação da outra guarda. A avaliação é feita considerando a seguinte ordem: (i) a primeira guarda retorna true; (ii) a primeira guarda retorna false; (iii) a segunda guarda retorna true; e (iv) a segunda guarda retorna false. Em um primeiro momento, temos a seguinte situação: hhV [v1 : +, v2 : E2 , v3 : E3 ], Ac [v1 7→ v2 , v1 7→ v3 ], Ad i , ρ, I, Oi =⇒ hhV [v1 : +, v2 : Eval(E2 ), v3 : E3 ], Ac [v1 7→ v2 , v1 7→ v3 ], Ad i , ρ, I, Oi, onde indegree(v1 ) = 0, indegreec (v2 ) = 1, indegreed (v2 ) = 0 O indegree(v1 ) é 0, ou seja, não existem arestas chegando nele. Além disso, a única aresta que chega a v2 é a aresta correspondente a v1 . Um fato a ser observado é que o vértice “+”não é retirado do grafo, pois ele pode ser utilizado novamente. Na avaliação mostrada acima, E2 assume um valor booleano, ou seja, true ou false. Considerando o passo (i), se E2 é true, os seguintes vértices e arestas são retirados do grafo: os vértices v1 , v2 e v3 ; as arestas de controle do vértice v2 ; e todos os descendentes de v3 . Neste caso, temos: hhV [v1 : +, v2 : true, v3 : E3 ], Ac [v1 7→ v2 , v1 7→ v3 ], Ad i , ρ, I, Oi =⇒ hhV 0 , A0c , Ad i , ρ, I, Oi onde, indegree(v1 ) = 0, V 0 = V \{w ∈ V | v3 7→∗c w}, 2.3. PEWS 45 A0c = Ac \{v2 7→∗ w | w ∈ V } Na situação (ii), E2 é avaliado como false. Retiramos todos os vértices filhos de v2 , assim como o próprio v2 e nos preparamos para avaliar a segunda guarda, v3 . Dessa forma, temos: hhV [v1 : +, v2 : f alse, v3 : E3 ], Ac [v1 7→ v2 , v1 7→ v3 ], Ad i , ρ, I, Oi =⇒ hhV 0 [v1 : +, v3 : E3 ], A0c [v1 7→ v3 ], Ad i , p, I, Oi onde, indegree(v1 ) = 0, V 0 = V \{w ∈ V | v2 7→∗c w}, A0c = Ac \{v2 7→∗ w | w ∈ V } Na realização de (ii), permanecemos com uma guarda disponível para avaliação. Realizando o passo (iii), E3 é avaliado como true. Assim, retiramos o vértice v1 :+, o vértice v3 e as arestas de seus filhos, correspondendo a seguinte situação: hhV [v1 : +, v3 : true], Ac [v1 7→ v3 ], Ad i , ρ, I, Oi =⇒ hhV, A0c , Ad i , ρ, I, Oi, onde indegree(v1 ) = 0, A0c = Ac \{v3 7→∗ w | w ∈ V } Da mesma forma, se temos v3 : E3 avaliado como false (o que corresponde ao passo (iv)), retiramos o vértice, suas arestas e seus filhos da mesma forma como fizemos no segundo passo. A figura 10 mostra a sequência desses passos, onde (iii) e (iv) tem as mesmas características da da avaliação do condicional simples (“IF”). Na avaliação de repetições, utilizamos o vértice µ. Sendo este um vértice mínimo, todos os vértices unf old relacionados a este vértice são substituidos por uma cópia do subgrafo que está dentro do âmbito do µ, ou seja, todos os vértices de Unfolding P. Seja G um grafo formado por um conjunto de vértices V , adicionando-se um vértice v : µ, pai de todos os outros vértices de V , e seja também w um vértice unfold correspondente ao vértice v : µ. Temos que G = hV [v : µ, w : unf old], Ac , Ad i. Observemos abaixo a seguinte transformação: 46 Capítulo 2. FUNDAMENTAÇÃO TEÓRICA Figura 10 – Transformação de uma escolha não-determinística hG, ρ, I, Oi =⇒ hhG[w/Gw ]\{v}, ∅, ∅i , ρ0 , I, Oi, onde indegree(v) = 0, w Gw = hV w , Aw c , Ad i Na transformação acima, temos o grafo G e a ocorrência de w/Gw , sendo w o vértice unfold e Gw uma cópia do grafo G a partir do vértice v. O símbolo “\” indica que, quando o vértice w for um vértice mínimo, ele deve ser substituído pelo grafo Gw . Graficamente, teríamos situação mostrada na figura 11. Figura 11 – Transformação de uma estrutura de repetição 47 3 RECUPERAÇÃO DE FALHAS EM PEWSAM Neste capítulo, descrevemos os métodos de recuperação de falhas no contexto da máquina de redução de grafos PEWS-AM. Além disso, mostraremos também as alterações que serão necessárias para que esses métodos sejam suportados, ou seja, a extensão da gramática de PEWS e a mudança e inserção de novas regras de tradução e de redução de grafos. 3.1 Classificação de Falhas Serviços Web são utilizados num ambiente extremamente dinâmico, que é a Web. Em virtude disso, existem grandes possibilidades de serviços ficarem indisponíveis por algum tempo, às vezes no momento em que são requisitados. Uma composição de serviços deve estar apta a se recuperar deste tipo de falha, pois sem o devido tratamento, esta ocorrência pode acarretar na perda de todo o processamento anteriormente realizado. Adotamos neste trabalho o tratamento de falhas de disponibilidade de serviços, em virtude da delimitação do escopo deste trabalho. Falhas de disponibilidade incluem Time-outs e Service not-found, causados respectivamente pela demora na resposta à solicitação e pela ação má sucedida na busca do serviço solicitado. Ambos ocorrem quando um solicitante tenta realizar uma comunicação com um serviço, não obtendo uma resposta. Isso pode ocorrer por diversos motivos, dentre eles destacamos três: queda do serviço, sobrecarga de serviço e inexistência de serviço. Nos dois primeiros casos, as falhas podem acontecer momentaneamente, ou seja, após algum tempo, o serviço pode voltar a funcionar normalmente. Já na inexistência de serviço, ou o serviço não existe ou mudou a forma de sua comunicação. O tratamento de falhas de disponibilidade dá possibilidades à composição de serviços de não abortar sua execução e evitar perdas de processamento. Dessa forma, adotaremos o tempo de resposta como critério de detecção de falhas, onde definiremos um espaço de tempo para que possamos verificar se houve ou não uma falha na execução do serviço. Em [5], vimos a proposta de duas abordagens para tratamento de falhas: DPD (Design Defensive Project), onde a composição é construída de forma que as soluções já estivessem embutidas no próprio código de composição; e SRTM (Service Run-Time Monitoring), onde soluções são tomadas no momento em que a execução da composição falha. No nosso trabalho, utilizaremos a técnica DPD. Construiremos composições de serviços onde iremos prever situações errôneas e descrever mecanismos de tratamento para 48 Capítulo 3. RECUPERAÇÃO DE FALHAS EM PEWS-AM que a mesma volte a um estado válido, ou seja, volte a um estado onde a continuação da execução seja possível. Apesar de dar suporte à recuperação da execução, a máquina não dá suporte ao rollback do processamento realizado, ou seja, não é possível desfazer as ações que foram realizadas antes da ocorrência de uma falha. Para o tratamento de falhas de disponibilidade, utilizaremos os mecanismos propostos em [5]. Os três mecanismos propostos para recuperação de falhas de disponibilidade são: Reinvocação do Serviço (Retry), Substituição de Serviço (Rebinding) e Reorganização do Processo (Restructure). Os mecanismos deste trabalho foram escolhidos como base por demonstrarem sua idéia geral de forma clara e pela baixo custo do esforço na adaptação da implementação em PEWS-AM. Nas seções seguintes, apresentamos a descrição de cada método e como eles são adaptados para o funcionamento na máquina de redução de grafos. 3.2 Reinvocação do Serviço Neste primeiro método, tentamos refazer uma conexão com o serviço. Este método ao qual nos referimos simplesmente como Retry neste trabalho pode fazer com que a execução se recupere de falhas momentâneas, causadas principalmente por time-outs. A estratégia visa encapsular cada chamada de serviço em um bloco e realizar um determinado número de tentativas antes de desistir da comunicação com este serviço. Para dar suporte ao Retry, o designer da composição deverá especificar dois valores: tempo de espera (TE , critério de detecção de falhas adotado) e número máximo de tentativas (NM T ). O TE indica o tempo que o requisitante irá esperar pela resposta de um serviço. Já o NM T indica o número máximo de tentativas de comunicação com um determinado serviço. Estes são parâmetros globais da composição. Cada serviço recebe inicialmente o número de tentativas máximas de execução NM T e este valor é controlado individualmente para cada serviço. Por exemplo, considerando determinados serviços S1 e S2 , temos o número de tentativas desses serviços representados, respectivamente, por NT 1 e NT 2 , sendo estes valores inicialmente iguais ao NM T global. Para o uso do Retry, temos que definir os parâmetros TE e NM T para a composição de serviço. À medida que um serviço falha em sua execução, seu parâmetro NT é diminuído em uma unidade e uma nova tentativa de execução é realizada. Quando o valor de NT for igual a zero numa tentativa de execução, detectamos que o número máximo de tentativas foi atingido, chegando a uma situação momentaneamente sem solução. Para representar o Retry, precisamos considerar o momento no qual o serviço foi chamado e o tempo atual, os quais denominaremos, respectivamente, TC e now(). Vejamos a representação na figura 12. 3.2. Reinvocação do Serviço 49 Figura 12 – Demonstração do uso do Retry Na figura 12, temos um serviço S1 e o par de parâmetros (TE ,NT ). Observamos três situações possíveis: em (i), a chamada ao serviço é realizada com sucesso (vértice de cor verde). Em (ii), a chamada foi considerada falha porque o tempo máximo de espera da resposta foi ultrapassado (now() − TC > TE , representado pelo vértice de cor amarela). Neste caso, devemos observar se o número máximo de tentativas de execução de S1 atingiu o limite, através de NT . Se o mesmo é maior que zero, fazemos novamente a chamada do serviço, agora com o par (TE , NT − 1). Na situação (iii), temos as mesmas características de (ii), porém com o parâmetro NT sendo igual a zero (vértice de cor vermelha). Neste caso, o número máximo de tentativas especificadas para o Retry atingiu o limite, não havendo mais alternativas para a execução deste serviço. 3.2.1 Alterações para suporte ao Retry Na gramática PEWS, inserimos um novo símbolo não-terminal C. Este símbolo pode gerar composições com a recuperação de falhas e sem a recuperação de falhas. O par (TE , NM T ) é um par de inteiros maiores ou iguais a 0. Todos os serviços iniciarão sua execução com esses mesmos valores. A nova gramática com essa extensão é mostrada abaixo (as extensões aparecem sublinhadas): C ::= ‘RETRY’ ‘(’ TE ‘,’ NM T ‘)’ P | P P ¯ Ō) | P1 || P2 | P1 ; P2 | [E1 ]P1 + [E2 ]P2 ::= S(I, | E IF [E1 ]P1 | unf old | unf olding P1 ::= id | n | t | E1 + E2 | E1 - E2 | E1 * E2 | E1 / E2 | − E1 | E1 and E2 | E1 or E2 | not E1 | E1 == E2 | E1 ≤ E2 | E1 < E2 50 Capítulo 3. RECUPERAÇÃO DE FALHAS EM PEWS-AM 3.2.2 Alterações na Máquina de Redução de Grafos Para dar suporte ao Retry na máquina de redução, propomos alterações em sua estrutura original. As regras de transformação não são alteradas com a inserção do Retry. O mesmo apenas define novos parâmetros da máquina de execução, ficando as modificações restritas às regras de redução. A proposta consiste na inserção de três novos elementos na máquina: • Monitor (M ) – Este elemento é o elemento principal, responsável pelo monitoramento da execução de um serviço. O mesmo é representado por uma lista de tuplas, ¯ por exemplo, considerando que contém: uma entidade de entrada de serviços (S!I, ¯ onde é realizada a chamada um serviço S com uma tupla de variáveis de entrada I), do serviço; um vértice de saída de serviço (S?Ō, similar ao anterior, com Ō sendo a tupla de variáveis de saída), para o qual devem ser enviadas as respostas relativas à execução; o momento da chamada do serviço; e o número de tentativas restantes de execução do serviço. Dessa forma, utilizando o raciocínio e os dados descritos, no momento de uma chamada de serviço, teremos a máquina com a seguinte configuração: D E ¯ S?Ō, now(), NM T ] M [ S!I, • Tempo de Espera (TE ) – Este parâmetro já foi citado anteriormente, sendo o tempo de espera máximo pela resposta dos serviços da composição; • Número de tentativas (NM T ) – Este parâmetro também já foi citado, sendo o número máximo de tentativas da execução dos serviços da composição; Para realizarmos a redução com estes novos parâmetros, consideremos dois vérti¯ e um de saída de serviço (v2 : S?Ō), existindo uma ces, um de entrada de serviço (v1 : S!I) aresta de controle entre ambos (v1 7→ v2 ). Além disso, consideremos os parâmetros globais TE e NM T , representando respectivamente o tempo máximo de espera pela resposta de um serviço e o número de tentativas máximas de execução de um serviço da composição. Dessa forma, apresentamos a redução da primeira invocação de um serviço S por meio da seguinte forma: hG, ρ, I, O, M, TE , NM T i, sendo D E ¯ v2 : S?Ō], Ac , Ad , G = V [v1 : S!I, indegree(v1 ) = 0 =⇒ 3.2. Reinvocação do Serviço 51 D E ¯ M [v1 : S!I, ¯ v2 : S?Ō, now(), NM T )], TE , NM T , G0 , ρ, I, O[S, v2 , Eval(I)], D 0 0 E G0 = V [v2 : S?Ō], Ac , Ad , 0 Ac = Ac − {v1 7→c w | w ∈ V }, 0 Ad = Ad − {v1 7→d w | w ∈ V }, O esquema geral de redução continua o mesmo apresentado na fundamentação. As diferenças residem na adoção dos parâmetros correspondentes ao tempo de espera TE , ao número máximo de tentativas NM T e na introdução do monitor M . O monitor M manterá o registro da execução do serviço S por meio de um nova tupla referente a sua chamada. Nesta tupla, temos: o vértice de entrada avaliado (v1 ); o vértice de saída para o qual será enviada a resposta (v2 ); o momento da chamada do serviço S, representado pela chamada da função auxiliar now(); e o número de tentativas de execução para este serviço (inicialmente NM T , sendo controlado invididualmente para este serviço). Temos três possíveis situações posteriores: (i) o serviço responde antes do tempo máximo de espera; (ii) o serviço não responde antes do tempo máximo de espera, sendo o número de tentativas restantes maior que zero; e (iii) o serviço não responde antes do tempo máximo de espera, sendo o número de tentativas restantes igual a zero. O caso (iii) é um caso onde o Retry não mais oferece ações alternativas, sendo a execução deste serviço descartada (posteriormente, faremos outras verificações quanto a ocorrência deste caso nas seções 3.3 e 3.4). Sendo assim, consideraremos os casos (i) e (ii). Considerando o caso (i), teremos sucesso na execução do serviço se tivermos uma resposta da solicitação antes do término do tempo de espera, ou seja, se recebermos uma resposta e tivermos a seguinte situação: (now() - TC ) ≤ TE Para a detecção do recebimento da resposta, temos de observar se existe alguma tupla no buf f er I enviada pelo vértice v1 , ou seja, se temos algum elemento do tipo D E S, v1 , t̄ , indicando o recebimento da resposta para o buf f er. Dessa forma, apresentamos a redução para o caso (i): D E ¯ v2 : S?Ō, TC , NT )], TE , NM T , sendo G, ρ, I[S, v1 , t̄], O, M [(v1 : S!I, D E G = V [v2 : S?Ō], Ac , Ad , indegree(v2 ) = 0 =⇒ hG0 , ρ0 , I, O, M, TE , NM T i, onde G0 = hV, Ac − {v2 7→c v 0 | v 0 ∈ V }, Ad − {v2 7→d v 0 | v 0 ∈ V }i, ρ0 = ρ[(xi , v2 , ki | 1 ≤ i ≤ n)], 52 Capítulo 3. RECUPERAÇÃO DE FALHAS EM PEWS-AM Ō =< x1 , ..., xn >, t̄ =< k1 , ..., kn > Observando a configuração inicial da máquina, vemos a presença de um elemento no buf f er de entrada que contém o vértice v1 . Isso indica que o serviço relacionado a este vértice respondeu a uma requisição. Com isso, a tupla relacionada a este serviço no monitor pode ser retirada. Além disso, a resposta presente no buf f er de entrada é consumida pelo vértice v2 e o mesmo é reduzido, sendo estes passos similares aos passos da máquina original. Considerando agora o caso (ii), teremos uma falha na execução do serviço se não tivermos uma resposta da solicitação antes do término do tempo de espera, ou seja, se não recebermos uma resposta e tivermos a seguinte situação: (now() - TC ) > TE Para a detecção do não-recebimento da resposta, temos de observar a não-existência de uma tupla no buf f er I enviada pelo vértice v1 , ou seja, se não temos nenhum elemento D E do tipo S, v1 , t̄ , que indicaria o recebimento da resposta para o buf f er. Dessa forma, apresentamos a redução para o caso (ii): D E ¯ v2 : S?Ō, TC , NT )], TE , NM T , sendo G, ρ, I, O, M [(v1 : S!I, D E G = V [v2 : S?Ō], Ac , Ad , indegree(v2 ) = 0, (now() - TC ) > TE , NT > 0, D E ∀t̄. S, v1 , t̄ ∈ /I =⇒ D ¯ M [(v1 : S!I, ¯ v2 : S?Ō, now(), NT − 1)], TE , NM T G, ρ, I, O[S, v2 , Eval(I)], D G = V [v2 : S?Ō], Ac , Ad E E Nas premissas, além da identificação de que o vértice de saída v2 possui indegree=0, temos mais três informações: o tempo de espera pela resposta da execução do serviço ultrapassou o limite; o número de tentativas restantes para a execução do mesmo é maior D E que zero; e que não existe um elemento no buf f er de entrada cujo padrão seja S, v1 , t̄ . Como não existe nenhuma entrada para ser consumida pelo vértice v2 , visto que o tempo de espera ultrapassou o limite, consideramos esta chamada como falha, e realizamos uma nova chamada ao serviço, uma vez que ainda temos tentativas restantes a realizar. Dessa forma, a redução possui a mesma configuração de uma chamada de serviço, porém com 3.3. Substituição do Serviço 53 a alteração do momento da chamada (definida pelo tempo atual now()) e do número de tentativas restantes para esta chamada, o qual é decrescido em uma unidade. Essa configuração se repete todas as vezes que forem detectadas falhas e quando ainda existirem tentativas de execução a serem realizadas. 3.3 Substituição do Serviço Um fator real que pode influir na escolha de serviços é a taxa de utilização. Alguns serviços podem ser gratuitos e outros podem ser pagos. Se ambos são exatamente iguais e funcionam corretamente, logicamente que o usuário iria escolher serviços gratuitos. Porém, na indisponibilidade dos mesmos, o usuário não teria outra alternativa a não ser escolher um serviço pago, desde que não prejudicasse a execução da composição. Sendo assim, propomos a implementação do Rebinding, que tem como objetivo tentar a execução com outro serviço que possa realizar a mesma atividade que um serviço cuja execução falhou. Duas estratégias podem ser utilizadas para o Rebinding: (i) uma forma estática, formando uma fila de substituição de serviços equivalentes ou (ii) uma forma dinâmica, procurando um serviço equivalente em tempo de execução. Considerando (i), a especificação da composição acompanha um conjunto de serviços que possam substituir o serviço falho. Utilizaremos a forma estática, ou seja, daremos a possibilidade do designer especificar uma fila de serviços equivalentes. Consideremos o conjunto de serviços S1 , S2 e S3 da figura 13, onde estes serviços são equivalentes, ou seja, apesar de diferentes variáveis de entrada, os mesmos produzem a mesma saída. Supomos a ordem de execução prioritária como sendo S1 7→ S2 7→ S3 . Como a prioridade de execução maior é a do serviço S1 , especificaremos o tempo de chamada deste serviço como sendo TC1 . Sendo TE o tempo máximo de espera na resposta de um serviço, a figura 13 apresenta um dos possíveis comportamentos de execução diante da possibilidade de substituição do serviço S1 . Temos duas situações possíveis: (i) o serviço escolhido enviou a resposta (especifiD E cado pela expressão ∃t̄ S1 , w1 , t̄ ∈ I) antes do termino do tempo máximo de espera TE ; D E ou (ii) o serviço não executou (especificado pela expressão ∀t̄ S1 , w1 , t̄ ∈ / I) dentro do tempo máximo TE . No primeiro caso, como a execução do serviço S1 foi bem sucedida dentro de um intervalo de tempo menor que TE , não há a necessidade da execução dos outros serviços equivalentes (S2 e S3 ). Sendo assim, eles são descartados. Já em (ii), a chamada ao serviço S1 não executou com sucesso dentro do intervalo de tempo máximo. Entretanto, outros serviços estão disponíveis para substituir S1 . Sendo assim, como resultado, descartamos o serviço S1 e continuamos o mesmo procedimento com os serviços que estão em espera, de acordo com a ordem definida pela fila de serviços. 54 Capítulo 3. RECUPERAÇÃO DE FALHAS EM PEWS-AM Figura 13 – Demonstração do uso do Rebinding 3.3.1 Alterações da gramática para o suporte ao Rebinding Para o suporte ao Rebinding, criaremos uma nova construção de vértices na gramática, que será semelhante à construção de chamadas de serviços, porém com o suporte à especificação de serviços equivalentes. Vejamos as extensões abaixo: ¯ Ō) SAlt | ... P ::= ... | ‘@’ S(I, ¯ Ō) SAlt | ‘@’ SAlt ::= ‘%’ S(I, Utilizamos o símbolo ‘@’ para delimitar um conjunto de serviços que realizam uma mesma tarefa dentro de uma composição. Para que possamos deixar ilimitada a especificação, criamos o símbolo não-terminal SAlt, em alusão à ‘Serviços Alternativos’. Para a separação dos serviços alternativos, utlizamos o símbolo terminal ‘%’. Com essas extensões, e também considerando a extensão advinda do Retry, a gramática de PEWS ficará da seguinte forma (as novas inserções contém um sublinhado): C ::= ‘RETRY’ ‘(’ TE ‘,’ NM T ‘)’ P | P P ¯ Ō) | P1 || P2 | P1 ; P2 | [E1 ]P1 + [E2 ]P2 ¯ Ō) SAlt | S(I, ::= ‘@’ S(I, | E if [E1 ]P1 | unf old | unf olding P1 ::= id | n | t | E1 + E2 | E1 - E2 | E1 * E2 | E1 / E2 | − E1 | E1 and E2 | E1 or E2 | not E1 | E1 == E2 | E1 ≤ E2 3.3. Substituição do Serviço | SAlt 55 E1 < E2 ¯ Ō) SAlt | ‘@’ ::= ‘%’ S(I, 3.3.2 Alterações da Máquina de Redução de Grafos Veremos passo a passo a construção da solução adotada para dar suporte ao Rebinding na máquina de redução, mostrando os refinamentos realizados para se chegar a solução final. Analisemos a situação inicial, na criação de n serviços, de acordo com a figura 14: Figura 14 – Rebinding - Geração de Vértices e Arestas Para n serviços equivalentes, criamos 2n vértices (correspondendo a um vértice de entrada e um de saída por serviço) e n arestas de controle, que ligam cada um dos vértices de entrada e saída. Adotamos como critério de cálculo de eficiência o número de vértices e arestas criadas. De forma a diminuir essa quantidade, observamos que todos os serviços, apesar de variáveis de entrada diferentes, produzem a mesma saída, ou seja, os vértices de saída de todos os serviços são iguais. Podemos considerar que todos os vértices de entrada tem uma aresta de controle para a mesma saída, ou seja, para n serviços equivalentes, temos n + 1 vértices. Ficamos com a situação mostrada na figura 15: Com esta mudança, passamos a ter agora não mais 2n vértices na criação dos serviços alternativos, mas agora n + 1. Há uma diminuição significativa quando colocamos muito serviços alternativos. Entretanto, observamos que o vértice de saída contém apenas _?Ō, faltando especificar qual o serviço Si , i ≤ 1 ≤ n, de resposta. Além disso, seria interessante que, além de dininuir o número de vértices, conseguissemos também diminuir o número de arestas criadas, onde, para n serviços, temos n arestas de controle. Para resolver essas questões, criamos a noção de um tipo de vértice composto 56 Capítulo 3. RECUPERAÇÃO DE FALHAS EM PEWS-AM Figura 15 – Rebinding - Diminuição do número de vértices (vértices que possuem outros vértices internos em sua estrutura), chamado pilha. Pilha é uma estrutura de dados do tipo LIFO (Last In - First Out), onde temos a noção de topo. Criamos então uma pilha de serviços, sendo este um vértice composto que contém um ou mais vértices de entrada de serviços. Se uma pilha de serviços é um vértice, este pode possuir arestas. Sendo assim, fazemos com que o vértice pilha de serviços (que contém um ou mais vértices de entrada em sua estrutura) crie uma aresta de controle entre a saída de serviços que é compartilhada entre os vértices de entrada de serviços. Dessa forma, no lugar de n arestas de controle, teremos apenas uma aresta que sai do vértice pilha de serviços, como é mostrado na figura 16. Figura 16 – Rebinding - Diminuição do número de arestas Identificamos o vértice pilha de serviços por S, um identificador fictício que assume o papel de um vértice e que também é o responsável pela saída da tupla de variáveis Ō. 3.3. Substituição do Serviço 57 Semanticamente, um dos serviços da pilha será o responsável por dar a saída do vértice S?Ō, porém, como não sabemos qual dos vértices irá executar corretamente, colocamos o vértice de saída com a identificação da pilha. Para mantermos os padrões de escrita de transformações e reduções da máquina, criaremos uma função auxiliar, chamada newName(), a qual tem a função de atribuir um identificador único ao vértice que representa a pilha de serviços alternativos. Vejamos a transformação de uma especificação de pilha de chamadas: T JS1 (I¯1 , Ō1 ) % S2 (I¯2 , Ō2 ) % ... % Sn (I¯n , Ōn )K = let S = newN ame(); K = [S1 !Ē1 , S2 !Ē2 , ..., Sn !Ēn ] D E in V [w : S!K, w0 : S?Ō], Ac [w 7→ w0 ], Ad Utilizamos S (com o auxílio da função newName()) para criar um identificador para a pilha e K (a pilha propriamente dita) para guardar os vértices de entrada S1 !Ē1 , S2 !Ē2 , ..., Sn !Ēn . Observamos a existência de uma aresta entre a pilha e o vértice de saída w0 criado como representação geral da saída de um dos vértices da pilha K. Na redução deste tipo de vértice, utilizaremos a nova configuração da máquina, com a inserção do monitor M , do tempo de espera máximo TE e do número de tentativas máximas de execução NM T . A primeira redução a ser realizada consiste na inserção de um novo parâmetro no monitor M , correspondente à invocação o vértice de chamada do topo da pilha, como mostrado abaixo: hG, ρ, I, O, M, TE , NM T i, sendo D E G = V [w : S!K, w0 : S?Ō], Ac [w 7→ w0 ], Ad , K = [w1 : S1 !I¯1 ; w2 : S2 !I¯2 ; ... ; wn : Sn !I¯n ], indegree(w) = 0 =⇒ D E G0 , ρ, I, O[S1 , w0 , Eval(I¯1 )], M [(w : S!K, w0 : S1 ?Ō, now(), NM T )], TE , NM T , onde D 0 0 E G0 = V [w0 : S1 ?Ō], Ac , Ad , K = [w1 : S1 !I¯1 ; w2 : S2 !I¯2 ; ... ; wn : Sn !I¯n ], 0 Ac = Ac − {w 7→c z | z ∈ V }, 0 Ad = Ad − {w 7→d z | z ∈ V } Como podemos observar, criamos uma entrada no monitor M cujo primeiro elemento é a pilha S!K, de onde virá a resposta de algum vértice nela presente. Como a execução correta da pilha está condicionada à resposta de algum dos vértice em avaliação pelo monitor, retiramos a pilha do grafo, sem a perda da referência, e avaliamos a mesma 58 Capítulo 3. RECUPERAÇÃO DE FALHAS EM PEWS-AM de acordo com o vértice do topo. Observamos que, após a redução, o vértice de saída de serviços recebeu agora um identificador de um vértice real de entrada de serviços (S1 ). Além da redução correspondente à invocação do serviço no topo da pilha de serviços alternativos, devemos considerar três casos: (i) quando a execução do serviço que está no topo da pilha ocorre dentro do tempo máximo permitido; (ii) quando a execução do serviço que está no topo da pilha ultrapassa o limite de tempo máximo, havendo ainda tentativas de execução; e (iii) quando a execução do serviço que está no topo da pilha ultrapassa o limite de tempo máximo, não havendo mais tentativas de execução. Neste último caso, consideremos a existência de pelo menos um vértice de chamada a mais que o vértice falho, ou seja, após a detecção e exclusão deste vértice, ainda temos uma pilha não-vazia. O caso onde temos uma pilha vazia será tratado posteriormente. No caso (i), temos uma situação semelhante ao caso de sucesso do Retry: a resposta foi enviada antes do término do tempo máximo de espera TE . Dessa forma, apresentamos a seguinte redução: D E G, ρ, I[S1 , w1 , t̄], O, M [(w : S!K, w0 : S1 ?Ō, TC , NT )], TE , NM T , sendo D E G0 = V [w0 : S1 ?Ō], Ac , Ad , K = [w1 : S1 !I¯1 ; w2 : S2 !I¯2 ; ... ; wn : Sn !I¯n ], indegree(w0 ) = 0 =⇒ hG0 , ρ0 , I, O, M, TE , NM T i, onde D E 0 0 G0 = V, Ac , Ad , 0 Ac = Ac − {w0 7→c z | z ∈ V }, 0 Ad = Ad − {w0 7→d z | z ∈ V }, ρ0 = ρ[(xi , w0 , ki | 1 ≤ i ≤ n)], Ō =< x1 , ..., xn >, t̄ =< k1 , ..., kn > Como temos um elemento no buf f er de entrada que foi enviado pelo vértice w1 (topo da pilha de chamadas), concluímos que o mesmo respondeu à requisição dentro dos limites estabelecidos. Com isso, excluímos o vértice de saída w0 , as arestas a ele relacionadas, além do elemento correspondente a pilha no monitor M . Vamos agora considerar o caso (ii). Da mesma forma que o caso anterior, esta situação assemelha-se com a situação de falha do Retry, onde não foi detectado o recebimento da resposta dentro do tempo máximo de espera, mas ainda existem tentativas disponíveis para o mesmo serviço. Sendo assim, temos a seguinte redução: 3.3. Substituição do Serviço 59 D E G, ρ, I, O, M [(w : S!K, w0 : S1 ?Ō, TC , NT )], TE , NM T , sendo D E G = V [w0 : S1 ?Ō], Ac , Ad , K = [w1 : S1 !I¯1 ; w2 : S2 !I¯2 ; ... ; wn : Sn !I¯n ], (now() - TC ) > TE , NT > 0, D E ∀t̄. S1 , w1 , t̄ ∈ /I indegree(w0 ) = 0 =⇒ D E G, ρ, I, O[S1 , w0 , Eval(I¯1 )], M [(w : S!K, w0 : S1 ?Ō, now(), NT − 1)], TE , NM T , sendo D E G = V [w0 : S1 ?Ō], Ac , Ad , K = [w1 : S1 !I¯1 ; w2 : S2 !I¯2 ; ... ; wn : Sn !I¯n ] Neste caso, o tempo de espera foi ultrapassado e não existe nenhum elemento no buf f er que seja destinado ao vértice w0 . Sendo assim, temos a ocorrência de uma falha na invocação do serviço S1 . Como NT é maior que zero, ainda há tentativas disponíveis de execução para o vértice do topo da pilha. Com isso, a máquina reinsere as informações correspondentes à invocação de S1 de volta no buf f er de saída O e diminui em uma unidade o número de tentativas restantes para a redução deste vértice (NT − 1). Por fim, consideremos o caso (iii), onde ocorre uma falha na resposta de um serviço sem que haja tentativas restantes para execução do mesmo serviço, mas existem serviços alternativos na pilha. Neste caso, temos a seguinte redução: D E G, ρ, I, O, M [(w : S!K, w0 : S1 ?Ō, TC , NT )], TE , NM T , sendo D E G = V [w0 : S1 ?Ō], Ac , Ad , K = [w1 : S1 !I¯1 ; w2 : S2 !I¯2 ; ... ; wn : Sn !I¯n ], (now() - TC ) > TE , NT = 0, D E ∀t̄. S1 , w1 , t̄ ∈ /I 0 indegree(w ) = 0 =⇒ D E G, ρ, I, O[S2 , w0 , Eval(I¯2 )], M [(w : S!K 0 , w0 : S2 ?Ō, now(), NM T )], TE , NM T , sendo D E G = V [w0 : S2 ?Ō], Ac , Ad , K 0 = [w2 : S2 !I¯2 ; ... ; wn : Sn !I¯n ] Detectando a falha pela não-obtenção da resposta dentro do tempo limite ((now() D E - TC ) > TE e ∀t̄. S1 , w1 , t̄ ∈ / I) e sabendo que não existem mais tentativas disponíveis 60 Capítulo 3. RECUPERAÇÃO DE FALHAS EM PEWS-AM para o referido serviço, precisamos excluí-lo da pilha para que outro entre em execução. Dessa forma, a redução consiste em excluir o vértice do topo da pilha (neste caso, w1 ) e atualizar no monitor M o vértice de saída para que ele corresponda ao próximo serviço a ser executado (o vértice w2 ). Esse processo se segue até que um dos serviços seja executado (o que acarreta na exclusão dos outros serviços restantes da pilha) ou que não haja mais serviços disponíveis para execução (neste momento, não temos nenhuma solução para esse caso). 3.4 Reorganização do Processo A estrutura mostrada pelo Rebinding tem a capacidade de substituir serviços individuais. Entretanto, podem haver casos em que apenas a substituição de um serviço não seja suficiente para resolver um problema de disponibilidade numa composição. Por exemplo, em determinada composição, uma tarefa pode ser realizada pela sequência S1 ; S2 ; S3 ou pela sequência S4 ; S5 . Uma substituição de serviços simples não seria suficiente. Portanto, propomos a utilização da estratégia de reestruturar determinados trechos de uma composição de serviços, através do método Restructure. Para exemplificar uma ação de reestruturação de uma composição de serviços, consideremos a figura 17. Figura 17 – Demonstração do uso do Restructure Na figura 17 temos sete serviços, identificados por Si , onde 1 ≤ i ≤ 7. Ao chegar na execução posterior ao serviço S2 , existe uma escolha, onde quatro serviços encontramse numa zona de cor diferente. As duas sequências de serviços são indicadas por (i), onde temos a sequência S3 ; S4 , e (ii), onde temos a sequência S5 ; S6 . Na semântica da composição, as sequências (i) e (ii) são equivalentes, ou seja, a realização de uma delas poderia satisfazer a execução da composição. Em outras palavras, a composição poderia ser executada de duas formas, obtendo o mesmo resultado: S1 ; S2 ; S3 ; S4 ; S7 , ou S1 ; S2 ; S5 ; S6 ; S7 3.4. Reorganização do Processo 61 Sendo assim, criamos a noção de trechos redundantes e caminhos alternativos. Trechos redundantes são determinados trechos da composição que possuem uma ou mais possibilidades de execução, sendo estas possibilidades os caminhos alternativos. No exemplo acima, o nosso trecho redundante seria o trecho de cor verde, onde há dois caminhos alternativos. Na execução da composição, no momento em que um trecho redundante é encontrado e está pronto para a execução, um dos caminhos alternativos é escolhido para executar. Se um caminho é executado com sucesso, todos os outros caminhos alternativos são descartados. Entretanto, se há a ocorrência de uma falha, todos os vértice pertencentes ao caminho alternativo em execução devem ser excluídos e outro caminho alternativo deve ser posto em execução. O método Restructure é o mais complexo dos apresentados anteriormente. Ele possui uma semelhança com o método Rebinding, pelo fato de usar uma técnica de substituição, porém o Rebinding utiliza substituição de chamadas de serviços, que, na máquina de redução, corresponde à substituição de apenas um vértice, enquanto o Restructure substitui um conjunto de vértices. 3.4.1 Alterações da gramática para o suporte ao Restructure Para que a gramática de PEWS dê suporte ao Restructure, precisamos criar uma nova regra na gramática, a fim de diferenciarmos os trechos normais dos trechos redundantes. Mostraremos abaixo as regras a serem acrescentadas: P ::= ... | ‘{’ P CAlt | ... CAlt ::= ‘%’ P CAlt | ‘}’ Como podemos observar, além da criação de uma nova regra, criamos também um novo símbolo não-terminal, denominado CAlt, em alusão ao termo “Composições Alternativas”. Isto nos permite criar um número ilimitado de composições alternativas, sendo as mesmas separadas pelo símbolo ‘%’. Além disso, a delimitação de um trecho redundante é feita com a utilização de um conjunto de chaves (símbolos ’{’ e ’}’). A nova gramática, após a inserção do Restructure, apresenta-se da seguinte forma (as novas inserções contém um sublinhado): C ::= ‘RETRY’ ‘(’ TE ‘,’ NM T ‘)’ P | P P ¯ Ō) SAlt | S(I, ¯ Ō) | P1 || P2 | P1 ; P2 | [E1 ]P1 + [E2 ]P2 ::= ‘@’ S(I, | E if [E1 ]P1 | unf old | unf olding P1 | ’{’ P CAlt ::= id | n | t | E1 + E2 | E1 - E2 | E1 * E2 | E1 / E2 | − E1 | E1 and E2 | E1 or E2 | not E1 | E1 == E2 | E1 ≤ E2 62 Capítulo 3. RECUPERAÇÃO DE FALHAS EM PEWS-AM | E1 < E2 SAlt ¯ Ō) SAlt | ‘@’ ::= ‘%’ S(I, CAlt ::= ‘%’ P CAlt | ‘}’ 3.4.2 Alterações na Máquina de Redução de Grafos Utilizaremos a figura 18 para que nos mostrar o passo-a-passo da construção desse método. Figura 18 – Exemplo de composição de serviços com caminhos alternativos Inicialmente, temos um conjunto de vértices e de arestas. Existe um vértice Y , a partir do qual saem outras n arestas (arestas de cor vermelha). Estas arestas estão identificadas por ci , com 1 ≤ i ≤ n, que delimita um trecho redundante, cujos vértices estão dentro de uma região de cor verde, e cada um desses caminhos representa uma subcomposição, identificada por Pi , com 1 ≤ i ≤ n. Da mesma forma, ao fim de cada caminho, existe uma aresta que liga o último vértice a outro que está fora do trecho dos caminhos alternativos. Na lógica do Restructure, apenas um dos caminhos precisa ser executado. Surge então um problema estrutural, de acordo com a figura: com a redução do vértice Y , todos os vértices iniciais dos caminhos de c1 a cn seriam vértices mínimos, e não haveria nenhum impedimento para a execução deles. Portanto, caso não ocorresse nenhum problema, todos executariam, o que não estaria em conformidade com o que o Restructure propõe. A primeira solução proposta visava criar um vértice de sincronismo no início de um trecho redundante, onde este vértice só seria reduzido no caso de um caminho alternativo terminar a execução, porém essa solução não trazia resultados muito satisfatórios. Assim como no Rebinding, criamos um vértice composto do tipo pilha. A grande diferença é que esta não seria uma pilha de chamadas de serviços, e sim uma pilha de subcomposições, onde os elementos da pilha não são mais vértices simples, mas sim subcomposições. Utilizando 3.4. Reorganização do Processo 63 os termos já apresentados, a pilha corresponderia a um trecho redundante e cada elemento da pilha corresponderia a uma subcomposição alternativa. Isso é mostrado na figura 19. Figura 19 – Aplicação do Restructure De acordo com a figura 19, o vértice w é uma pilha de subcomposições que possui uma aresta de controle chegando de Y e possui uma aresta de controle para o vértice Z. Podemos inserir diversas subcomposições alternativas sem a criação de arestas adicionais, pois os vértices externos só enxergam a pilha. Tendo como base os fatos apresentados, veremos abaixo como é realizada a transformação de uma pilha de subcomposições alternativas: T J{P1 % P2 % ... % Pn }K = h{w : [P1 , P2 , ..., Pn ], ∅, ∅}i Na transformação acima, criamos um vértice w, que representa a pilha de subcomposições, onde a mesma contém os elementos P1 , P2 , ... Pn , que representam as subcomposições alternativas. Neste momento, não realizamos a transformação de cada uma das composições em vértices e arestas, deixando suas especificações armazenadas na pilha de subcomposições. A semântica de redução da pilha de subcomposições consiste nos seguintes passos: (i) observar a existência de composições alternativas na pilha; (ii) transformar a subcomposição do topo da pilha em vértices e arestas; (iii) realizar n transições nos vértices dessa composição até que se chegue a uma situação onde não existam mais possibilidades de redução. Neste último caso, se não existirem mais possibilidades de redução pelo fato de não haver mais vértices disponíveis, concluimos que toda a subcomposição foi executada, não necessitando mais das outras subcomposições alternativas. Caso contrário, se não há mais possibilidades de execução, porém ainda há vértices disponíveis, concluímos que ocorreu uma falha que não pode ser tratada, fazendo com que o resto da subcomposição seja descartada e outra subcomposição realize o mesmo processo de transformação e redução. 64 Capítulo 3. RECUPERAÇÃO DE FALHAS EM PEWS-AM Dessa forma, a redução correspondente à primeira execução de uma pilha de subcomposições alternativas é mostrada abaixo: hhV [w : [P1 , . . .]]i , ρ, I, O, M, TE , NM T i, sendo indegree(w) = 0 =⇒ hhV [w : [T JP1 K, . . .]], Ac , Ad i , ρ, I, O, M, TE , NM T i, sendo T JP1 K = hV1 , Ac1 , Ad1 i, indegree(w) = 0 Realizada a transformação do topo da pilha em vértices e arestas, é preciso agora reduzir o conjunto de vértices V1 até que não haja mais possibilidade de execução. Isso é feito através de n reduções, cujos detalhes das mesmas serão omitidos nesse momento. Se a redução dos vértices do conjunto V1 for total, ou seja, não existirem mais vértices nesse conjunto, a execução da subcomposição foi realizada com sucesso e não necessitamos mais das outras subcomposições. Dessa forma, temos a seguinte redução para o caso de sucesso: hhV [w : [T JP1 K, . . .]], Ac , Ad i , ρ, I, O, M, TE , NM T i, sendo T JP1 K = hV1 , Ac1 , Ad1 i, hV1 , Ac1 , Ad1 i =⇒∗ h∅, ∅, ∅i, indegree(w) = 0 =⇒ hhV, A0c , A0d i , ρ0 , I 0 , O0 , M 0 , TE , NM T i Observamos a existência de um conjunto de vértice V , onde existe um vértice w que é uma pilha de subcomposições, sendo este um vértice mínimo, cujo topo é a subcomposição P1 . Sabendo que a transformação de P1 resulta no conjunto de vértices e arestas hV1 , Ac1 , Ad1 i e, após n transições, chegamos a h∅, ∅, ∅i, chegamos ao fim da execução da subcomposição. Dessa forma, após a redução, excluímos a pilha de subcomposições (consequentemente, excluimos os vértices e arestas internos a ela) e retiramos as arestas que a mesma tinha em direção a outros vértices. Além disso, após a execução do topo da pilha, teremos a mudança de vários parâmetros da máquina (ρ0 , I 0 , O0 , M 0 ), alterados durante a redução dos vértices pertencentes a V1 . Abaixo, realizamos a redução para o caso de ter ocorrido uma falha em determinado vértice do topo da pilha, onde esta falha não possui solução a ser tomada: 3.4. Reorganização do Processo 65 hhV [w : [T JP1 K, P2 , . . .]], Ac , Ad i , ρ, I, O, M, TE , NM T i, sendo T JP1 K = hV1 , Ac1 , Ad1 i, hV1 , Ac1 , Ad1 i =⇒∗ hV 0 , A0c1 , A0d1 i, V 0 6= ∅, indegree(w) = 0 =⇒ hhV [w : [T JP2 K, . . .]], Ac , Ad i , ρ0 , I 0 , O0 , M 0 , TE , NM T i Na redução, observamos a subcomposição P1 sendo o topo da pilha de subcomposições. Após n transições, chegamos a um ponto onde não há mais possibilidades de redução, entretanto, não temos um conjunto de vértices vazios em V 0 (conjunto de vértices após as reduções). Como não temos mais possibilidades de redução, descartamos o conjunto de vértices restantes relativos a subcomposição P1 . Assim como no caso de sucesso, vemos a alteração dos parâmetros da máquina. Um fator a ser observado é em relação ao ambiente de variáveis ρ0 . Este ambiente pode ter sido alterado por determinados serviços de V1 que tiveram sua execução realizada. Como a máquina de redução não realiza um rollback das ações, podemos ter problemas quando à consistência dos dados. No estudo desses métodos, observamos uma semelhança entre os métodos Rebinding e Restructure. Após uma análise, chegamos à conclusão que o mecanismo Rebinding pode ser representado pelo Restructure, onde podemos considerar subcomposições como sendo simplesmente chamadas de serviços. Dessa forma, surge o questionamento sobre a utilização do Rebinding: por que não utilizar apenas o Restructure? Utilizamos algumas métricas para mostrar que o Rebinding pode ser mais eficiente que o Restrucutre, onde estas métricas englobam o número de vértices e o número de arestas criadas por cada uma das estruturas. Consideremos o caso onde temos apenas uma chamada de serviço. Tanto para o Rebinding quanto para o Restructure, criamos três vértices (entrada de serviços, saída de serviços e o vértice pilha) e uma aresta que liga o vértice de entrada ao vértice de saída. Neste caso, não temos aparentemente nenhuma vantagem na utilização do Rebinding ao Restructure. Porém a medida que o número de serviços cresce, conseguimos perceber a diferença entre estas soluções. Para n serviços alternativos, o Rebinding cria n + 2 vértices e apenas uma aresta. Já no Restructure, criamos 2n + 1 vértices e n arestas. Em relação aos vértices, o crescimento de ambos é linear, porém o Restructure apresenta um valor maior de aumento de vértices à medida que o número de serviços alternativos aumenta. Em relação às arestas, o crescimento do Restructure é linear, enquanto o Rebinding cria apenas uma aresta, independente do número de serviços alternativos especificados. Com isso, chegamos à conclusão de que o uso do Rebinding apresenta um maior nível de eficiência com o aumento do número de serviços alternativos. 67 4 IMPLEMENTAÇÃO Neste capítulo, apresentaremos a descrição da implementação de uma instância da máquina de redução de grafos com a aplicação dos métodos de detecção e recuperação de falhas a partir da máquina abstrata. Daremos uma visão geral sobre o ambiente de implementação e mostraremos a implementação da máquina de redução original, a máquina de redução estendida e a integração da máquina com a ferramenta GraphViz [13], usada para geração gráfica do fluxo de uma composição de serviços. 4.1 A Máquina de Redução Original A estrutura da máquina de redução pode ser dividida em três grandes módulos: módulo de especificação, módulo de transformação e módulo de execução (ou redução). O módulo de especificação tem a função de descrever a gramática de PEWS, que tem a função de verificar se as descrições das composições atendem às regras descritas na mesma. Em um arquivo, chamado Pews.jacc, são descritas todas as construções sintáticas e as respectivas semânticas relacionadas. No anexo deste documento, mostraremos a descrição de toda a gramática de PEWS. O módulo de transformação contém toda a lógica de transformação das especificações PEWS em grafos, definidas na máquina abstrata. Os principais pacotes de códigos relacionados à transformação são os pacotes: pews.parser e pews.grafos. O primeiro pacote, pews.parser, contém as classes responsáveis por fazer as análises léxica e sintática da especificação do usuário. Descrevemos abaixo algumas entidades relacionadas a esse primeiro pacote: • PewsTokens – Descritor dos tokens, que são os caracteres reconhecidos pelas regras da gramática. Este arquivo é gerado automaticamente na construção e compilação da gramática, definida pelo arquivo Pews.jacc; • PewsParser – Responsável por realizar a análise sintática e chamar as construções semânticas relacionadas. Este arquivo também é gerado automaticamente na construção e compilação da gramática; • PewsLexer – Analisador Léxico da composição. A saída desta classe é usada como entrada na classe PewsParser. Diferentemente das anteriores, este arquivo é definido pelo usuário; 68 Capítulo 4. IMPLEMENTAÇÃO • Token – Superclasse abstrata que representa todos os símbolos da gramática, onde todos são considerados um Token. Estes são transformados em objetos concretos quando é encontrado um determinado padrão sintático; • TInt e TString– Classes auxiliares usadas no momento da transformação dos tokens em tipos reais, que são utilizados pela máquina. Estas classes estendem a classe Token. A classe TInt é usada para tipos inteiros e a classe TString é usada para tipos Strings. Figura 20 – Classes TInt e TString Já o pacote pews.grafos é o maior pacote existente. Ele contém a descrição de todos os tipos de vértices, ou seja, nas chamadas semânticas, sempre algum elemento desta classe é utilizado para que a transformação da especificação em grafos seja realizada. As duas classes principais são: Grafo e Vertice, onde ambas estendem a superclasse Token. A classe Grafo é a classe que é chamada em todas as construções da estrutura dos grafos, ou seja, é a chamada semântica de cada padrão sintático referente à construção do grafo propriamente dito (neste caso, não entram a construção de expressões). Ela possui vários tipos de construtores, sendo alguns usados para mais de uma estrutura (por exemplo, sequências e paralelismo). Já a classe Vertice contém as informações gerais sobre todos os tipos de vértices, como o número de arestas de controle, dependência, etc. Muitos vértices apresentam algumas peculiaridades, porém todos possuem os atributos e métodos da classe vértice. Dessa forma, a descrição de cada classe de vértice estende a especificação da classe Vertice. Citaremos brevemente cada uma das classes que representam os vértices: • Chamada – Classe que representa vértices do tipo S!Ē. Seus atributos principais são: private String nomeDoServico; // Nome do serviço de chamada private Expressao expressoes; // Tupla de expressoes Ē 4.1. A Máquina de Redução Original 69 private Variavel variavel; // Variável de saída representada por S?X̄ • Retorno – Classe que representa vértices do tipo S?X̄. Seus atributos principais são: private String nomeDoServico; // Nome do serviço de chamada private List<String> listaDeVariaveis; // Tupla de variáveis de saída X̄ • Soma – Classe que representa vértices do tipo ‘+’ (escolha não-determinística). Todos os elementos que estendem a classe vértice possuem um atributo chamado idhashCodeSoma, que identifica se ele está dentro do âmbito de um vértice ‘+’ (assume o valor -1 no caso de sua execução não estar relacionada com a avaliação de nenhuma expressão). Este valor será o mesmo idhashCodeSoma do vértice ‘+’ correspondente; • Expr – Classe de vértices que representam as expressões. Na máquina, estes vértices são sempre antecedidos de um vértice ‘+’. Seu único atribulo é List<Expressao> listaDeExpressoes, que indica a expressão numérica a ser avaliada para o retorno positivo (true) ou negativo (false); • Expressao – São as expressões propriamente ditas. Esta classe não é um vértice, porém é de grande importância, pois é a classe que representa a chamada semântica de cada padrão sintático referente à construção das expressões (semelhante à classe Grafo); • Mu – Classe que representa os vértices de repetições (Unfolding). • Unfold – Classe que representa os vértices Unfold, que serve como ponto de referência para cópia do subgrafo que está dentro do âmbito de um vértice Unfolding, ou seja, o subgrafo P da construção Unfolding P; Na figura 21, temos a hierarquia com as novas classes apresentadas: O último módulo a ser apresentado é o módulo de execução, que contém toda a lógica de execução da máquina de redução. Os dois pacotes responsáveis por toda essa lógica são: pews.main e pews.maquina. O pacote pews.main contém apenas a classe Main, cuja função é chamar o analisar léxico e o analisador sintático. Já o pacote pews.maquina contém a classe Maquina, que possui um conjunto de atributos e métodos responsáveis pela execução propriamente dita do grafo da composição. Ela é chamada no último passo da recursão da gramática do parser. Em virtude de sua extensão, iremos resumir algumas explicações sobre esta classe. Os atributos dessa classe são: 70 Capítulo 4. IMPLEMENTAÇÃO Figura 21 – Hierarquia de classes do pacote pews.grafos • todosOsVertices – Este atributo é o responsável por guardar todos os vértices da máquina de redução. Considerando a máquina hhV, Ac , Ad i ρ, I, Oi, este atributo corresponde ao elemento V da máquina; • minimals – Atributo que indica quais vértices podem ser avaliados, ou seja, os vértices mínimos; • listaDeOutIn – Atributo que guarda as informações dos buffers de entrada (I) e saída (O) da máquina de redução. Objetos desta classe são criados no momento da execução de vértices de chamada, onde uma nova instância deste objeto é criada para guardar as informações recebidas após a execução de um serviço; • ambiente – Representa o ambiente global de variáveis. No momento em que um serviço é executado, ele gera (ou possivelmente altera) o valor de uma variável no ambiente; • escolhido – Vértice escolhido para a avaliação. Todos os vértices escolhidos para serem avaliados devem estar também presentes no conjunto minimals; • exprCheck – Parâmetro auxiliar que indica o número de vértices avaliados pela máquina de redução; Quando o último passo da recursão é realizado e a máquina de redução é criada, todo o grafo deve estar construído, juntamente com as informações sobre arestas de controle e dependência. A partir daí, o construtor desta classe é chamado. A classe Maquina possui um método print. Este método é o responsável por mostrar todas as informações textuais sobre a situação da máquina no início, durante e no fim de sua execução. As informações gerais que são mostradas em cada chamada deste método são: quantidade de 4.2. A Máquina de Redução Estendida 71 vértices restantes na máquina, variáveis de ambiente (nome e valor) e número de reduções realizadas (relacionadas ao atributo exprCheck). Outro método da classe Maquina é o método run. A chamada para avaliação de vértices é feita dentro deste método. Ele faz a verificação dos vértices mínimos existentes na máquina, e faz a execução para um dos vértices escolhidos. A cada redução de um vértice, o método run é chamado, tornando esta uma tarefa cíclica, até que não existam mais vértices mínimos na máquina. O método que contém toda a semântica de execução da máquina é o método executar, que recebe como parâmetro um vértice, e verifica o tipo do mesmo. A partir da definição da instância do vértice (S!Ē, S?X̄, +, etc), é aplicado um tipo de redução específica. 4.2 A Máquina de Redução Estendida Com a inserção dos métodos de tratamento de falhas, aplicamos algumas mudanças na gramática e na máquina para o suporte a estas alterações. No módulo de especificação, assim como na descrição abstrata da máquina, alteramos a estrutura da gramática de PEWS, da seguinte forma: • Criação de novos tokens, padrões léxicos da gramática, sendo estes: TEMPORESPOSTA (usado para especificar o tempo de resposta como método de detecção de falhas) e STRING (otimização realizada para separar a definição de variáveis e strings). Além disso, outros tokens simples foram acrescentados para delimitar pilha de chamadas (símbolos ‘@’), delimitar pilhas de subcomposições (símbolos ‘{’ e ‘}’) e separar tanto serviços alternativos quanto composições alternativas (símbolo ‘%’); • Inserção de novas regras na gramática, a fim de que a mesma dê suporte à construção de composições utilizando o tempo de resposta como método de detecção de falhas, e dê suporte aos mecanismos de recuperação propostos (Retry, Rebinding e Restructure). Essas estruturas sintáticas serão mostradas com mais detalhes no Apêndice A deste documento; • Inserção de novas chamadas semânticas para novas construções criadas na gramática, cuja estrutura será mostrada no Apêndice A deste documento; No módulo de transformação, algumas mudanças foram automáticas, pois, no momento da compilação da nova gramática, as classes PewsTokens e PewsParser são geradas novamente, dando suporte às novas construções criadas. As grandes mudanças ocorreram no pacote pews.grafos. Na classe Grafo, novos construtores foram inseridos, para dar 72 Capítulo 4. IMPLEMENTAÇÃO suporte à criação dos grafos relacionados com os mecanismos de recuperação. Além das classes já existentes no pacote, foram acrescentadas as seguintes classes (todas estendem a super-classe Vertice): • PilhaDeChamadas – Nova classe de vértice criada para dar suporte às pilhas de chamadas de serviços alternativos (dá suporte ao Rebinding). Estende a classe vértice, sendo seu atributo principal pilhaDeChamadas, que guarda todos os vértices de chamada da pilha (vértices do tipo S!Ē); • PilhaSubcomposicoes – Nova classe de vértice criada para dar suporte às pilhas de subcomposições (dá suporte ao Restructure). Esta classe também estende a classe Vertice. O atributo principal desta classe é subcomposicoes, que guarda todos os vértices das subcomposições alternativas separadamente; • Sync – Este é um tipo especial de vértice que foi criado para resolver um problema relacionado ao paralelismo. No momento em que vamos criar uma pilha de subcomposições, selecionamos todos os vértices mínimos e criamos um caminho alternativo a partir destes vértices. Ocorre que, no momento em que um paralelismo é a primeira construção de uma subcomposição alternativa, poderíamos erradamente considerar cada um dos dois ou mais vértices do paralelismo como um caminho alternativo. Sendo assim, criamos este vértice para que ele seja colocado no início e no final de um paralelismo. Esta classe não possui atributos, sendo criada apenas para facilitar a criação das pilhas de subcomposições. Na figura 22, temos o exemplo de como o vértice Sync ajuda na formação dos caminhos alternativos; Figura 22 – a) Formação do grafo sem o Sync; b) Formação do grafo com o Sync; Com estas inserções, mais classes estenderam a classe Vertice. Sendo assim, a hierarquia de classes que extendem a classe vértice é mostrada na figura 23. O módulo de execução foi o que sofreu as maiores mudanças. Criamos um novo pacote, denominado pews.modulosTratamento. A nossa implementação permite que um desenvolvedor possa utilizar a mesma estrutura da máquina e implementar seu métodos de 4.2. A Máquina de Redução Estendida 73 Figura 23 – Hierarquia de classes do pacote pews.grafos na Máquina Estendida detecção. Para isso, o mesmo terá que implementar apenas as classes de detecção de falhas e nomear o método. Essa identificação deve ser acrescentada na entidade tipoModulo, que pertence também ao pacote pews.modulosTratamento. Como estamos trabalhando com o tempo de resposta, criamos o pacote pews.modulosTratamento.TempoResposta, que comporta as classes de implementação deste método de detecção, onde o mesmo possui as seguintes classes: • TempoResposta – Classe que é utilizada na execução de serviços quando o designer da composição especifica que o mecanismo de detecção utilizado na composição será o tempo de resposta. Esta classe contém as informações padrões de todas as chamadas de serviços, ou seja, o par (TE ,NM T ). Com a utilização do tempo de resposta, todo o processamento das chamadas de serviços é realizado por esta classe. A execução da mesma é simples: é criada uma thread de uma chamada de serviço, sendo a mesma executada e o tempo em que essa tarefa é realizada é guardado numa variável. Logo após, um laço infinito é disparado, sendo as condições de parada do mesmo a execução e envio do sinal de término do serviço ou a detecção do estouro do limite de tempo máximo. Os atributos dessa classe são mostrados abaixo: private int tempoEspera; private int numMaximoChamadas; private BufferedWriter arquivo_saida; • ThreadChamada – Esta classe realiza a chamada a um serviço propriamente dita. Nesta classe (assim também como na classe Chamada) está descrito o código para 74 Capítulo 4. IMPLEMENTAÇÃO chamada de cada serviço, assim como os artifícios para receber as respostas do mesmo. Por fim, as maiores alterações foram realizadas na classe Maquina, que pertence ao pacote pews.maquina. Em relação aos atributos, além dos já existentes, foram acrescentados os seguintes: • tipoTratamentoFalhas – Atributo criado pela inserção dos métodos de tratamento de falhas. Ele é uma entidade do tipo enum que, até o presente momento, pode assumir os valores NENHUM (caso a máquina seja usada sem detecção de falhas) ou TEMPORESPOSTA (caso a máquina seja especificada com o uso do tempo de resposta para detecção de falhas). A construção da máquina foi feita de forma a dar suporte a outros métodos de detecção, sendo que estes devem ser implementados e criado outro valor a ser acrescentado neste atributo (por exemplo, QoS); • moduloTratamentoFalhas – Atributo criado pela inserção dos métodos de tratamento de falhas. É um elemento do tipo Object. O objetivo deste atributo é ser setado com algum módulo de tratamento de falhas (se houver) que deve ser criado pelo usuário. No nosso caso, o único módulo opcional de detecção de falhas é o TEMPORESPOSTA; • geradorFiguras e caracteristicas – Atributos auxiliares, usado pela classe geradora das figuras, citada no Apêndice C deste documento; • exclusoes – Parâmetro auxiliar que indica o número de vértices excluídos, ou seja, aqueles cuja avaliação foi dispensada, como por exemplo, subcomposições alternativas descartadas de uma pilha; • arquivo_saida – Referência para o arquivo de saída, onde são escritas as informações textuais de execução da máquina; Quando o último passo da recursão é realizado e a máquina de redução é criada, todo o grafo deve estar construído, juntamente com as informações sobre arestas de controle e dependência. Atualmente, existem dois métodos construtores para a classe Maquina, sendo eles: public Maquina (Token grafo) {...} public Maquina (Token grafo, Token Te, Token Nmt) { ... } O primeiro construtor é usado quando a composição é feita sem o uso de mecanismos de detecção de falhas, ou seja, o construtor original da máquina. O corpo desse 4.2. A Máquina de Redução Estendida 75 método simplesmente inicializa todos os atributos da classe, e o atributo tipoModulo recebe o valor NENHUM, indicando que nenhum método de detecção de falhas será utilizado. O segundo construtor é usado quando o designer da composição vai utilizar o tempo de resposta como método de detecção. O corpo desse método, em geral, é igual ao primeiro, com a inicialização dos atributos padrões. A diferença é que o atributo tipoModulo receberá o valor TEMPORESPOSTA e os outros valores de entrada do método serão usados no construtor TempoResposta, cuja instância será atribuida ao atributo moduloTratamentoFalhas. As maiores alterações desta classe foram realizadas no método executar. Este método contém toda a lógica de redução conceitual dos vértices. Este método recebe como parâmetro um vértice e, a partir da identificação de seu tipo, aplica a avaliação correspondente. Foram acrescentadas três novas avaliações, relacionadas aos três novos tipos de vértices: • Sync – Sua avaliação consiste apenas em excluí-lo, juntamente com suas arestas, quando o mesmo for um vértice mínimo; • Pilha de Chamadas – A sequência da lógica da avaliação deste vértice consiste em, primeiramente, analisar se a pilha de chamadas é vazia ou não. Vejamos os dois casos abaixo: – Se a pilha não é vazia, tenta-se executar o serviço do topo da pilha. Se ele é executado, todos os outros serviços da pilha, assim como a própria pilha, são excluídos. Se ele falha, o mesmo é retirado da pilha e faz-se novamente a avaliação se a pilha é vazia ou não; – Se a pilha é vazia, é verificado se a mesma faz parte de uma subcomposição alternativa, ou seja, de um caminho alternativo que está sendo executado por uma pilha de subcomposições. A semântica do caso em que isso é verdadeiro faz parte das pilhas de subcomposições. Se isso não é verdade, a composição chega a uma situação onde não há mais soluções a serem tomadas. • Pilha de Subcomposições – A mesma lógica de redução que é usada na pilha de chamadas também é aplicada na pilha de subcomposições. Primeiramente, é observado se a pilha não é vazia. Caso ela não seja, tenta-se executar a composição do topo da pilha. Se ela é executada com sucesso, todas as outras subcomposições são excluídas, assim como o próprio vértice pilha. Se ocorre uma falha, toda a subcomposição é retirada do ambiente, tentando-se executar uma outra composição, se houver. Caso a pilha esteja vazia, verificamos se o vértice pilha de subcomposições não faz parte de um caminho alternativo de outra pilha de subcomposições; A fim de aproveitar todo o código de redução definido pelo método executar, realizamos a avaliação de uma pilha de subcomposições da seguinte maneira: quando 76 Capítulo 4. IMPLEMENTAÇÃO encontramos uma pilha não-vazia, retiramos a subcomposição alternativa do topo e a colocamos no ambiente. Estando esta subcomposição fora da pilha, ela terá vértices mínimos que podem ser executados. Entretanto, a própria pilha também continua sendo um vértice mínimo, podendo ser avaliada novamente. Para que façamos uma nova avaliação da pilha apenas quando ocorrer uma falha na subcomposição que está fora, sendo a mesma excluída, criamos um atributo booleano execute nos vértices do tipo pilha de subcomposições. Para que uma pilha seja um vértice mínimo, além de não possuir arestas de controle chegando, a mesma precisa ter esse parâmetro com um valor false. O significado deste valor é que “não existem subcomposições da pilha sendo executadas”. No momento em que uma pilha é avaliada e retirada uma subcomposição para execução, setamos esse atributo com o valor true, fazendo assim com que este vértice não seja “avaliável”. Todos os vértices possuem atributos chamados idhashCodeSoma e idhashCodeMu. Estes atributos indicam a qual bloco de estruturas + e Mu o vértice pertence (sendo o valor igual a -1, ele não pertence a nenhum bloco). Utilizando esta mesma idéia, criamos um atributo na classe Vertice, chamado idPilhaSubcomposicoes, que indica a qual pilha de subcomposições o vértice está relacionado. Quando uma subcomposição alternativa executa completamente, pegamos a referência da pilha, através deste atributo, e excluímos todas as outras subcomposições relacionadas, além da própria pilha. Da mesma forma, quando ocorre uma falha, excluímos todo o resto da subcomposição e, através desta referência ao vértice pilha, setamos o valor do atributo execute para false, indicando que o mesmo pode ser avaliado. Outro fato é que precisamos de uma forma para observar quando uma subcomposição alternativa chegou a fim, visto que os vértices estarão “soltos”. Quando terminamos a avaliação de cada vértice, verificamos a possível ocorrência do fim de uma subcomposição alternativa, utilizando o parâmetro idPilhaSubcomposicoes. O fim de uma subcomposição alternativa é alcançado no momento em que um vértice que contém um idPilhaSubcomposicoes diferente de -1 é avaliado, reduzido, e não possui arestas de controle para qualquer outro vértice (com exceção do vértice +). Nem todos os vértices precisam passar por essa avaliação, visto que: • Vértices do tipo Chamada e PilhaDeChamadas são sempre precedidos de um vértice do tipo Retorno; • Vértices do tipo Expr tem sua avaliação feita dentro do âmbito de vértices Soma e são sempre seguidos de alguma subcomposição (segundo as construções [E1 ]P1 + [E2 ]P2 ou IF[E]P). • Vértices do tipo Unfolding são sempre precedidos de alguma composição, segundo a construção Unfolding P; 4.2. A Máquina de Redução Estendida 77 Todos os outros vértices (Retorno, Sync,PilhaSubcomposicoes e Unfold) passam por este teste nas avaliações. A única peculiaridade é em relação ao vértice +. De acordo com sua construção, ele sempre terá arestas de controle que saem para vértices do tipo Expr, porém pode ser um vértice que represente o fim de uma composição. Isso ocorre quando, numa subcomposição alternativa, a última construção é do tipo [E1 ]P1 + [E2 ]P2 ou IF[E]P. Vejamos o exemplo de um especificação de composição em PEWS, mostrada na figura 24: {P1 ; IF [x < 3] P2 % P3 % P4 } Figura 24 – Exemplo de final de subcomposição Temos três subcomposições, identificadas por Subcomp1 , Subcomp2 e Subcomp3 . Considerando que a subcomposição do topo da pilha é Subcomp1 , a mesma é retirada para execução. Supomos que P1 execute normalmente, ficando apenas a construção IF [x < 3] P2 . Se x é menor que três, a redução ocorre normalmente, retirando os vértices + e Expr (representado por x < 3) e prosseguindo a execução de P2 . Entretanto, se x ≥ 3, todos os vértices são excluídos, pois não há a necessidade de executar. Porém, chegamos ao final de uma subcomposição alternativa que executou com sucesso, necessitando da exclusão das outras subcomposições alternativas e também do vértice pilha de subcomposições. Sendo assim, para o vértice +, aplicamos o teste de chegada de fim de subcomposição no caso da guarda do vértice Expr ter o valor false (no caso da escolha não-determinística, as duas guardas precisam ser falsas). Além das inserções de novas características referentes ao tratamento de falhas opcionais, inserimos uma nova característica que foi colocada como padrão da máquina de redução: o tratamento do uso de variáveis não-inicializadas. A composição pode fazer uma referência a uma variável que não exista no ambiente. Isso ocorre em vértices de chamada de serviço (quando uma variável não-inicializada é colocada como parâmetro de entrada do serviço) ou em vértices de avaliação de expressões (Expr). Para isso, criamos a classe VariaveisNaoInicializadas, que possui apenas um método booleano, chamado verificarVariaveisNaoInicializadas, que recebe uma lista de parâmetros (inteiros, 78 Capítulo 4. IMPLEMENTAÇÃO strings e variáveis) e o ambiente de variáveis atual, verificando se existe alguma variável que não foi colocada no ambiente. As duas únicas situações onde não foram encontradas soluções foram: (i) condicionais simples fora do âmbito de pilhas de subcomposições e (i) chamadas de serviços foram do âmbito de pilhas de subcomposições ou pilha de chamadas de serviços. Sendo assim, foram implementadas soluções para as seguintes situações: • Escolhas não-determinísticas – Suponha uma escolha do tipo [x<5] P1 + [y<5] P2 , onde x é uma variável não-inicializada e y possui algum valor no ambiente de variáveis. Ao tentar executar a guarda [x<5] e não achar um valor definido para x, a máquina irá excluir a composição P1 , além da guarda [x<5]. É como se considerássemos que a avaliação da mesma retornou um valor false na sua avaliação. Posteriormente, tentaremos avaliar a segunda guarda, onde o valor de y foi definido no ambiente, e obteremos sucesso na avaliação; • Condicionais simples e Chamadas de Serviços dentro de uma pilha de subcomposições – Caso uma variável nesses dois tipos de vértices não estejam inicializadas, consideraremos que houve um erro, e tentaremos a execução de outra subcomposição da pilha relacionada; • Pilha de Chamadas – Numa pilha de chamadas, ao tentar executar um serviço que possua em seus parâmetros de entrada uma ou mais variáveis que não foram inicializadas, excluiremos esse serviço e tentaremos a execução do próximo serviço da pilha de chamadas. Isso é feito porque podem existir serviços na pilha que não necessitem dessas variáveis para execução. Além disso, caso a pilha de chamadas não tenha mais serviços a serem executados, porém a mesma encontra-se dentro de uma pilha de subcomposições, consideramos que ocorreu um erro na subcomposição e excluiremos a mesma, para que outra subcomposição seja executada, dando mais possibilidades de execução para a composição principal; O ambiente usado para a implementação da máquina de redução de grafos foi o Eclipse [7], utilizando a linguagem de programação Java. A escrita das composições de serviços são feitas através de linha de comando, onde a leitura da composição é realizada e traduzida para o formato esperado pela máquina através de um gerador de parsers, chamado Jacc [16] (Just another compiler - compiler for Java). A gramática utilizada foi retirada de [8], sendo que alterações foram realizadas para o suporte aos métodos de recuperação de falhas. 4.3 Otimizações realizadas Além de acrescentar o tratamento de falhas opcional no âmbito das composições PEWS da máquina de redução, realizamos algumas alterações na implementação padrão 4.3. Otimizações realizadas 79 da máquina, para que a mesma ficasse mais robusta. Essas otimizações são mostradas a seguir. 4.3.1 Geração de um arquivo texto A máquina de redução original mostrava informações sobre o estado da composição no início da execução e após a realização de uma redução sempre em linha de comando. Acrescentamos na máquina estendida a geração de um arquivo texto, cujo nome é SAIDA.TXT. Além disso, acrescentamos nesse arquivo informações individuais sobre cada um dos vértices presentes na composição. Vejamos abaixo a descrição de um determinado vértice descrito no arquivo de saída, após o início da execução da composição: Tipo de Vértice: Chamada de Serviço (Nome: getCidadeByName) (i) - Identificacao: 26936546 (ii) - Possui 1 filho(s) de controle-> Saida de Serviço (6147782) (iii) - Não possui nenhuma aresta de controle chegando (iv) - Não possui filhos de dependencia (v) - HashCode de Soma-> -1 O trecho acima mostra a descrição de um vértice de chamada de serviço (S!Ē) cujo nome do serviço é getCidadeByName. As próximas cinco linhas mostram informações deste vértice: (i) identificação única do vértice, (ii) vértices para os quais o vértice de chamada possui uma aresta de controle saindo; (iii) vértices que possuem arestas de controle chegando ao vértice de chamada; (iv) vértices para os quais o vértice de chamada possui uma aresta de dependência; e (v) se o mesmo pertence a um bloco de expressões condicionais (o valor -1 é default no caso do mesmo não pertencer a nenhum bloco). Após a avaliação de cada vértice, é gerado no arquivo a situação atual da máquina após a redução. Um fator também interessante é que na redução de cada vértice, são mostradas informações específicas da redução realizada. Por exemplo, ao realizar a execução de um vértice do tipo S?X̄ cuja identificação é 2804837, considerando que existe apenas uma variável y de saída e que a mesma ainda não existe no ambiente, a redução trará a seguinte resposta: Variável ‘y’ inserida no ambiente Vertice RETORNO (2804837) reduzido 4.3.2 Correção na redução de vértices de blocos Denominamos vértices de bloco aqueles vértices que setam determinados identificadores dos vértices por estarem em seu âmbito, sendo eles vértices: + (seta o parâmetro 80 Capítulo 4. IMPLEMENTAÇÃO idhashCodeSoma), Mu (seta o parâmetro idhashCodeMu) e Pilhas de Subcomposições (seta o parâmetro idPilhaSubcomposicoes). Implementamos as pilhas de subcomposições já com o suporte à redução correta quando havia o aninhamento destes tipos de vértices. Porém, os dois outros tipos (+ e Mu) apresentavam problemas quando havia aninhamento entre blocos de vértices do mesmo tipo. Consideremos a figura 25 para análise. Figura 25 – Exemplo aninhamento de blocos Na especificação, vemos o aninhamento de dois vértices +. O grande problema encontra-se na lógica de exclusão de vértices quando a guarda é falsa, sendo excluídos todos os vértices que possuem o mesmo idhashcodeSoma do + correspondente. Supondo que a guarda y<4 seja falsa, não haveria problemas de exclusão, pois todos os vértices a serem excluídos possuem o mesmo idhashcodeSoma. Entretanto, supondo que a guarda x<3 seja falsa, tinhamos um problema, pois os quatro outros vértices de idhashcodeSoma=2 também deveriam ser excluídos, entretanto não estavam sendo por terem um identificador diferente da guarda que foi tida como falsa. Sendo assim, criamos um novo parâmetro na classe Soma, chamado filhosSoma, sendo este uma lista de “filhos” do tipo +. Os filhosSoma de um vértice +1 são todos os vértices +i que se encontram dentro do bloco de +1 . Na exclusão, são descartados todos os vértices que possuem o mesmo idhashcodeSoma de um vértice +1 , além dos outros vértices que possuem o idhashcodeSoma de um vértice +i , sendo este último pertencente ao conjunto filhosSoma de +1 ; Além disso, a informação sobre o aninhamento também é mostrada no arquivo texto. Os vértices do tipo + mostram uma linha a mais, que indica quais os identificadores dos outros vértices + fazem parte do seu âmbito. O mesmo exemplo da figura 25 com a descrição textual é mostrado na figura 26. 4.3. Otimizações realizadas 81 Figura 26 – Exemplo de saída do arquivo texto com aninhamento de + 4.3.3 Inserção do tipo STRING Na máquina original, não havia diferenciação entre variáveis e strings para o usuário. Isso era apenas identificado pelo serviço que recebia os parâmetros, pois, como é o próprio designer que escreve o código de execução do serviço dentro da máquina, ele sabe se o serviço está recebendo uma variável ou uma string (“código de execução” não significa o “código de implementação” do serviço). Por exemplo, consideremos a chamada de um serviço teste(3;media,x), onde os parâmetros de entrada são 3 e media e o parâmetro de saída é x. Não é possível para o usuário identificar se o parâmetro media é uma variável ou uma string. Sendo assim, quando formos escrever uma composição em que exista uma string como parâmetro de entrada, devemos escrevê-las entre aspas duplas. Dessa forma, se a chamada do serviço anterior tiver media sendo uma string, devemos chamá-lo como sendo teste(3;“media”,x); 4.3.4 Mudança de semântica de Unfolding e Unfold Uma outra alteração que realizamos trouxe impactos na parte conceitual da máquina original. Realizamos uma mudança na redução dos vértices Unfolding e Unfold. Após uma análise, identificamos que o vértice unfold não é um vértice avaliável, porém é colocado como uma construção na gramática de PEWS. Isso pode fazer com que possamos especificar uma construção da seguinte forma: unfold ; unfold. Não teriamos nenhuma semântica para avaliação de unfolds. Sabendo dessa possibilidade, a partir deste momento, a avaliação do Unfolding irá criar uma cópia do grafo e atribuí-la a todos os seus unfolds relacionados sem colocá-la no grafo no momento da avaliação. Já a avaliação do vértice unfold consistirá em expandir para o grafo a cópia que lhe foi atribuída. Criaremos um atributo específico para vértices do tipo unfold, chamado copia, que guardará informações sobre vértices e ares- 82 Capítulo 4. IMPLEMENTAÇÃO tas, ou seja, ao nos referirmos a este atributo com a chamada unfold.copia, receberemos como resposta uma configuração hV, Ac , Ad i. Utilizamos o símbolo ← como um símbolo de atribuição. A nova redução de vértices Unfolding é apresentada a seguir: w hhV [w : µ, w1 : unf oldw 1 , ..., wn : unf oldn ], Ac , Ad i , ρ, I, O, M, TE , NM T i, sendo w Gw = hV w , Aw c , Ad i =⇒ DD 0 E E w V [w1 : unf oldw 1 , ..., wn : unf oldn ], Ac , Ad , ρ, I, O, M, TE , NM T , sendo wi .copia ← Gw , com 1 ≤ i ≤ n, 0 Ac = Ac /{w 7→ y | y ∈ V } Na configuração inicial, temos um vértice w que é do tipo Unfolding (µ). Além disso, temos n vértices do tipo unfold, identificados por w1 a wn . Especificamos que estes vértices estão relacionados ao vértice Unfolding através da identificação sobrescrita w. Além disso, especificamos o grafo G, que é o grafo formado pelos vértices (e arestas relacionadas) que encontram-se dentro do âmbito do Unfolding, ou seja, os vértices a serem copiados. Na redução, o que fazemos é excluir o vértice w : µ do conjunto dos vértices principais (assim como suas arestas de controle, visto que este vértice não possui arestas de dependência para nenhum outro vértice), criar uma cópia do grafo G para cada vértice unf oldw i e atribuí-la ao parâmetro unfold.copia. Já na redução de vértice unfold, temos a seguinte configuração: hhV [w: unf old], Ac , Ad i , ρ, I, O, M, TE , NM T i, sendo w w.copia = hV w , Aw c , Ad i =⇒ DD 0 E E w V ∪ V w , Ac ∪ Aw c ∪ Δ, Ad ∪ Ad , ρ, I, O, M, TE , NM T , onde 0 Ac = Ac /{w 7→ y | y ∈ V }, Δ = {x 7→ y |x ∈ V w , !∃z.x 7→ z, y ∈ V, w 7→ y } Identificamos inicialmente apenas o vértice w:unfold. Além disso, referimo-nos ao w parâmetro unf old.copia como sendo hV w , Aw c , Ad i, uma cópia atribuída a este parâmetro por algum vértice unfold anteriormente reduzido. Na redução, retiramos o vértice unfold e suas arestas. Além disso, o subgrafo associado ao parâmetro copia é colocado diretamente no grafo (vértices, arestas de controle e arestas de dependência). Por fim, acrescentamos à máquina de redução o parâmetro Δ. Este parâmetro acrescenta arestas que saem dos vértices de V w que não possuem filhos (!∃z.x 7→ z) para os vértices aos quais o unfold 4.4. Vizualização de Transformações e Reduções 83 tinha arestas de controle (y ∈ V, w 7→ y). Em outras palavras, substituimos o vértice unfold pela cópia relacionada. 4.4 Vizualização de Transformações e Reduções A otimização realizada pela geração do arquivo texto trouxe ganhos consideráveis, pois temos a noção da situação detalhada dos vértices e da máquina a cada vez que uma redução é realizada. Porém, quando se trabalha com composições extensas, é difícil ter uma noção das ligações entre as arestas de controle e dependência dos vértices. Dessa forma, neste trabalho, tivemos também o objetivo de dar uma noção dos estados da composição após cada ação realizada através de uma estrutura gráfica, gerada a partir de informações da composição. A implementação de estruturas que fizessem essa geração gráfica teria um esforço muito grande e não seria um dos focos principais deste trabalho. Portanto, decidimos procurar uma ferramenta na qual pudéssemos fazer uma integração entre o ambiente da máquina de redução e esta ferramenta. Utilizamos a ferramenta GraphViz [13], que consiste numa variedade de softwares para desenhar grafos, implementando um extenso conjunto de linguagens para geração de imagens, sendo elas: dot [38], neato [14], fdp [12], sfdp [15], circo [36], entre outros. Como trabalhamos com grafos direcionados, utilizaremos a linguagem dot. Além da linguagem, é preciso obter qualquer versão do GraphViz e instalá-la no computador. No anexo deste documento, mostraremos alguns detalhes de utilização do GraphViz. Veremos a seguir como sua utilização beneficiará a vizualização de grafos na geração e execução de composições de serviços. 4.4.1 Uso do GraphViz na Máquina de Redução Estendida A integração da máquina de redução com o GraphViz ocorre da seguinte forma: a cada passo da redução, geramos um arquivo texto. Para a geração deste arquivo, pegamos a situação atual do ambiente e transformamos numa estrutura padrão entendida pelo GraphViz. Em seguida, fazemos a compilação deste arquivo texto e geramos a situação atual do grafo em forma de imagem. Em termos de código Java, criamos uma classe específica para geração do arquivo texto. A mesma está presente no pacote pews.main, sendo denominada GeradorFiguras. Esta classe contém dois atributos: • contFiguras – Geramos um arquivo de imagem para cada situação da máquina. Por isso, precisamos ter nomes de arquivos diferentes, para vizualizarmos a sequência de redução. Sendo assim, utilizamos o parâmetro contFiguras para gerar nomes diferentes de arquivos através de identificação numérica progressiva. Os nomes dos 84 Capítulo 4. IMPLEMENTAÇÃO arquivos gerados tem sempre o padrão: Siti .dot, onde 1 ≤ i ≤ n, sendo n o número de figuras geradas pela execução da composição. • contClusters – Da mesma forma que vários arquivos de imagem precisam ter nomes diferentes, algumas estruturas (pilhas de chamadas e subcomposições) precisam ter uma identificação única. Utilizaremos clusters para representar essas estruturas, onde, assim como o atributo anterior, os nomes dos clusters gerados tem o padrão clusteri , onde 1 ≤ i ≤ n; A classe contém diversos métodos utilizados para gerar as figuras. Os principais métodos são: • GeradorFiguras – Construtor da classe, que apenas tem a função de setar o parâmetro contFiguras com o valor inicial sendo 1; • nomeVerticeImpressao – Esta classe é utilizada para atribuir características aos vértices. A identificação do vértice será sempre o seu identificador único do Java, ou seja, seu hashcode. Os vértices simples tem a seguinte identificação padrão (consideraremos o número 123456 como id do vértice de todos os exemplos): – Entrada de serviço (S!Ē) – 123456 [label= "S!Ē"]; – Saída de serviço (S?X̄) – 123456 [label= "S?X̄"]; – Escolha (+) – 123456 [label= "+",shape=diamond]; – Repetição (Mu) – 123456 [label= "Mu"]; – Unfold – 123456 [label= "Unfold"]; – Sync – 123456 [label= "Sync"]; – Expressão (Expr) – 123456 [label= "Expr",shape=box]; Mostramos abaixo um exemplo da descrição padrão para pilhas de chamadas e pilha de subcomposições (utilizaremos apenas a identificação de pilhas de subcomposições): subgraph cluster0 { vertice1 [atributos]; vertice2 [atributos]; ... verticen [atributos]; label="Pilha de Subcomposições" } 4.4. Vizualização de Transformações e Reduções 85 Um detalhe a ser observado é que, na escrita de pilhas de chamadas e pilhas de subcomposições, identificamos também os vértices que fazem parte dos mesmos. Como sabemos, as pilhas são vértices que contém outros vértices internamente. Sendo assim, precisamos descrever os mesmos dentro dos clusters referentes às pilhas, para que eles apareçam internamente no momento da geração da figura. Essa descrição é feita dentro desta função. • gerarGraficos – É a função principal da classe, que é chamada no momento em que é solicitada uma geração de imagem do grafo. Esta função recebe como parâmetro todos os vértices do ambiente (represntado por todosOsVertices) e um conjunto de atributos a serem colocados nos vértice (cores, formas, etc). Todas as vezes que a função é chamada, criamos um novo arquivo para nomear a imagem gerada, cujo padrão de nomes é Siti , onde é i referencia o parâmetro contFiguras, citado anteriormente. Até agora, apenas criamos o arquivo e inserimos o texto. Precisamos fazer a integração com o GraphViz para geração das imagens. O GraphViz tem uma interface que permite a geração da imagem apenas com a escrita do arquivo, porém, com o arquivo gerado, podemos fazer essa geração através de linha de comando. Sendo assim, com o arquivo gerado, criamos uma instância de um terminal dentro do código java através da classe Runtime. Supondo que o executável dot.exe é uma variável de ambiente e que estamos dentro da pasta, executamos o seguinte comando utilizando runtime.exec() (considerando que o arquivo gerado tenha o nome Siti ): dot Siti -Tgif -Kdot -Tplain Tendo visto os atributos e métodos da classe geradorFiguras, veremos os tipos de figuras geradas. No momento da geração da primeira imagem do grafo, os vértices serão gerados segundo mostrado na figura 32. Como podemos observar, todos os vértices são impressos como uma elipse, que é o tipo padrão. Dos vértice simples, diferenciamos apenas o vértice de escolha (mostrado como um losango) e o vértice que representa expressões (mostrado como um quadrado). Os vértices que representam pilhas de chamadas e subcomposições são mostrados em retângulos verticais, onde estão presentes, respectivamente, a representação dos vértices de chamada de serviços e as subcomposições alternativas. Quando a composição principal começa a executar, as imagens da execução começam a ser geradas. As imagens são geradas logo após a avaliação, ou mesmo antes do término da avaliação, dependendo do vértice que está sendo avaliado. Abaixo, temos as características que podem assumir todos os tipos de vértices durante a execução: 86 Capítulo 4. IMPLEMENTAÇÃO Figura 27 – Forma de Impressão dos vértices • Unfolding e Unfold – Além da cor branca padrão, só assumem a cor vermelha, para o caso de fazerem parte de um condicional cuja execução foi descartada; • Sync – Além da cor branca padrão, ele assume a cor verde sempre quando é avaliado, ou a cor vermelha, quando faz parte de um condicional cuja execução foi descartada; • Escolha (+) – Além da cor branca padrão, ele pode assumir as seguintes cores: amarela, para quando alguma de suas expressões está sendo avaliada; e verde, para o caso da avaliação de uma das guardas assumir o valor true; • Expr – Além da cor branca padrão, ele pode assumir as seguintes cores: laranja, para o caso de alguma variável presente na expressão não ter sido avaliada; verde, para o caso da expressão ter retornado um valor true; ou vermelho, para o caso da expressão ter retornado um valor false; • Chamada de Serviços – Além da cor branca padrão, ele pode assumir as seguintes cores: amarelo, quando a chamada está sendo avaliada; verde, para quando uma chamada foi executada com sucesso; ou vermelho, para quando uma chamada não conseguiu executar mesmo após a aplicação do Retry; 4.4. Vizualização de Transformações e Reduções 87 • Saída de Serviços – Além da cor branca padrão, ele só assume a cor verde, depois de avaliado; • Pilha de Chamadas – Este vértice é um cluster que possui vértice internos (chamadas de serviços). Ele pode assumir 3 cores diferentes: amarelo, quando os serviços internos estiverem sendo avaliados; verde, quando algum dos serviços completar a execução; e vermelho, quando nenhum dos serviços tiver sido executado. No caso deste vértice, adotamos não colorir o interior do mesmo, e sim o contorno externo; • Pilha de Subcomposições – Este vértice também é um cluster que possui composições internas (que também são clusters). Da mesma forma como nas Pilhas de Chamadas, neste tipo de vértice, mudamos a cor da borda externa do cluster. Quando a pilha está sendo avaliada, ela assume uma cor amarela; no momento em que não há mais composições internas a serem executadas, a pilha assume uma cor vermelha; e se alguma composição executou completamente, a pilha assume uma cor verde. As subcomposições da pilha também podem assumir cores: a que está sendo executada assume a cor amarela, além de ser identificada plelo texto EM EXECUÇÃO, atribuido por label="EM EXECUÇÃO". Se uma composição em execução falha, ela assume a cor vermelha. Se ela executa completamente, assume a cor verde. As outras subcomposições da pilha ficam em espera, apenas com a cor padrão, assumindo a cor vermelha se a execução de uma subcomposição foi realizada com sucesso; Para demonstrar a sequência de imagens geradas, vamos propor uma composição e uma possível execução para a mesma na figura 33: @ b(1,x) % a(1,x) % b(1,x) @ ; IF [x<2] a(x+1,x) Criamos dois serviços fictícios: a, um serviço que avalia o parâmetro de entrada e atribui o resultado da avaliação à variável de saída (no caso, x); e b, um serviço qualquer. Vemos de (i) a (iii) a execução da pilha de chamadas de id=29131495. Primeiramente, ela fica sob avaliação (de acordo com (i)) e, ao executar um serviço, tanto os outros serviços da pilha como a própria pilha são descartados (de acordo, com (iii)). Vemos que o contorno da pilha e o vértice do serviço a ficam verdes, indicando que a execução ocorreu da forma esperada. O vértice que contém o serviço b é descartado por não necessitar de sua execução, tendo o resultado desse procedimento mostrado em (iv). Observamos que existem duas arestas pontilhadas saindo do vértice 29131495?X. Essas são arestas de dependência, pois os vértices nos quais elas chegam utilizam-se de alguma variável presente neste vértice (no caso, a variável x). Em (v), temos a redução de um vértice de saída, que insere uma nova variável no ambiente, cujo resultado é mostrado em (vi). Chega-se a uma avaliação condicional do vértice Expr, que avalia a expressão x<2. Como 88 Capítulo 4. IMPLEMENTAÇÃO Figura 28 – Figuras geradas pela execução de uma composição x=1, valor dado pelo serviço a, a avaliação retorna um valor true, e os vértices + e Expr são reduzidos do grafo, e o resultado é mostrado em (viii). Novamente, temos uma avaliação de um serviço a. No parâmetro de entrada, temos a variável x, que possui um valor no ambiente e, na variável de saída, também temos a mesma variável x. Neste caso, o que irá acontecer é o incremento desta variável, visto o parâmetro de entrada ser x+1. Portanto, não haverá inserções de variáveis, mas sim a atualização do seu valor. 89 5 EXPERIMENTAÇÃO Após mostrarmos como foi implementada a máquina de redução com o tratamento de falhas, temos o intuito de realizar um estudo de caso, com o objetivo de validar nossa implementação. Apesar de termos realizado algumas alterações e refinamentos na estrutura original, nosso objetivo maior é mostrar a execução, utilidade e eficiência dos mecanismos de recuperação de falhas. Dessa forma, não será necessário construir uma composição extensa que englobe todas as capacidades da máquina, mas que foque nos mecanismos de recuperação implementados. 5.1 Descrição do Estudo de Caso O nosso estudo de caso é um sistema que mostra informações de tempo de uma cidade. Estas informações incluem: Temperatura, Índice Ultra-Violeta, Umidade, Pressão e Visibilidade. Supomos a existência de serviços que produzam essas informações. Além disso, acrescentamos mais dois tipos de serviços: (i) um serviço de localização, onde o usuário pode buscar a referência de uma localidade através do nome ou de suas coordenadas geográficas; e (ii) um serviço que mostra as informações de tempo para o usuário de forma gráfica ou no modo textual. Para usarmos as estruturas que dão suporte ao tratamento de falhas, vamos criar a composição considerando o seguinte fluxo de execução: 1. Na entrada de dados, o usuário pode se utilizar de duas formas de entrada: (i) repassando o nome da cidade ou (ii) repassando as coordenadas geográficas da cidade. Cada uma dessas formas de entrada pode ser realizada por um serviço, sendo eles: CidadeNome, que contém uma operação chamada buscarCidadeNome; e CidadeCoord, que contém uma operação chamada buscarCidadeCoordenadas; 2. Tendo sido realizada a localização da cidade por alguma das operações citadas acima, precisamos obter os dados referentes às medidas temporais da cidade escolhida. Para isso, existem seis Serviços Web. Todas as operações dos serviços recebem um valor inteiro, que será a referência da cidade escolhida pelo usuário. A figura 34 mostra os identificadores dos serviços e suas operações. 3. Tendo sido coletados os dados referentes à solicitação do usuário, os mesmos agora precisam ser mostrados ao mesmo. Podemos mostrar a resposta ao usuário de duas formas: de uma forma gráfica, utilizando o serviço RespostaUsuarioFigura, que 90 Capítulo 5. EXPERIMENTAÇÃO Figura 29 – Lista de operações dos Serviços Web contém uma operação mostrarFigura; e de uma forma textual, utilizando um serviço RespostaUsuarioTexto, que contém uma operação mostrarTexto (informação na linha de comando). A forma gráfica é mostrada na figura 29: Figura 30 – Resposta gerada pelo Serviço RespostaUsuarioFigura O sistema é executado da seguinte forma: o usuário entra com um determinado dado para um serviço de localização; este serviço, ao encontrar a cidade relacionada ao dado de entrada, repassa a saída para um ou mais serviços que irão buscar os dados temporais referentes à cidade; ao encontrar esses dados, estes são repassados para um outro serviço que mostrará ao usuário a resposta de sua solicitação 5.2 Especificação da Composição em PEWS Vamos especificar uma composição em PEWS, observando os novos mecanismos inseridos. Utilizaremos duas informações de busca: Temperatura e Índice Ultra-Violeta. Vejamos a especificação abaixo: 5.2. Especificação da Composição em PEWS 91 TEMPORESPOSTA (x,y) @buscarCidadeNome("Natal",idCidade) % buscarCidadeCoord("1,1",idCidade)@ ;} {s1Temperatura(idCidade,temperatura) || s2IUV(idCidade,iuv) % s3Temperatura(idCidade,temperatura) || s4IUV(idCidade,iuv) % s5Temperatura(idCidade,temperatura) || s6IUV(idCidade,iuv) % s1Temperatura(idCidade,temperatura) || s6IUV(idCidade,iuv) }; @mostrarFigura(temperatura;iuv,status) % mostrarTexto(temperatura;iuv,status)@}? Primeiramente, declaramos o termo TEMPORESPOSTA (x,y), que indica a forma de detecação de erros. Para cada serviço, a máquina vai esperar um intervalo de x segundos para receber a resposta, e tentará a conexão com o serviço por y vezes após a detecção de uma falha. Logo após, especificamos uma pilha de chamadas, que contém os serviços buscarCidadeNome e buscarCidadeCoord. Eles recebem tipos de entradas de dados diferentes, entretanto, retornam a mesma resposta. Como os serviços estão em uma pilha de chamadas, apenas um deles irá executar. O elemento do topo de uma pilha de chamadas sempre corresponde à operação mais a esquerda, no caso buscarCidadeNome. Se o mesmo é executado com sucesso, a operação buscarCidadeCoord é descartada, assim como a pilha de chamadas. Se ele falha, o mesmo é retirado da pilha e a execução de buscarCidadeCoord é utilizada. O resultado da execução de uma dessas operações é guardada na variável idCidade. Em seguida, passamos a saída para os serviços responsáveis em buscar as informações solicitadas pelo usuário. Anteriormente, citamos seis serviços. Alguns são específicos de algumas informações e outros apresentam algumas capacidades a mais. Como não existe nenhum serviço que retorne as duas informações solicitadas de uma vez só, precisamos utilizar dois serviços. Como eles são independentes, iremos utilizar uma execução paralela de dois serviços escolhidos. Várias combinações podem ser realizadas, como: Servico1 || Servico2 Servico3 || Servico4 Servico5 || Servico6 Servico1 || Servico6 Qualquer uma das combinações acima retorna o resultado esperado pelo usuário. Utilizaremos uma pilha de subcomposições para especificar o paralelismo entre as várias combinações entre esses serviços. Estando em uma pilha, apenas uma delas irá executar. Se a composição do topo é executada com sucesso, todas as outras subcomposições são descartadas, assim como a pilha de subcomposições. Se em sua execução ocorre alguma falha, os vértices restantes são excluídos e subcomposição é colocada em execução. Ao encontrar os valores de temperatura e índice ultra-violeta, é preciso mostrar esses dados ao usuário. Criamos dois Serviços Web que podem realizar essa tarefa: RespostaUsuarioFigura e RespostaUsuarioTexto. O serviço RespostaUsuarioFigura possui uma operação mostrarDadosFigura, que mostra o resultado da solicitação numa janela a parte. 92 Capítulo 5. EXPERIMENTAÇÃO Utilizando a integração com o GraphViz, no momento da execução da composição, é gerada uma imagem que é mostrada na figura 31. Figura 31 – Geração da imagem da composição pelo GraphViz A máquina indica a criação de 5 vértices: duas pilhas de chamadas (um dos serviços de localização e outra dos serviços que mostram o resultado ao usuário), dois vértices de retorno, relacionados a estas duas pilhas, e uma pilha de subcomposições alternativas. Apesar disso, a quantidade de vértices é maior. A duas pilhas de chamadas possuem dois vértices internos. Como as pilhas também são vértices, com as pilhas de chamadas, temos 6 vértices (somando com seus respectivos vértices de respostas, temos 8 vértices). Na pilha de subcomposições, temos quatro subcomposições alternativas, cada uma com 6 vértices cada, ou seja, 24 vértices. Além disso, a própria pilha também é um vértice ou seja, 24+8+1 totalizam 33 vértices. 5.3 Casos de Teste Para a execução da composição e análise da eficácia das estruturas implementadas, vamos propor um conjunto de casos de teste para a composição criada na seção anterior. Os casos de testes precisam contemplar: o atraso na resposta de serviços, a troca de um serviço errôneo por outro serviço equivalente, e a troca de uma subcomposição em que ocorreu uma 5.3. Casos de Teste 93 falha por uma outra subcomposição que realize a mesma tarefa. Utilizaremos, para todos os casos, TEMPORESPOSTA (3,3). Para simulação do atraso na resposta de um serviço, criamos um arquivo, chamado TempoEspera.txt. O texto do arquivo contém os seguintes dados: getCidadeByName 2 getCidadeByCoord 0 s1getTemperatura 4 s2getIuv 1 ... s6getIuv 4 O intuito da criação deste arquivo é definir um determinado tempo de atraso na resposta das operações chamadas pela composição. Para cada operação, temos um determinado valor de atraso. Além disso, queremos que, em tempo de execução, esses valores possam ser alterados, para simular casos onde o Retry é benéfico à composição. Sendo assim, no código de todas as operações dos serviços, colocamos um código que abre o arquivo, coleta a informação do tempo de atraso, e chama a função Thread.sleep, que simula um tempo de espera dentro do código, cujo trecho é mostrado abaixo: FileReader arq = new FileReader(".../TempoEspera.txt")); BufferedReader arqTempo = new BufferedReader(arq); String tempo = arqTempo.readLine(); ... Thread.sleep(x*1000); Dessa forma, podemos agora mostrar os casos de testes para testarmos a eficiência da máquina de redução: • Caso 1: – Neste primeiro caso, vamos ver a utilidade e eficiência do Retry. Propomos a execução normal da composição, exceto na operação buscarCidadeNome. Num primeiro momento, daremos um atraso para que o mesmo não execute nas primeiras tentativas. Logo após, faremos com que o mesmo execute normalmente, fazendo assim com que a operação alternativa buscarCidadeCoord seja descartada. Todas as outras execuções devem ocorrer de forma correta, ou seja, na pilha de subcomposições, as chamadas às operações s1Temperatura e s2IUV devem ser executadas com sucesso, acarretando na exclusão das outras subcomposições equivalentes. Da mesma forma, a operação mostrarFigura deve ocorrer com sucesso, tendo como consequência a exclusão da operação mostrarTexto. • Caso 2: – Pretendemos ver a utilidade e eficiência do Rebinding. Vamos simular uma situação em que haja o estouro de tentativas em relação à operação buscarCidadeNome, 94 Capítulo 5. EXPERIMENTAÇÃO onde haverá a substituição do serviço referente a esta operação pelo serviço CidadeCoord, que possui a operação buscarCidadeCoord. • Caso 3: – Neste terceiro caso, vamos ver a utilidade, eficiência e níveis de inconsistência do Restructure. Este tem a semântica semelhante ao Rebinding, mas é aplicado a composições, e não a serviços individuais. Simularemos duas situações: uma onde há um erro no início da execução da subcomposição alternativa e outro onde há um erro no final da subcomposição, quando determinados processamentos já tiverem sido realizados. Como não há a possibilidade de desfazer processamentos, veremos como o resultado final da execução da composição é afetado. • Caso 4: – Neste último caso teste, vamos propor a utilização de todas as estruturas de uma só vez. Na primeira pilha de chamada, simularemos o estouro de tentativas da operação buscarCidadeNome. Na pilha de subcomposições, simularemos erros em duas subcomposições alternativas, uma no início da subcomposição e outra no final da subcomposição; por fim, na última pilha de chamadas, daremos um atraso na execução da operação mostrarFigura, que executará normalmente na segunda tentativa. 5.4 Resultados obtidos Após termos dado uma composição de serviços e proposto um conjunto de casos de testes para sua execução, vamos verificar os resultados obtidos e analisar o quanto as estruturas criadas podem ser úteis no tratamento de falhas ocorridas nas composições. Inserimos também códigos para o cálculo do tempo gasto na execução de cada uma das estruturas. 5.4.1 Caso 1 No primeiro caso, propomos uma falha na primeira tentativa de execução da operação buscarCidadeNome, que pertence ao serviço Servico1. Atribuiremos um atraso na resposta desse serviço e vamos supor que, nessa chamada, devido a uma sobrecarga momentânea, o serviço respondesse em 8 segundos. Sendo este tempo muito maior do que o tempo de espera, a primeira tentativa não terá sucesso e a operação não será considerada. Já na segunda tentativa, não colocamos nenhum atraso na comunicação com o serviço, simulando que o mesmo não está mais sobrecarregado. Alguns dos trechos do arquivo de saída são os seguintes: Início da composição em 19:01:22 ... Temporizador disparado em 19:01:22. 1a tentativa... Tempo esgotado na 1a tentativa. Temporizador disparado em 19:01:25. 2a tentativa... Serviço "buscarCidadeNome"(33172286) executado com sucesso em 19:01:26. ... 5.4. Resultados obtidos 95 Fim da composição em 19:01:33 Na primeira tentativa, foi detectado um atraso no tempo de resposta do serviço, sendo esta tentativa abortada. Já na segunda tentativa, vemos que o serviço respondeu à solicitação em cerca de 1 segundo apenas. Em outras palavras, com o uso do Retry, tivemos um gasto de 4 segundos na resposta da operação buscarCidadeNome (3 segundos na primeira tentativa que foi abortada, e cerca de 1 segundo nesta nova tentativa). Se executássemos a composição sem o uso do Retry, teríamos um gasto de cerca de 8 segundos na execução da operação buscaCidadeNome. Sendo assim, tivemos um ganho de 4 segundos, mesmo com duas chamadas, na execução da operação buscarCidadeNome. Isso influi também no tempo total de execução da composição, que foi de 11 segundos (sem o uso do Retry, esse tempo iria ser de cerca de 15 segundos). Ainda utilizando este primeiro caso, vamos supor atrasos que impeçam a execução tanto na primeira quanto na segunda execução, sendo estes atrasos de 4 segundos cada. Obtemos o seguinte resultado no arquivo texto: Início da composição em 20:10:37 ... Temporizador disparado em 20:10:38. 1a tentativa... Tempo esgotado na 1a tentativa. Temporizador disparado em 20:10:41. 2a tentativa... Tempo esgotado na 2a tentativa. Temporizador disparado em 20:10:44. 3a tentativa... Serviço "buscarCidadeNome"(33172286) executado com sucesso em 20:10:44. ... Fim da composição em 20:10:50 Consideremos o tempo a partir da primeira tentativa até o momento da execução da operação buscarCidadeNome. De acordo com o arquivo de saída, esse tempo foi de 6 segundos. Entretanto, especificamos que o atraso na primeira tentativa de execução do serviço seria de 4 segundos. Sem o uso do Retry, teríamos conseguido executar o serviço em um espaço de tempo menor que o espaço usando o Retry. Dessa forma, concluímos que, em determinados casos, o uso do Retry pode trazer benefícios à eficiência da composição, enquanto que, em outros casos, o uso pode tornar a composição um pouco mais lenta. 5.4.2 Caso 2 Vamos considerar agora o estudo de caso 2, onde queremos ver a utilidade do uso do Rebinding. Vamos propor falhas na comunicação com a operação mostrarFigura, que pertence à pilha de chamadas que está localizada no final da composição principal. Faremos com que haja o estouro nas tentativas de execução desta operação. Como ela encontra-se dentro de uma pilha de chamadas, haverá a substituição desta operação por um outra (caso a pilha não seja vazia). 96 Capítulo 5. EXPERIMENTAÇÃO Ao realizar a execução da composição com a inserção desta falham, mostramos abaixo alguns trechos especificados pelo arquivo de saída: Início da composição em 20:33:15 ... Temporizador disparado em 20:33:26. 1a tentativa... Tempo esgotado na 1a tentativa. Temporizador disparado em 20:33:29. 2a tentativa... Tempo esgotado na 2a tentativa. Temporizador disparado em 20:33:32. 3a tentativa... Tempo esgotado na 3a tentativa. Não foi possível executar o serviço "mostrarFigura". Procurando soluções Removendo o vertice Chamada de Serviço - 1397168 Temporizador disparado em 20:33:33. 1a tentativa... Serviço "mostrarTexto" (33172286) executado com sucesso em 20:33:35. Fim da composição em 20:33:35 Temos o tempo onde foi iniciada a execução da composição (desconsideramos os tempos das outras execuções, as quais foram realizadas todas com sucesso). Logo após, chegamos às tentativas de executar a operação buscarFigura. Após três tentativas, não obtivemos sucesso e, dessa forma, precisamos descartar esse serviço, em virtude da demora na sua execução. A máquina procura alternativas para continuar a execução. Como este serviço faz parte de uma pilha de chamadas, a máquina verifica se a pilha esta vazia. Se ainda existem serviços a serem utilizados, ela retira o serviço do topo da pilha e começa a execução deste (no caso, o serviço RespostaUsuarioTexto, cuja operação relacionada é mostrarTexto). A execução desta operação ocorre logo na primeira tentativa. Como a pilha o último vértice da composição, chegamos ao final da mesma. O tempo gasto na execução desta pilha, com o uso do Rebinding, foi de 9 segundos. Supondo que a operação buscarFigura demorasse um bom tempo para executar, o Rebinding serviu para dar alternativas à execução da composição, uma vez que, sem seu uso, teríamos que esperar um tempo indeterminado para a finalização da execução por causa de um atraso pontual na composição. 5.4.3 Caso 3 Vamos agora analisar o estudo de caso 3. Mostraremos agora a utilidade e eficiência do uso do Restructure. Temos o uso deste mecanismo nos serviços que retornam informações temporais sobre as cidades. Criamos uma pilha de subcomposições que contém quatro subcomposições (uma composição principal e três subcomposições alternativas), sendo que cada uma delas possui o paralelismo de dois serviços, visto que nenhum deles tem a capacidade de enviar toda a informação de uma só vez. Utilizaremos dois cenários nesse caso: no primeiro, a falha 5.4. Resultados obtidos 97 ocorre logo no início da execução da subcomposição, onde nenhum processamento foi realizado, tendo a exclusão de vários vértices; já no segundo simularemos a falha no final da subcomposição, onde determinados processamentos foram realizados. No primeiro cenário, vamos simular um erro na execução da operação s1Temperatura. Vejamos trechos importantes do arquivo de saída: Início da composição em 17:07:26 ... Temporizador disparado em 17:07:37. 1a tentativa... Tempo esgotado na 1a tentativa. Temporizador disparado em 17:07:41. 2a tentativa... Tempo esgotado na 2a tentativa. Temporizador disparado em 17:07:46. 3a tentativa... Tempo esgotado na 3a tentativa. Não foi possível executar o serviço "s1Temperatura". Procurando soluções Removendo o vertice Chamada de Serviço - 9992755 Removendo o vertice Saida de Serviço - 8304354 Removendo o vertice Sync - Saida - 18403721 Removendo o vertice Chamada de Serviço - 6586390 Removendo o vertice Saida de Serviço - 1397168 ... Temporizador disparado em 17:07:51. 1a tentativa... Serviço "s3Temperatura" (14397555) executado com sucesso em 17:07:52. ... Temporizador disparado em 17:07:52. 1a tentativa... Serviço "s4IUV" (10618321) executado com sucesso em 17:07:53. ... Fim da composição em 17:07:54 Após encontrar a referência da cidade desejada através da operação buscarCidadeNome, encontramos a pilha de subcomposições. A primeira subcomposição disponível contém um paralelismo entre as operações s1Temperatura e s2IUV. Tentamos executar a s1Temperatura antes que s2IUV seja executado. Depois de três tentativas, vemos que não obtivemos sucesso na conexão com este serviço. Isso acarreta na exclusão de toda a subcomposição, sendo excluídos 5 vértices. Como nenhum processamento foi realizado, o ambiente de variáveis continua o mesmo de antes da execução da subcomposição. Posteriormente, retiramos outra composição para a execução. Obtivemos sucesso em ambos os serviços que buscam a informação para o usuário. Vemos que a execução da composição ocorreu em 28 segundos. Considerando ainda o mesmo erro, vamos propor uma falha em s1Temperatura. Porém, 98 Capítulo 5. EXPERIMENTAÇÃO antes da detecção da ocorrência da falha, vamos supor que a operação s2IUV tenha sido executada e tenha alterado o ambiente de variáveis, inserindo a variável iuv no ambiente. Após a execução, temos as seguintes informações: Início da composição em 17:35:18 ... Temporizador disparado em 17:35:27. 1a tentativa... Serviço "s2IUV" (20955323) executado com sucesso em 17:35:28. ... Temporizador disparado em 17:35:29. 1a tentativa... Tempo esgotado na 1a tentativa. Temporizador disparado em 17:35:33. 2a tentativa... Tempo esgotado na 2a tentativa. Temporizador disparado em 17:35:38. 3a tentativa... Tempo esgotado na 3a tentativa. Não foi possível executar o serviço "s1Temperatura". Procurando soluções Removendo o vertice Chamada de Serviço - 6586390 Removendo o vertice Saida de Serviço - 1397168 Removendo o vertice Sync - Saida - 18403721 ... Temporizador disparado em 17:35:42. 1a tentativa... Serviço "s3Temperatura" (10272673) executado com sucesso em 17:35:43. ... Temporizador disparado em 17:35:43. 1a tentativa... Serviço "s4IUV" (2124186) executado com sucesso em 17:35:44. ... Fim da composição em 17:35:45 Temos a mesma situação do cenário anterior, porém, com a execução da operação s2IUV ocorrendo antes da falha. Observamos que temos um número menor de remoções de vértices que o cenário anterior. Como houve a execução de uma operação da subcomposição antes da ocorrência da falha, houve a criação de um variável no ambiente. Como falamos anteriormente em outras seções, a máquina de redução estendida não dá suporte à compensação de procedimentos realizados antes de falhas, ou seja, não há como remover a variável do ambiente. Entretanto, com a exclusão dos vértices e da subcomposição falha, outra subcomposição entra em execução (paralelismo entre as operações s3Temperatura e s4IUV). Quando a execução desta subcomposição termina, a variável temperatura é inserida no ambiente. Em relação à variável iuv, esta já existe. Entretanto, ela é setada com o valor dado pela operação s4IUV. Nessa situação, vemos que não houve incosistências no resultado final de execução da composição, pois apenas a variável já havia sido criada, e foi apenas alterada. 5.4. Resultados obtidos 99 5.4.4 Caso 4 Por fim, no estudo de caso 4, propomos pelo menos uma falha em cada uma das três estruturas presentes na composição principal. Na primeira subcomposição, vamos supor um atraso momentâneo na operação buscarCidadeNome, que é executada em outra tentativa. Já na pilha de subcomposições, vamos colocar atrasos nas operações s2IUV e s3Temperatura, para que as mesmas não consigam executar dentro do número de tentativas máximas. Da mesma forma, vamos colocar um atraso na operação mostrarFigura, para que a mesma não execute e seja substituída pela operação mostrarTexto. Após a execução com esses parâmetros, obtemos os seguintes resultados no arquivo texto (omitimos algumas partes pela extensão do arquivo): Início da composição em 19:21:34 ... Temporizador disparado em 19:21:34. 1a tentativa... Tempo esgotado na 1a tentativa. Temporizador disparado em 19:21:37. 2a tentativa... Serviço "buscarCidadeNome" (30967688) executado com sucesso em 19:21:37. ... Temporizador disparado em 19:21:39. 1a tentativa... Serviço "s1Temperatura" (10651400) executado com sucesso em 19:21:39. ... ERROS NA PRIMEIRA E SEGUNDA SUBCOMPOSIÇÃO ... Temporizador disparado em 19:21:58. 1a tentativa... Serviço "s5Temperatura" (14500383) executado com sucesso em 19:21:59. Temporizador disparado em 19:22:00. 1a tentativa... Serviço "s6IUV" (18328955) executado com sucesso em 19:22:00. ... Temporizador disparado em 19:22:01. 1a tentativa... Tempo esgotado na 1a tentativa. Temporizador disparado em 19:22:04. 2a tentativa... Tempo esgotado na 2a tentativa. Temporizador disparado em 19:22:07. 3a tentativa... Tempo esgotado na 3a tentativa. Não foi possível executar o serviço "mostrarFigura". Procurando soluções Temporizador disparado em 19:22:10. 1a tentativa... Serviço "mostrarTexto" (21247461) executado com sucesso em 19:22:10. ... Fim da composição em 19:22:10 O tempo total de execução da composição foi de 26 segundos. Na primeira pilha de chamadas, com uma tentativa falha executada pela operação buscarCidadeNome, e com sucesso 100 Capítulo 5. EXPERIMENTAÇÃO na segunda tentativa, foram gastos um pouco mais de 3 segundos (a segunda chamada teve a resposta quase que instantânea). Já na pilha de subcomposições dos serviços de informações das cidades, com erros tanto na primeira subcomposição (ocorrendo no fim da execução da subcomposição) quanto na segunda subcomposição (ocorrendo no início da subcomposição) foram gastos 21 segundos. Já na pilha de chamadas que mostram o resultado ao usuário, com falhas na operação mostrarFigura, tivemos um gasto de 9 segundos. 5.5 Conclusão dos Experimentos Após termos inserido alguns casos de testes na implementação da máquina, iremos fazer algumas considerações sobre os resultados obtidos e concluirmos em quais casos o uso de cada uma das estruturas é benéfico à composição. No Caso Teste 1, propomos dois cenários: no primeiro, com o Retry, tivemos um ganho no tempo total de execução da composição, em virtude do grande atraso que teríamos na espera da primeira execução do serviço; já no segundo, realizamos algumas tentativas que atrasaram o tempo total de execução da composição. Nesses dois casos, vemos que a escolha do valor do tempo de espera pela resposta de um serviço tem uma importância elevada, pois a escolha de valores inapropriados podem fazer com que tenhamos uma perda de eficiência na execução da composição como um todo. Como o valor da especificação é aplicado a todos os serviços, é necessário realizar um estudo sobre o tempo médio de resposta de todos os serviços e calcular um valor médio de espera para que haja o maior ganho possível de eficiência na execução. No Caso Teste 2, propomos um cenário em que um determinado serviço não realizasse a execução de uma solicitação do usuário. Na indisponibilidade do mesmo, foi necesário a troca deste serviço por outro que realizasse a mesma tarefa. Não colocamos aqui os critérios pelos quais nos baseamos para escolher a ordem dos serviços. Um dos motivos dessa escolha é que, por exemplo, o serviço em stand-by tem critérios menos atratíveis ao usuário. Entretanto, em relação às prioridades, a maior delas é a execução de uma composição. A indisponibilidade do primeiro serviço poderia fazer com que a mesma não executasse se não houvessem medidas alternativas a serem tomadas. Sendo assim, independente do tempo ou do custo de execução da composição, o Rebinding dá robustez à composição por permitir a troca e a continuação da execução de uma composição na ocorrência de uma falha de um serviço individual. Em alguns casos, um tempo de espera maior um maior número de tentativas poderia acarretar na execução do primeiro serviço, trazendo um custo menor para o usuário. Mesmo assim, o Rebinding dá a capacidade de mais soluções alternativas para a composição. No Caso Teste 3, injetamos falhas na execução de serviços que estavam em uma subcomposição alternativa de uma pilha de subcomposições. Neste caso, utilizamos dois cenários: no primeiro, a falha ocorria logo no início da execução da subcomposição, enquanto no segundo caso, a falha ocorria em algum serviço próximo ao fim da subcomposição. Com a falha ocorrendo no início da subcomposição, e não havendo mais soluções a serem aplicadas (Retry e Rebinding), temos de excluir este e os outros vértices restantes da subcomposição. O número 5.5. Conclusão dos Experimentos 101 de vértices excluídos é bem maior do que a ocorrência de uma falha no final da subcomposição, entretanto, como nenhum processamento tinha sido realizado, não haverá inconsistências no resultado final com a troca de subcomposição. Já no segundo caso, com a falha ocorrendo no fim da subcomposição, o número de vértices a serem excluídos é bem menor, pois muitos foram executados e reduzidos. Entretanto, surge o problema da possível inconsistência em virtude de não ser possível realizar um roll-back. O ponto inicial de execução é retornado, entretanto, o processamento realizado não é desfeito. Na composição utilizada, não houve perdas, nem inconsistências, pois estavamos trabalhando apenas com consultas. Porém, se existissem serviços que estivessem alterando informações que necessitassem ser desfeitas pela ocorrência de uma falha, teríamos resultados inesperados e muitas inconsistências no final da execução da composição. Sendo assim, o uso de subcomposições alternativas proporciona mais possilibidades do término da execução principal, mas outras falhas podem surgir, dependendo do processamento realizado antes da ocorrência da falha. Portanto, é preciso saber em quais momentos podemos utilizar subcomposições alternativas sem causar prejuízos ao resultado final das composições. Por fim, o Caso Teste 4 utilizou todos os mecanismos de recuperação de falhas propostos. Sendo assim, trazemos também os benefícios e inconsistências dos três métodos. Para o uso desses mecanismos, temos alguns custos na criação das estruturas. Com o tempo de resposta como critério de detecção, criamos um temporizador para cada execução de serviço. No ambiente utilizado para execução, não existe um “paralelismo real”, tanto entre serviços quanto entre um serviço e o temporizador. Na proximidade entre os tempos de espera e de tempo execução de um serviço, pode acontecer que várias execuções tenham resultados diferentes: (i) um serviço pode executar antes do temporizador e enviar um sinal de executado para a máquina, ou (ii) um temporizador executa antes do serviço e envia um sinal para abortar sua execução. Por fim, temos um conjunto de soluções de recuperação que podem levar à execução completa da composição. Entretanto, existem alguns custos de eficiência e inconsistência que devem ser analisados para se chegar a uma conclusão sobre a viabilidade do uso dos mecanismos em determinados pontos da composição. 103 6 CONCLUSÃO Neste trabalho, apresentamos uma proposta de extensão de um ambiente de execução de composições de serviços com recuperação de falhas para uma variante da linguagem PEWS, cuja proposta foi implementada num ambiente real de execução de composições de Serviços Web, a máquina de redução de grafos PEWS-AM. O processo baseou-se em mecanismos que foram descritos em alguns trabalhos, citados no capítulo 2 deste documento. Estes trabalhos abordam diferentes formas de tratamento. A partir da revisão bibliográfica sobre as diferentes abordagens, escolhemos alguns mecanismos que cobrissem uma parte significante de falhas ocorridas em tempo de execução de composições de Serviços Web, abordando o tratamento de falhas de disponibilidade, sendo que os mecanismos de recuperação adotados foram: Retry, Rebinding e Restructure, que fazem, respectivamente, várias tentativas de conexão com o serviço, reconexão com outro serviço e substituição de trechos da composição principal por trechos alternativos. Para que pudéssemos dar suporte a estes métodos, realizamos mudanças tanto na variante de PEWS quanto na estrutura da máquina abstrata. Em PEWS, estendemos a linguagem para que a mesma desse suporte ao Retry (especificando o número de tentativas de conexão com um mesmo serviço), Rebinding (criando uma nova construção na gramática que permitisse a especificação de vários serviços que pudessem receber diferentes parâmetros de entrada, mas que tivessem a mesma saída) e Restructure (criando uma nova estrutura que desse a possibilidade de especificar uma ou mais subcomposições que realizem uma mesma tarefa, havendo a substituição no caso de falha em qualquer ponto de uma subcomposição em execução). Esses métodos foram colocados como forma opcional de uso, ou seja, o designer da composição pode usar as especificações da forma antiga. Já na máquina abstrata, introduzimos a estrutura de dados pilha e criamos dois novos tipos de vértices compostos: as pilhas de chamadas de serviços e as pilhas de subcomposições. Serviços ou subcomposições errôneas que fazem parte de uma pilha são excluídos e substituídos por outro serviço ou subcomposição que faz parte da mesma pilha e que realizará a mesma tarefa. Para o suporte aos métodos, tivemos que realizar algumas alterações em determinadas reduções de alguns vértices, principalmente no vértice de entrada de serviços. A redução dos mesmos só ocorrerá quando a resposta for dada antes do tempo máximo esperado. Serviços e subcomposições alternativas devem ser especificadas pelo designer da composição. Além dessas extensões, implementamos algumas melhorias na máquina de redução: • Inserimos a capacidade da verificação de variáveis não-inicializadas, dando suporte ao tratamento desses casos em tempo de execução; • Realizamos a diferenciação entre variáveis e strings, onde estes eram inseridos da mesma forma na especificação da composição; 104 Capítulo 6. CONCLUSÃO • Alteramos a semântica dos vértice Unfolding e Unfold, para o possível ganho de eficiência em futuras implementações de uma outra máquina real (ou até mesmo do melhoramento de PEWS-AM) e também para o tratamento de vértices do tipo unfold, onde os mesmos não eram vértices avaliáveis, porém pertenciam a uma das regras simples da gramática; • Criamos um arquivo de saída para a análise dos dados de redução e demos a capacidade do mesmo de descrever mais informações sobre os estados da máquina, como: arestas de controles entre os vértices, arestas de dependências entre os vértices, detalhes da redução dos vértices, etc. Em virtude da vizualiação dessas estruturas no modo texto não ser uma forma viável para uma boa compreensão, integramos a máquina de redução com a ferramenta GraphViz, que tem a capacidade de construir grafos direcionados. Através da criação de algumas estruturas, conseguimos mostrar os estados da máquina em imagens a cada análise e redução dos vértices. Nas imagens, é possível visualizar vértices que executaram corretamente, vértices que falharam, vértices a serem excluídos após uma determinada redução, pilhas de chamadas e subcomposições, entre outros fatores. Outro fator importante é que a implementação foi realizada de forma a permitir outros métodos de detecção de falhas. Podemos inserir outros métodos de detecção na máquina real apenas com a criação de classes específicas ao mecanismo de detecção. Os testes realizados mostram que, em muitas situações, o uso das estruturas criadas são benéficos à composição, uma vez que dão possibilidade para vários tipos de execuções alternativas. Já em alguns casos, vimos que o uso do Retry pode atrasar o tempo de execução total da composição de serviços, caso seus parâmetros não tenho sido bem especificados. Sendo o maior objetivo a execução completa e correta da composição, concluímos que, para um maior ganho de eficiência, devemos fazer um estudo da composição por inteiro, a fim de escolher-se os parâmetros cujo ganho de tempo seja o maior possível. A máquina apresenta algumas limitações, principalmente quanto à compensação às alterações realizadas. Na ocorrência de uma falha em uma subcomposição, todo o processamento anteriormente realizado por vértices pertencentes a ela não são desfeitos, ou seja, podem existir determinados casos em que inconsistências ou até mesmo erros não consigam ser evitados. Além disso, o ambiente que guarda o valor das variáveis suporta apenas o tipo de dados inteiro, ou seja, determinadas composições que precisam de tipos de dados textuais gravados no ambiente ainda não são possíveis. Outro fator a ser citado é que a descrição WSDL dos serviços necessita estar dentro da máquina para a execução das composições, não sendo possível ainda a execução de Serviços Web totalmente externos a esta. Em trabalhos futuros, pretendemos alcançar os seguintes objetivos: • Implementar novos mecanismos de detecção de falhas (por exemplo, Qos) e, possivelmente, fazer com que os vários mecanismos de detecção possam trabalhar em conjunto; 105 • Criar novos mecanismos de recuperação de falhas, onde os mesmos podem tratar outros tipos de falhas além das falhas de disponibilidade; • Dar capacidade a execução de serviços que não necessariamente possuam o WSDL embutido na máquina, dando mais flexibilidade à execução de serviços externos; • Realização de experimentos com aplicações que possuam um maior grau de complexibilidade, chegando-se a conclusões mais precisas quanto a eficiência e eficácia da máquina com a inserção dos mecanismos de recuperação; • Suporte a vários tipos de dados persistentes no ambiente, não limitando-se apenas a um ambiente de variáveis inteiras; • Permitir a descrição e atribuição de valores distintos de TE e NM T para serviços, tentando evitar problemas relativos ao paralelismo na execução de serviços; Apesar da simplicidade dos métodos de recuperação de falhas abordados, acreditamos que nossa proposta contribui como esforço inicial para a geração de composições de serviços mais robustas em PEWS. Mesmo em casos onde houve aumento no tempo global de execução das composições, os mecanismos propostos permitiram a conclusão bem sucedida da aplicação mesmo diante das falhas simuladas. 107 Referências [1] http://www.sinfic.pt/SinficNewsletter/sinfic/Newsletter43/Dossier2.html, 2012. Citado na página 25. [2] S. Andler. Predicate path expressions. In Proceedings of the 6th ACM SIGACT-SIGPLAN symposium on Principles of programming languages, pages 226–236. ACM, 1979. Citado na página 33. [3] A. Assaf, A. A. Intalio, S. A. Intalio, S. Fordin, W. J. Sap, K. Kawaguchi, D. Orchard, et al. Web service choreography interface 1.0. .., 2002. Citado na página 24. [4] C. Ba, M. Carrero, M. H. Ferrari, and M. Musicante. Pews: A new language for building web service interfaces. Journal of Universal Computer Science, 11(7):1215–1233, 2005. Citado 5 vezes nas páginas 24, 33, 34, 35 e 36. [5] L. Baresi, C. Ghezzi, and S. Guinea. Towards self-healing composition of services. In Contributions to ubiquitous computing, pages 27–46. Springer, 2007. Citado 6 vezes nas páginas 17, 29, 32, 47, 48 e 124. [6] D. Box, D. Ehnebuske, G. Kakivaya, A. Layman, N. Mendelsohn, H. F. Nielsen, S. Thatte, and D. Winer. Simple object access protocol (soap) 1.1, 2000. Citado na página 23. [7] F. Budinsky. Eclipse modeling framework: a developer’s guide. Addison-Wesley Professional, 2004. Citado na página 78. [8] D. A. d. S. Carvalho. Uma máquina de redução de grafos para serviços web. Mestrado, DIMAP, Centro de Ciencias Exatas e da Terra, UFRN, 2012. Citado 6 vezes nas páginas 18, 19, 36, 37, 42 e 78. [9] W. Chen, Y. Xu, B. Peng, and W. Sun. Dynamic monitor based service recovery for composite service in manets. In Communication Technology, 2008. ICCT 2008. 11th IEEE International Conference on, pages 557–560. IEEE, 2008. Nenhuma citação no texto. [10] N. Curbera and Weerawarama. Web services: Why and how. IBM Research Center, 2001. Citado 2 vezes nas páginas 17 e 24. [11] R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, and T. Berners-Lee. Hypertext transfer protocol–http/1.1, 1999. Citado na página 23. [12] T. M. Fruchterman and E. M. Reingold. Graph drawing by force-directed placement. Software: Practice and experience, 21(11):1129–1164, 1991. Citado na página 83. [13] E. R. Gansner. Drawing graphs with graphviz. Technical report, Technical report, AT&T Bell Laboratories, Murray, 2009. Citado 3 vezes nas páginas 18, 67 e 83. 108 Referências [14] E. R. Gansner, Y. Koren, and S. North. Graph drawing by stress majorization. In Graph Drawing, pages 239–250. Springer, 2005. Citado na página 83. [15] Y. Hu. Efficient, high-quality force-directed graph drawing. Mathematica Journal, 10(1):37– 71, 2005. Citado na página 83. [16] M. P. Jones. jacc: just another compiler compiler for java a reference manual and user guide, 2004. Citado na página 78. [17] M. Karray, C. Ghedira, and Z. Maamar. Towards a self-healing approach to sustain web services reliability. In Advanced Information Networking and Applications (WAINA), 2011 IEEE Workshops of International Conference on, pages 267–272. IEEE, 2011. Citado 3 vezes nas páginas 17, 29 e 123. [18] R. Khalaf, N. Mukhi, and S. Weerawarana. Service-oriented composition in bpel4ws. WWW (Alternate Paper Tracks), 2003. Citado 2 vezes nas páginas 24 e 29. [19] R. B. Kieburtz. The g-machine: A fast, graph-reduction evaluator. In Functional Programming Languages and Computer Architecture, pages 400–413. Springer, 1985. Citado na página 36. [20] H. Kreger et al. Web services conceptual architecture (wsca 1.0). IBM Software Group, 5:6–7, 2001. Citado 5 vezes nas páginas 17, 21, 22, 23 e 24. [21] P. A. Lee and T. Anderson. Fault tolerance principles and practice, volume 3 of dependable computing and fault-tolerant systems, 1990. Citado na página 26. [22] T. Murata. Petri nets: Properties, analysis and applications. Proceedings of the IEEE, 77(4):541–580, 1989. Citado na página 29. [23] E. Newcomer. Understanding Web Services: XML, Wsdl, Soap, and UDDI. Addison-Wesley Professional, 2002. Citado 2 vezes nas páginas 22 e 24. [24] C. Ouyang, E. Verbeek, W. M. van der Aalst, S. Breutel, M. Dumas, and A. H. ter Hofstede. Wofbpel: A tool for automated analysis of bpel processes. In Service-Oriented ComputingICSOC 2005, pages 484–489. Springer, 2005. Citado na página 29. [25] M. P. Papazoglou. Service-oriented computing: Concepts, characteristics and directions. In Web Information Systems Engineering, 2003. WISE 2003. Proceedings of the Fourth International Conference on, pages 3–12. IEEE, 2003. Citado na página 21. [26] M. P. Papazoglou, P. Traverso, S. Dustdar, and F. Leymann. Service-oriented computing: State of the art and research challenges. Computer, 1(11):38–45, 2007. Citado na página 17. [27] M. P. Papazoglou and W.-J. Van Den Heuvel. Service oriented architectures: approaches, technologies and research issues. The VLDB journal, 16(3):389–415, 2007. página 21. Citado na Referências 109 [28] C. Peltz. Web services orchestration and choreography. Computer, 36(10):46–52, 2003. Citado na página 25. [29] J. Postel. Simple mail transfer protocol. Information Sciences, 1982. Citado na página 23. [30] J. Postel and J. Reynolds. File transfer protocol. 1985. Citado na página 23. [31] J. Rao and X. Su. A survey of automated web service composition methods. In Semantic Web Services and Web Process Composition, pages 43–54. Springer, 2005. Citado na página 36. [32] H. Saboohi and S. A. Kareem. Requirements of a recovery solution for failure of composite web services. In International Journal of Web and Semantic Technology (IJWesT). IEEE, 2012. Citado 2 vezes nas páginas 26 e 33. [33] A. W. Scheer. ARIS: business process modeling. Springer, 2000. Citado na página 24. [34] J. Simmonds, S. Ben-David, and M. Chechik. Guided recovery for web service applications. In Proceedings of the eighteenth ACM SIGSOFT international symposium on Foundations of software engineering, pages 247–256. ACM, 2010. Nenhuma citação no texto. [35] J. Simmonds, S. Ben-David, and M. Chechik. Monitoring and recovery of web service applications. In The smart internet, pages 250–288. Springer, 2010. Citado 3 vezes nas páginas 17, 29 e 123. [36] J. M. Six and I. G. Tollis. Circular drawings of biconnected graphs. In Algorithm Engineering and Experimentation, pages 57–73. Springer, 1999. Citado na página 83. [37] J. Steyn. Approaches to failure and recovery in service composition, 2006. Citado 4 vezes nas páginas 17, 26, 28 e 33. [38] K. Sugiyama, S. Tagawa, and M. Toda. Methods for visual understanding of hierarchical system structures. Systems, Man and Cybernetics, IEEE Transactions on, 11(2):109–125, 1981. Citado na página 83. [39] F. Tartanoglu, V. Issarny, A. Romanovsky, and N. Levy. Dependability in the web services architecture. In Architecting dependable systems, pages 90–109. Springer, 2003. Citado 2 vezes nas páginas 27 e 28. [40] R. Vaculin and K. Sycara. Specifying and monitoring composite events for semantic web services. In Web Services, 2007. ECOWS’07. Fifth European Conference on, pages 87–96. IEEE, 2007. Nenhuma citação no texto. [41] R. Vaculín, K. Wiesner, and K. Sycara. Exception handling and recovery of semantic web services. In Networking and Services, 2008. ICNS 2008. Fourth International Conference on, pages 217–222. IEEE, 2008. Citado 4 vezes nas páginas 17, 29, 30 e 124. [42] M. Van Steen. Distributed systems principles and paradigms. Network, 4:20, 2004. Citado na página 27. 110 Referências [43] K. Wiesner, R. Vaculín, M. Kollingbaum, and K. Sycara. Recovery mechanisms for semantic web services. In Distributed Applications and Interoperable Systems, pages 100–105. Springer, 2008. Citado 5 vezes nas páginas 17, 29, 30, 31 e 123. [44] T. Yu and K.-J. Lin. Adaptive algorithms for finding replacement services in autonomic distributed business processes. In Autonomous Decentralized Systems, 2005. ISADS 2005. Proceedings, pages 427–434. IEEE, 2005. Citado 4 vezes nas páginas 17, 29, 31 e 124. 111 APÊNDICE A – Código do arquivo Pews.jacc %{ package pews.parser; import pews.main.Main; import pews.grafos.*; import pews.maquina.Maquina; import pews.maquina.TipoToken; %} %semantic Token %token ‘+’ ‘-’ ‘*’ ‘/’ ‘(’ ‘)’ ‘[’ ‘]’ ‘;’ ‘,’ ‘?’ ‘<’ ‘>’ ‘=’ ‘.’ ‘%’ ‘@’ ‘{’ ‘}’ UNFOLDING UNFOLD PARALELO MAIORIGUAL MENORIGUAL IGUALIGUAL AND OR NOT INTEGER IDENTIFIER IF TEMPORESPOSTA STRING %left OR %left AND %left IGUALIGUAL %left MAIORIGUAL MENORIGUAL ‘<’ ‘>’ ‘;’ %left ‘+’ ‘-’ PARALELO ‘?’ ‘=’ ‘.’ UNFOLD IF ‘@’ ‘%’ TEMPORESPOSTA %left ‘*’ ‘/’ %left ‘(’ ‘)’ %right NOT UNFOLDING %% prog : prog ’?’ P {this.maquina = new Maquina($3);} | TEMPORESPOSTA ‘(’ INTEGER ‘,’ INTEGER ‘)’ P {this.maquina = new Maquina($9,$3,$5);} | P {this.maquina = new Maquina($1);} ; P : | | | | | | | | | ‘[’ E ‘]’ P ‘+’ ‘[’ E ‘]’ P { $$ = new Grafo($2, $4, $7, $9); } IF ‘[’ E ‘]’ P { $$ = new Grafo($3, $5); } P ‘;’ P { $$ = new Grafo($1, $3, "SEQFORTE"); } P ‘.’ P { $$ = new Grafo($1, $3, "SEQFRACA"); } P PARALELO P { $$ = new Grafo($1, $3, "PARALELO"); } UNFOLDING P { $$ = new Grafo($2); } UNFOLD { $$ = new Grafo("unfold"); } ‘(’ P ‘)’ { $$ = $2; } IDENTIFIER ‘(’ E ‘,’ IDENTIFIER ‘)’ { $$ = new Grafo($1, $3, $5, "CHAMADA"); } ‘@’ IDENTIFIER ‘(’ E ‘,’ IDENTIFIER ‘)’ SAlt { $$ = new Grafo($2, $4, $6, $8, "PRINCIPAL"); } 112 APÊNDICE A. Código do arquivo Pews.jacc | ‘{’ P CAlt { $$ = new Grafo ($2, $3, "PILHA SUBCOMPOSICOES"); } ; SAlt : ‘%’ IDENTIFIER ‘(’ E ‘,’ IDENTIFIER ‘)’ SAlt { $$ = new Grafo($2, $4, $6, $8, "ALTERNATIVO"); } | ‘@’ { $$ = new Grafo (); } ; CAlt : ‘%’ P CAlt { $$ = new Grafo ($2, $3, "SUBCOMPOSICAO ALTERNATIVA"); } | ‘}’ { $$ = new Grafo (); } ; E : E ‘;’ E { $$ = new Expressao ($1, $3);} | E ‘+’ E { $$ = new Expressao ($1, $3, Operador.SOMA, TipoToken.EXPRESSAO);} | E ‘-’ E { $$ = new Expressao ($1, $3, Operador.SUB, TipoToken.EXPRESSAO);} | E ‘*’ E { $$ = new Expressao ($1, $3, Operador.MULT, TipoToken.EXPRESSAO);} | E ‘/’ E { $$ = new Expressao ($1, $3, Operador.DIV, TipoToken.EXPRESSAO);} | E ‘<’ E { $$ = new Expressao ($1, $3, Operador.MENOR, TipoToken.EXPRESSAO);} | E ‘>’ E { $$ = new Expressao ($1, $3, Operador.MAIOR, TipoToken.EXPRESSAO);} | E MAIORIGUAL E { $$ = new Expressao ($1, $3, Operador.MAIORIGUAL, TipoToken.EXPRESSAO);} | E MENORIGUAL E { $$ = new Expressao ($1, $3, Operador.MENORIGUAL, TipoToken.EXPRESSAO);} | E AND E { $$ = new Expressao ($1, $3, Operador.AND, TipoToken.EXPRESSAO);} | E OR E { $$ = new Expressao ($1, $3, Operador.OR, TipoToken.EXPRESSAO);} | NOT E { $$ = new Expressao ($2, Operador.NOT, TipoToken.EXPRESSAO);} | ‘(’ E ‘)’ { $$ = $2; } | IDENTIFIER { $$ = new Expressao ($1, TipoToken.IDENTIFICADOR); } | INTEGER { $$ = new Expressao ($1, TipoToken.INTEGER); } | STRING { $$ = new Expressao ($1, TipoToken.STRING); } ; %% private PewsLexer lexer; private Maquina maquina; public PewsParser(PewsLexer lexer) { this.lexer = lexer; }; private void yyerror(String msg) { Main.error(yyerrno<0 ? msg : yyerrmsgs[yyerrno]); }; 113 APÊNDICE B – Uso do GraphViz em PEWS-AM A linguagem dot, pertencente ao GraphViz, tem a capacidade de desenhar grafos direcionados. Suas características incluem: layout para a desenho de nós, bordas, rótulos de borda, registros, estruturas de dados, cluster e uma linguagem de arquivos subjacente para ferramentas de gráficos orientados a streams. O construtor digraph G indica a construção de grafos direcionados, onde G é o nome dado a este grafo. O padrão estrutural do GraphViz mostra os grafos de forma vertical. Para que vizualizamos os mesmos de forma horizontal usamos o atributo rankdir=LR, em alusão à left-right. Utilizamos também os chamados clusters). Na máquina de redução estendida, temos dois tipos de vértices compostos, sendo eles as pilhas de chamadas e as pilhas de subcomposições. Podemos necessitar ligar arestas de controle a estas estruturas, que no GraphViz não serão nós, e sim clusters. Dessa forma, precisamos explicitar que iremos permitir a ligação entre vértices e estruturas. Isso é feito setando o parâmetro compound=true. Então, visto estes parâmetros, temos a seguinte situação inicial na construção do arquivo para geração das figuras: Digraph G { rankdir=LR; compound=true; ... } Dentro deste construtor, podemos descrever os vértices e as ligações entre eles. Por exemplo, queremos construir dois vértices e criar uma aresta entre ambos. Isso pode ser feito de duas formas: (i) escrevendo primeiramente a identificação dos vértices e depois as ligações, ou (ii) realizar esses dois passos de uma vez. Se queremos criar dois vértices chamados entrada e saida e uma ligação entre eles, podemos fazer isso, utilizando os passos acima, da maneira mostrada na figura 32: Figura 32 – Exemplo de entrada e saída de um código no GraphViz Como podemos observar, ambas as formas produzem o mesmo resultado. Para uma forma mais compacta e menos extensa, seria mais interessante usar a forma (ii), pois ela mais direta. Entretanto, adotamos a forma (i) pelo fato de que precisaremos descrever alguns atributos para os vértices, a fim de tornar a vizualização mais clara, algo que não é possível se criarmos a identificação dos vértices e criamos 114 APÊNDICE B. Uso do GraphViz em PEWS-AM também as arestas ao mesmo tempo. O padrão utilizado é: descrever as características de cada vértice e, posteriormente, fazermos as ligações entre eles. O dot também permite a escrita de atributos para os vértices, como: formas dos vértices (círculos, retângulos, triângulos, etc.), cores (tanto das linhas de contorno quanto de preenchimento), nome de impressão (cada vértice tem um identificador, que é o seu nome, entretanto podemos colocar textos alternativos que apareçam nu lugar do nome do vértice, sendo possível a utilização de caracteres especiais que não são possíveis de serem descritos no momento da identificação), etc. Isso é possível descrevendo estes atributos no momento em que o vértice é criado. Utilizando os mesmos vértices do exemplo 32, com o uso de alguns atributos, podemos ter a saída mostrada na figura 33. Figura 33 – Exemplo de inserção de atributos para vértices e arestas Descrevemos dois vértices, cujos nomes são entrada e saida, com uma aresta direcionada do primeiro para o segundo. Porém, na imagem gráfica, não são esses os nomes que aparecem nos vértices. Isso porque setamos o atributo label de cada um dos vértices, para que nomes diferentes dos seus identificadores fossem mostrados na imagem. Além disso, a forma dos vértices também foi mudada para um losango e um quadrado (o padrão é sempre um círculo), fato realizado pela alteração do atributo shape. Ainda setamos a cor interna dos vértices com a inserção dos atributos stylle=filled (indica que o preenchimento interno será alterado) e color=yellow/green. Um outro fato interessante é a mudança do tipo de aresta utilizada. A mesma agora apresenta traços pontilhados pela alteração do atributo stylle=dotted, diferentemente do caso anterior (caso padrão), onde a aresta era um linha completa. Uma outra capacidade do dot é descrever subgrafos. Temos o grafo principal, que é descrito logo no início do arquivo pelo construtor Digraph. Podemos descrever outros subgrafos internos a este, identificando estes subgrafos através do uso de clusters. Para criar um subgrafo, utilizamos o construtor subgraph cluster { ... }, onde descrevemos os vértices internos entre os delimitadores ‘{’ e ‘}’. Para criar vários subgrafos, precisamos nomear os clusters numericamente. Por exemplo, podemos ter clusteri , com 1 ≤ i ≤ n, onde n é o número de clusters presentes no subgrafo. Um exemplo do uso de clusters é mostrado na 34. Como não colocamos atributos para os vértices, descrevemos as ligações diretamente entre eles. Temos a presença de seis vértices. Em especial, observamos os vértices vertice1, vertice2, vertice3 e vertice4, que aparecem dentro de blocos internos ao bloco principal. Estes são os nossos subgrafos, denominados cluster0 e cluster1. Como descrevemos as ligações dos vértices dentro dos subgrafos, eles irão aparecer internamente a estes. No final, vemos que os vértices entrada e saida não foram descritos dentro dos subgrafos, aparecendo fora deles. Entretanto, a ligação foi feita entre os vértices. Também é possível fazer a ligação entre um vértice e uma estrutura, através da especificação compound=true, citada anteriormente. Contudo, a linguagem dot não permite a especificação entrada -> cluster0, sendo necessário o uso de alguma característica a mais. 115 Figura 34 – Exemplo de ligação entre vértices normais e vértices de clusters Para realizar a ligação entre um vértice e uma estrutura, precisamos utiliar dois parâmetros característicos de listas em linguagens funcionais: lhead e ltail, que indicam o início e o fim, respectivamente. Utilizando o exemplo da figura 34, temos: entrada -> vertice1 [lhead=cluster0]; entrada -> vertice3 [lhead=cluster1]; vertice2 -> saida [ltail=cluster0]; vertice4 -> saida [ltail=cluster1]; O atributo lhead pega um determinado espaço do subgrafo que esteja próximo tanto do vértice escolhido como do início do subgrafo, da mesma forma que o ltail pega um determinado espaço do subgrafo que esteja próximo tanto do vértice escolhido como do fim do subgrafo. Assim como nos vértices, os subgrafos também podem receber atributos, como cores, identificadores, entre outros. A diferença é que a descrição destes atributos é feita em qualquer local dentro ao subgrafo, tendo a mesma estrutura de descrição dos atributos dos vértices. É possível também ligar subgrafos entre si, usando a junção da lógica mostrada. Figura 35 – Exemplo de ligação entre vértices e clusters O GraphViz possui muitas outras características, porém o seu extenso uso não foi utilizado neste trabalho. 117 APÊNDICE C – Atributos e métodos das classes da Máquina Estendida PEWS-AM Abaixo, descreveremos os atributos e métodos das classes pertencenes a PEWS-AM, com a inserção dos mecanismos de detecção e recuperação de falhas. Pacote pews.main Pacote que contém a classe Main, cuja função é chamar o analisar léxico e o analisador sintático, e a classe GeradorFiguras, responsável por chamar os atributos e métodos que geram as imagens de execução da composição. A classe Main não possui atributos, nem métodos. Já a classe GeradorFiguras possui os seguintes atributos e métodos: • contFiguras – Geramos um arquivo de imagem para cada situação da máquina. Por isso, precisamos ter nomes de arquivos diferentes, para vizualizarmos a sequência de redução. Sendo assim, utilizamos o parâmetro contFiguras para gerar nomes diferentes de arquivos através de identificação numérica progressiva. Os nomes dos arquivos gerados tem sempre o padrão: Siti .dot, onde 1 ≤ i ≤ n, sendo n o número de figuras geradas pela execução da composição. • contClusters – Da mesma forma que vários arquivos de imagem precisam ter nomes diferentes, subgrafos internos ao grafo principal precisam ter uma identificação única. Utilizaremos clusters para representar pilhas de chamadas de serviços e pilhas de subcomposições alternativas. Assim como o atributo anterior os nomes dos clusters gerados tem o padrão clusteri , onde 1 ≤ i ≤ n; • GeradorFiguras – Construtor da classe, que apenas tem a função de setar o parâmetro contFiguras com o valor inicial sendo 1; • nomeVerticeImpressao – Esta classe é utilizada para atribuir características aos vértices. Como foi falado anteriormente, podemos criar vértices já fazendo suas ligações com outros vértices, ou então descrevemos primeiro os vértices e depois suas ligações. Utilizamos esta última forma para que pudessemos descrever atributos para os vértices. A classe recebe três parâmetros: vertice (referência do vértice aos quais serão dados diversos atributos), spaces (atributo não-importante da função que serve apenas para formatar o arquivo de saída, deixando-o numa estrutura bem identada) e caracteristicas (estrutura do tipo Java Map que contém um conjunto de vértices e suas respectivas características a serem descritas). A idéia geral da função é procurar as características do vértice dentro do atributo caracteristicas e acrescentá-las no arquivo de saída. Caso não exista nenhuma referência ao vértice procurado, é colocada uma descrição padrão. • gerarLigacoesVerticesInternosDePilha – Logo após ter descrito os vértices e seus atributos, precisamos indicar as ligações entre os vértices. Isso é feito dentro da função gerarFiguras, descrita mais adiante. No momento de fazer as ligações, encontramos um problema: ao encontrar pilhas de subcomposições, o ambiente de vértices não enxerga os vértices internos das pilhas, apenas no momento em que retiramos uma subcomposição para execução. Sendo assim, além das ligações dos vértices “externos”, precisamos fazer as ligações entre os vértices internos das subcomposições 118 APÊNDICE C. Atributos e métodos das classes da Máquina Estendida PEWS-AM de pilhas. Isso é feito pela função gerarLigacoesVerticesInternosDePilha. Esta função pega cada uma das subcomposições e faz a ligação entre os seus vértices internos; • acrescentarParametrosHeadTail – Como vimos anteriormente, a linguagem dot não faz a ligação direta entre vértices e clusters, onde é preciso pegar algum vértice interno ao cluster para utilizá-lo como referência, através dos atributos lhead e ltail. Isto é feito dentro da função acrescentarParametrosHeadTail. No momento em que é necessária uma ligação entre pelo menos um vértice que utiliza cluster (neste caso, pilhas de chamadas ou subcomposições), escolhemos um vértice de referência do cluster e fazemos a ligação com o outro vértice (no caso de ser outro cluster, também pegamos um vértice de referência deste cluster). Para o vértice no qual sai a aresta, utilizamos o atributo ltail e para o vértice no qual chega a aresta, utilizamos o atributo lhead. • pegarVerticeIdealPilhaSubComp – Como falado anteriormente, para realizar a ligação de um vértice para um cluster, basta pegamos um vértice interno do cluster (não necessariamente o vértice precisa ao cluster, podendo estar em outro cluster interno a este). Entretanto, para que tenhamos uma vizualização mais clara da imagem, nem sempre é bom escolher “qualquer vértice” do cluster. Ao ligar um vértice a uma estrutura escolhendo-se um vértice v1 , a linguagem dot tenta encontrar um local mais próximo do vértice para fazer a ligação. Se queremos ligar um vértice a um cluster e escolhemos um vértice de referência que está no final do cluster, a impressão da imagem não sairá conforme o esperado, apesar de não estar errada. Dessa forma, a função pegarVerticeIdealPilhaSubComp é responsável por buscar o vértice “ideal” de uma pilha de subcomposições para construção da imagem. Nas pilhas de subcomposições, sempre o vértice ideal estará na composição “mediana” da pilha. Considerando que uma pilha de subcomposições possui n subcomposições alternativas, utilizamos o seguinte cálculo para encontrar a subcomposição da qual será escolhido o vértice ideal: n/2 + n%2 Se a pilha é o ponto de saída da aresta, precisamos pegar o “último vértice” desta pilha. Isto é feito pelo método retornaUltimoVerticeSubcomposicao. O último vértice será sempre um vértice que não tenha filhos. Da mesma forma, se a pilha é o ponto de chegada da aresta, precisamos pegar o “primeiro vértice” desta pilha, feito por retornaPrimeiroVerticeSubcomposicao. O primeiro vértice será sempre o vértice na qual não chega nenhuma aresta de controle. Pode ocorrer o seguinte fato: os vértice ideias são pilhas (chamadas ou subcomposições). Para isso, podemos utilizar a função pegarVerticeIdealPilhaSubComp recursivamente. • pegarVerticeIdealPilhaChamadas – Tem a mesma função do método anterior, sendo que é usado para pilhas de chamadas. No lugar de considerar as subcomposições, consideramos os vértices de chamadas de serviços; • ImprimirFinalizarExecucao – Criamos essa função para quando há a ocorrência do término da execução da composição, tanto pelo sucesso da execução ou por ter chegado a um ponto onde não há mais possibilidades de execução. Ela recebe apenas um parâmetro, que indica o tipo de finalização – sucesso ou fracasso – e imprime apenas um vértice, com uma das seguintes informações: Execucao Finalizada por Falta de Opcoes Composicao Executada com sucesso • gerarGraficos – É a função principal da classe, que é chamada no momento em que é solicitada uma geração de imagem do grafo. Esta função recebe como parâmetro todos os vértices do 119 ambiente (represntado por todosOsVertices) e um conjunto de atributos a serem colocados nos vértice (cores, formas, etc). Todas as vezes que a função é chamada, criamos um novo arquivo para nomear a imagem gerada, cujo padrão de nomes é Siti , onde é i referencia o parâmetro contFiguras, citado anteriormente. Na criação deste arquivo, dentro do código, especificamos um caminho para o mesmo. Se a máquina de redução estendida for executada em outro local, provavelmente será necessária a mudança deste caminho (ou a omissão do mesmo, para que os arquivos gerados fiquem dentro da própria pasta dos códigos da máquina). Criamos uma string, chamada geradorFiguras, onde nela será colocado todo o texto referente do arquivo. A primeira especificação colocada na string são os atributos referentes ao layout da geração (horizontal, definido pelo atributo rankdir=LR e à permissão para ligar vértices a estruturas (definido pelo atributo compound=true). Depois disso, são feitas as definições dos vértices (nomenclatura, que é sempre o hashcode do vértice e os atributos definido pelo parâmetro caracteristicas) e suas ligações, cujas funções foram citadas anteriormente. As arestas de controle são definidas como setas completas, enquanto as arestas de dependência são definidas como setas pontilhadas. Pacote pews.parser O pacote pews.parser contém as classes geradas pelo Jacc e algumas classes genéricas utilizadas por outros elementos que serão definidos mais adiante. Temos seis entidades descritas: • PewsTokens – Interface criada pelo Jacc. Na criação da gramática, definimos alguns símbolos terminais que, na compilação do arquivo que contém a descrição da gramática, são transformados em tokens, onde a cada token é atribuído um inteiro identificador. Um trecho do arquivo que contém a gramática responsável pela descrição dos tokens é mostrado abaixo: % token ‘+’ ‘-’ ‘*’ ‘/’ ‘(’ ‘)’ INTEGER IDENTIFIER ... • PewsParser – Classe criada pelo Jacc a partir da gramática. Essa classe faz a análise sintática e aplica a estrutura semântica sobre a especificação da composição dada pelo usuário. • PewsLexer – Classe que faz a análise léxica da especificação da composição. A saída desta classe é usada como entrada na classe PewsParser. Esta classe deste pacote é criada pelo usuário; • Token – Superclasse abstrata que representa os símbolos terminais e que é retornada em cada passo de recursão do parser. Todos os símbolos são considerados tokens, sendo transformados em objetos concretos quando é encontrado um determinado padrão sintático. Ela também possui uma função abstrata eval, que é extendida por outras classes. • TInt e TString– Classes auxiliares usadas no momento da transformação dos tokens em tipos reais, que são utilizados pela máquina. Estas classes estendem a classe Token. A classe TInt é usada para tipos inteiros e a classe TString é usada para tipos Strings. Pacote pews.maquina O pacote pews.maquina contém toda a lógica de redução de vértices. Ele contém as classes auxiliares: OutpuInput, que simula os buffers de entrada e saída da máquina de redução (parâmetros I e O); TipoToken, que identifica a qual “classe” de tokens uma expressão pertence, podendo assumir os valores: INTEGER, IDENTIFICADOR, EXPRESSAO ou STRING; e Variavel, que representa as variáveis do 120 APÊNDICE C. Atributos e métodos das classes da Máquina Estendida PEWS-AM ambiente de uma composição. A classe Maquina, que é a classe principal do pacote, possui os seguintes atributos e métodos: • todosOsVertices – Este atributo é o responsável por guardar todos os vértices da máquina de redução. Considerando a máquina hhV, Ac , Ad i ρ, I, Oi, este atributo corresponde ao elemento V da máquina de redução; • minimals – Atributo do tipo List que, após uma análise sobre o estado atual da máquina, indica quais vértices podem ser avaliados, ou seja, os vértices mínimos; • listaDeOutIn – Atributo que guarda as informações dos buffers de entrada (I) e saída (S) da máquina de redução. Objetos desta classe são criados no momento da execução de vértices Chamada, onde uma nova instância deste objeto é criada para guardar as informações recebidas após a execução de um serviço; • ambiente – Representa o ambiente global de variáveis. No momento em que um serviço é executado, ele gera (ou possivelmente altera) o valor de uma variável no ambiente; • escolhido – Vértice escolhido para a avaliação. Todos os vértices escolhidos para serem avaliados devem estar também presentes no conjunto minimals; • exprCheck – Parâmetro auxiliar que indica o número de vértices avaliados pela máquina de redução; Quando o último passo da recursão é realizado e a máquina de redução é criada, todo o grafo deve estar construído, juntamente com as informações sobre arestas de controle e dependência. A partir daí, o construtor desta classe é chamado. A classe Maquina possui um método print. Este método é o responsável por mostrar todas as informações textuais sobre a situação da máquina no início, durante e no fim de sua execução. As informações gerais que são mostradas em cada chamada deste método são: quantidade de vértices restantes na máquina, variáveis de ambiente (nome e valor) e número de reduções realizadas (relacionadas ao atributo exprCheck). Outro método da classe Maquina é o método run. A chamada para avaliação de vértices é feita dentro deste método. Ele faz a verificação dos vértices mínimos existentes na máquina, e faz a execução para um dos vértices escolhidos. A cada redução de um vértice, o método run é chamado, tornando esta uma tarefa cíclica, até que não existam mais vértices mínimos na máquina. O método que contém toda a semântica de execução da máquina é o método executar, que recebe como parâmetro um vértice, e verifica o tipo do mesmo. A partir da definição da instância do ¯ S?Ō, +, etc), é aplicado um tipo de redução específica. vértice (S!I, Pacote pews.modulosTratamento.TempoResposta Comporta as classes de implementação do mecanismo de detecção de falhas baseado no tempo de resposta. Possui as seguintes classes: • TempoResposta – Classe que é utilizada na execução de serviços quando o designer da composição especifica que o módulo de tratamento utilizado na composição será o tempo de resposta. Esta classe contém as informações dos parâmetros usados como padrão em todas as chamadas de serviços, ou seja, o par (TE ,NM T ). A execução da mesma é simples: é criada uma thread que executa o serviço, enquanto é disparado um temporizador. Se a execução da chamada ocorre antes 121 do término do temporizador, então a chamada ocorreu corretamente. Caso contrário, é executada uma nova chamada deste serviço, respeitando o limite máximo de chamadas. Os atributos dessa classe são mostrados abaixo: private int tempoEspera; private int numMaximoChamadas; private BufferedWriter arquivo_saida; • ThreadChamada – Esta é uma classe que estende a classe padrão do Java Thread e que realiza a chamada a um serviço. Nesta classe (assim também como na classe Chamada) está descrito o código Java para chamada de cada serviço, assim como os artifícios para receber as respostas do mesmo. APÊNDICE D – Tabela de Revisão Bibliográfica dos Trabalhos Falhas Tratadas Prótotipo Pré-processamento Solução Adotada Vantagens/Desvantagens [35] Descumprimento de contratos de serviços Sim - Criação de pré e pós-condições e invariantes de serviços; Criação de planos de recuperação; - Monitores de serviços executam em paralelo com a orquestração. Detectada uma violação de contrato, são retornados os planos de recuperação previamente definidos. A aplicação dos planos leva o sistema a um estado válido; Vantagens: Permite escolha do plano mais adequado; forma ranking de planos; Desvantagens: Mecanismo de compensação, com uso de uma operação arbitrária, retorna a execução para um estado essencialmente diferente. [17] Descumprimento de comportamentos operacional (lógica de negócios) e controle (lógica de execução) Sim - Definição dos comportamentos operacionais e de controle; Armazenamento de padrões “errôneos”; - O módulo de recuperação de erros (ERM) procura em sua base de dados um determinado tratamento já realizado para a devida falha. Caso não exista tratamento prédefinido para a falha, ele aplica uma nova solução, armazenando em sua base de dados o novo padrão de recuperação; Vantagens: Divisão do mecanismo de detecção e tratamento em módulos dedicados, além da separação entre a lógica de negócio e da lógica de execução; Desvantagens: Eficácia da abordagem aplica-se apenas à viabilidade e aspectos da implementação. [43] Descumprimento de invariantes da composição Não - Definição de invariantes - Propostos três mecanismos para correção: troca de um serviço por um equivalente; roolback das ações realizadas; e compensação das ações realizadas anteriormente; Vantagens: Substituição de serviços errôneos dão mais robustez à execução da composição; Desvantagens: Busca e troca em tempo real adiciona algum grau de não-determinismo, onde serviços malintencionados podem ser executados. 123 124 Falhas Tratadas Pré-processamento Solução Adotada Vantagens/Desvantagens [41] Descumprimento de invariantes e falhas na invocação de serviços Não - Definição de hard constraints e soft-constraints - Manipuladores de exceções são responsáveis por tratar hard constraints (tratadas pelos CV-Handlers) e soft constrains (tratadas pelos Event-Handlers); Vantagens: Pretende fornecer bases sólidas para uma rápida recuperação; Desvantagens: Falhas fora do escopo da semântica dos manipuladores não são tratadas. [44] Indisponibilidade de serviços Sim - Definição da composição em forma de grafos para criação de caminhos de backup; - Caso haja uma falha, é retornado um backup do caminho para que a composição retorne a um estado válido. A partir do estado válido, caso não haja outros caminhos a serem “seguidos”, é realizada uma reestruturação da composição; Vantagens: Possibilidade de vários caminhos de execução, permitindo o retorno a um estado em que a mesma continue; Desvantagens: Não há informação sobre roolback do processamento realizado que deve ser desconsiderado. [5] Indisponibilidade de serviços Não - Construção da composição de forma “defensiva” (criação de soluções estáticas de recuperação); - Três ações são tomadas em sequência, caso não se tenha obtido resultado satisfatório: repetição da comunicação com o serviço; troca de um serviço por um equivalente; e reestruturação da composição; Vantagens: A composição sabe das soluções a serem tomadas em cada tipo de falha; Desvantagens: Na reestruturação, não há roolback das ações realizadas, ou seja, o processamento não é desfeito. APÊNDICE D. Tabela de Revisão Bibliográfica dos Trabalhos Protótipo