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
Download

Detecção e Recuperaç Máquina de Redução d Detecção e