Injeção de capacidade de teste e dependência - AWS Flow Framework para Java

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Injeção de capacidade de teste e dependência

A estrutura é projetada para ser amigável para inversão de controle (IoC). As implementações de atividades e de fluxo de trabalho assim como os operadores fornecidos pela estrutura e os objetos de contexto podem ser configurados e instanciados usando contêineres, como o Spring. Pronta para uso, a estrutura fornece integração com o Spring Framework. Além disso, integração com o JUnit foi fornecida para testes de unidade de implementações de fluxo de trabalho e de atividades.

Integração com o Spring

O pacote de com.amazonaws.services.simpleworkflow.flow.spring contém classes que facilitam o uso do Spring Framework em seus aplicativos. Esses incluem operadores de atividades e de fluxo de trabalho com reconhecimento de um escopo personalizado do Spring: WorkflowScope, SpringWorkflowWorker e SpringActivityWorker. Essas classes permitem configurar suas implementações de fluxo de trabalho e de atividade bem como os operadores totalmente por meio do Spring.

WorkflowScope

WorkflowScope é uma implementação de Escopo do Spring fornecida pela estrutura. O escopo permite criar objetos no contêiner do Spring cujo tempo de vida tem o escopo de uma tarefa de decisão. Os beans nesse escopo são instanciados sempre que uma nova tarefa de decisão é recebida pelo operador. Você deve usar esse escopo para beans de implementação de fluxo de trabalho e todos os outros beans dos quais ele depende. Os escopos singleton e de protótipo fornecidos pelo Spring não devem ser usados para beans de implementação de fluxo de trabalho porque a estrutura exige que um novo bean seja criado para cada tarefa de decisão. Não fazer isso resultará em comportamento inesperado.

O exemplo a seguir mostra um trecho de código de configuração do Spring que registra o WorkflowScope e, em seguida, usa-o para configurar um bean de implementação de fluxo de trabalho e um bean de cliente de atividade.

<!-- register AWS Flow Framework for Java WorkflowScope --> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="workflow"> <bean class="com.amazonaws.services.simpleworkflow.flow.spring.WorkflowScope" /> </entry> </map> </property> </bean> <!-- activities client --> <bean id="activitiesClient" class="aws.flow.sample.MyActivitiesClientImpl" scope="workflow"> </bean> <!-- workflow implementation --> <bean id="workflowImpl" class="aws.flow.sample.MyWorkflowImpl" scope="workflow"> <property name="client" ref="activitiesClient"/> <aop:scoped-proxy proxy-target-class="false" /> </bean>

A linha de configuração: <aop:scoped-proxy proxy-target-class="false" />, usada na configuração do bean workflowImpl, é necessária porque o WorkflowScope não oferece suporte a proxy usando CGLIB. Você deve usar essa configuração para qualquer bean no WorkflowScope que esteja conectado a outro bean em outro escopo. Nesse caso, o bean workflowImpl precisa ser conectado a um bean de operador de fluxo de trabalho em escopo singleton (consulte o exemplo completo a seguir).

Saiba mais sobre o uso de escopos personalizados na documentação do Spring Framework.

Operadores com reconhecimento do Spring

Ao usar o Spring, você deve usar as classes de operador com reconhecimento do Spring fornecidas pela estrutura: SpringWorkflowWorker e SpringActivityWorker. Esses operadores podem ser injetados em seu aplicativo usando o Spring, conforme mostrado no exemplo a seguir. Os operadores com reconhecimento do Spring implementam a interface SmartLifecycle do Spring e, por padrão, começam a pesquisar tarefas automaticamente quando o contexto do Spring é inicializado. Você pode desativar essa funcionalidade definindo a propriedade disableAutoStartup do operador como true.

O exemplo a seguir mostra como configurar um agente de decisão. O exemplo usa as interfaces MyActivities e MyWorkflow (não mostradas aqui) e as implementações correspondentes, MyActivitiesImpl e MyWorkflowImpl. As interfaces e as implementações de cliente geradas são MyWorkflowClient/MyWorkflowClientImpl e MyActivitiesClient/MyActivitiesClientImpl (também não mostradas aqui).

O cliente de atividades é injetado na implementação do fluxo de trabalho usando o recurso de conexão automática do Spring:

public class MyWorkflowImpl implements MyWorkflow { @Autowired public MyActivitiesClient client; @Override public void start() { client.activity1(); } }

A configuração do agente de decisão do Spring é a seguinte:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- register custom workflow scope --> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="workflow"> <bean class="com.amazonaws.services.simpleworkflow.flow.spring.WorkflowScope" /> </entry> </map> </property> </bean> <context:annotation-config/> <bean id="accesskeys" class="com.amazonaws.auth.BasicAWSCredentials"> <constructor-arg value="{AWS.Access.ID}"/> <constructor-arg value="{AWS.Secret.Key}"/> </bean> <bean id="clientConfiguration" class="com.amazonaws.ClientConfiguration"> <property name="socketTimeout" value="70000" /> </bean> <!-- Amazon SWF client --> <bean id="swfClient" class="com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient"> <constructor-arg ref="accesskeys" /> <constructor-arg ref="clientConfiguration" /> <property name="endpoint" value="{service.url}" /> </bean> <!-- activities client --> <bean id="activitiesClient" class="aws.flow.sample.MyActivitiesClientImpl" scope="workflow"> </bean> <!-- workflow implementation --> <bean id="workflowImpl" class="aws.flow.sample.MyWorkflowImpl" scope="workflow"> <property name="client" ref="activitiesClient"/> <aop:scoped-proxy proxy-target-class="false" /> </bean> <!-- workflow worker --> <bean id="workflowWorker" class="com.amazonaws.services.simpleworkflow.flow.spring.SpringWorkflowWorker"> <constructor-arg ref="swfClient" /> <constructor-arg value="domain1" /> <constructor-arg value="tasklist1" /> <property name="registerDomain" value="true" /> <property name="domainRetentionPeriodInDays" value="1" /> <property name="workflowImplementations"> <list> <ref bean="workflowImpl" /> </list> </property> </bean> </beans>

Como o SpringWorkflowWorker é totalmente configurado no Spring e começa a pesquisar automaticamente quando o contexto do Spring é inicializado, o processo do host para o agente de decisão é simples:

public class WorkflowHost { public static void main(String[] args){ ApplicationContext context = new FileSystemXmlApplicationContext("resources/spring/WorkflowHostBean.xml"); System.out.println("Workflow worker started"); } }

Da mesma forma, o operador de atividades pode ser configurado da seguinte maneira:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- register custom scope --> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="workflow"> <bean class="com.amazonaws.services.simpleworkflow.flow.spring.WorkflowScope" /> </entry> </map> </property> </bean> <bean id="accesskeys" class="com.amazonaws.auth.BasicAWSCredentials"> <constructor-arg value="{AWS.Access.ID}"/> <constructor-arg value="{AWS.Secret.Key}"/> </bean> <bean id="clientConfiguration" class="com.amazonaws.ClientConfiguration"> <property name="socketTimeout" value="70000" /> </bean> <!-- Amazon SWF client --> <bean id="swfClient" class="com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient"> <constructor-arg ref="accesskeys" /> <constructor-arg ref="clientConfiguration" /> <property name="endpoint" value="{service.url}" /> </bean> <!-- activities impl --> <bean name="activitiesImpl" class="asadj.spring.test.MyActivitiesImpl"> </bean> <!-- activity worker --> <bean id="activityWorker" class="com.amazonaws.services.simpleworkflow.flow.spring.SpringActivityWorker"> <constructor-arg ref="swfClient" /> <constructor-arg value="domain1" /> <constructor-arg value="tasklist1" /> <property name="registerDomain" value="true" /> <property name="domainRetentionPeriodInDays" value="1" /> <property name="activitiesImplementations"> <list> <ref bean="activitiesImpl" /> </list> </property> </bean> </beans>

O processo de host do operador de atividades é semelhante ao do agente de decisão:

public class ActivityHost { public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext( "resources/spring/ActivityHostBean.xml"); System.out.println("Activity worker started"); } }

Injeção do contexto de decisão

Se a implementação do fluxo de trabalho depender dos objetos do contexto, você também poderá injetá-los facilmente por meio do Spring. A estrutura registra automaticamente os beans relacionados ao contexto no contêiner do Spring. Por exemplo, no trecho de código a seguir, os vários objetos do contexto foram conectados automaticamente. Não é necessária nenhuma outra configuração do Spring para os objetos de contexto.

public class MyWorkflowImpl implements MyWorkflow { @Autowired public MyActivitiesClient client; @Autowired public WorkflowClock clock; @Autowired public DecisionContext dcContext; @Autowired public GenericActivityClient activityClient; @Autowired public GenericWorkflowClient workflowClient; @Autowired public WorkflowContext wfContext; @Override public void start() { client.activity1(); } }

Para configurar os objetos de contexto na implementação do fluxo de trabalho por meio da configuração XML do Spring, use os nomes de beans declarados na classe WorkflowScopeBeanNames no pacote com.amazonaws.services.simpleworkflow.flow.spring. Por exemplo:

<!-- workflow implementation --> <bean id="workflowImpl" class="asadj.spring.test.MyWorkflowImpl" scope="workflow"> <property name="client" ref="activitiesClient"/> <property name="clock" ref="workflowClock"/> <property name="activityClient" ref="genericActivityClient"/> <property name="dcContext" ref="decisionContext"/> <property name="workflowClient" ref="genericWorkflowClient"/> <property name="wfContext" ref="workflowContext"/> <aop:scoped-proxy proxy-target-class="false" /> </bean>

Como alternativa, você pode injetar um DecisionContextProvider no bean de implementação de fluxo de trabalho e usá-lo para criar o contexto. Isso pode ser útil se você desejar fornecer implementações personalizadas de provedor e do contexto.

Injeção de recursos em atividades

Você pode instanciar e configurar implementações de atividades usando um contêiner de inversão de controle (IoC) e injetar recursos facilmente, como conexões de banco de dados, declarando-os como propriedades da classe de implementação da atividade. Normalmente, esses recursos terão escopos singletons. Observe que as implementações de atividades são chamadas por operadores de atividades em vários threads. Portanto, o acesso aos recursos compartilhados deve ser sincronizado.

Integração com o JUnit

A estrutura fornece extensões de JUnit bem como implementações de teste dos objetos do contexto, como um relógio de teste, que você pode usar para escrever e executar testes de unidade com o JUnit. Com essas extensões, você pode testar a implementação do fluxo de trabalho localmente em linha.

Criação de um teste de unidade simples

Para escrever testes para seu fluxo de trabalho, use a classe WorkflowTest no pacote com.amazonaws.services.simpleworkflow.flow.junit. Essa classe é uma implementação JUnit MethodRule específica da estrutura e executa seu código de fluxo de trabalho localmente, chamando atividades em linha em vez de passar pelo Amazon SWF. Isso fornece a flexibilidade de executar testes quantas vezes forem desejadas sem incorrer em encargos.

Para usar essa classe, simplesmente declare um campo do tipo WorkflowTest e anote-o com a anotação @Rule. Antes de executar seus testes, crie um novo objeto WorkflowTest e adicione suas implementações de atividades e de fluxo de trabalho a ele. Em seguida, você pode usar a fábrica de cliente de fluxo de trabalho gerada para criar e iniciar uma execução do fluxo de trabalho. A estrutura também fornece um executor personalizado do JUnit, FlowBlockJUnit4ClassRunner que você deve usar para seus testes de fluxo de trabalho. Por exemplo:

@RunWith(FlowBlockJUnit4ClassRunner.class) public class BookingWorkflowTest { @Rule public WorkflowTest workflowTest = new WorkflowTest(); List<String> trace; private BookingWorkflowClientFactory workflowFactory = new BookingWorkflowClientFactoryImpl(); @Before public void setUp() throws Exception { trace = new ArrayList<String>(); // Register activity implementation to be used during test run BookingActivities activities = new BookingActivitiesImpl(trace); workflowTest.addActivitiesImplementation(activities); workflowTest.addWorkflowImplementationType(BookingWorkflowImpl.class); } @After public void tearDown() throws Exception { trace = null; } @Test public void testReserveBoth() { BookingWorkflowClient workflow = workflowFactory.getClient(); Promise<Void> booked = workflow.makeBooking(123, 345, true, true); List<String> expected = new ArrayList<String>(); expected.add("reserveCar-123"); expected.add("reserveAirline-123"); expected.add("sendConfirmation-345"); AsyncAssert.assertEqualsWaitFor("invalid booking", expected, trace, booked); } }

Você também pode especificar uma lista de tarefas separada para cada implementação de atividade adicionada ao WorkflowTest. Por exemplo, se tiver uma implementação de fluxo de trabalho que programa atividades em listas de tarefas específicas ao host, você poderá registrar a atividade na lista de tarefas de cada host:

for (int i = 0; i < 10; i++) { String hostname = "host" + i; workflowTest.addActivitiesImplementation(hostname, new ImageProcessingActivities(hostname)); }

Observe que o código em @Test é assíncrono. Portanto, você deve usar o cliente de fluxo de trabalho assíncrono para iniciar uma execução. Para verificar os resultados do teste, uma classe auxiliar AsyncAssert também é fornecida. Essa classe permite que você aguarde que as promessas estejam prontas antes de verificar os resultados. Neste exemplo, esperamos que o resultado da execução do fluxo de trabalho esteja pronto antes de verificar a saída do teste.

Se você estiver usando o Spring, a classe SpringWorkflowTest pode ser usada em vez da classe WorkflowTest. O SpringWorkflowTest oferece propriedades que você pode usar para configurar facilmente por meio de implementações de atividade e de fluxo de configuração do Spring. Assim como os operadores com reconhecimento do Spring, você deve usar o WorkflowScope para configurar beans de implementação de fluxo de trabalho. Isso garante que um novo bean de implementação de fluxo de trabalho seja criado para cada tarefa de decisão. Certifique-se de configurar esses beans com a configuração de scoped-proxy proxy-target-class definida como false. Consulte a seção Integração com o Spring para obter mais detalhes. A configuração de exemplo do Spring mostrada na seção Integração com o Spring pode ser alterada para testar o fluxo de trabalho usando o SpringWorkflowTest:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans ht tp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframe work.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- register custom workflow scope --> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="workflow"> <bean class="com.amazonaws.services.simpleworkflow.flow.spring.WorkflowScope" /> </entry> </map> </property> </bean> <context:annotation-config /> <bean id="accesskeys" class="com.amazonaws.auth.BasicAWSCredentials"> <constructor-arg value="{AWS.Access.ID}" /> <constructor-arg value="{AWS.Secret.Key}" /> </bean> <bean id="clientConfiguration" class="com.amazonaws.ClientConfiguration"> <property name="socketTimeout" value="70000" /> </bean> <!-- Amazon SWF client --> <bean id="swfClient" class="com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient"> <constructor-arg ref="accesskeys" /> <constructor-arg ref="clientConfiguration" /> <property name="endpoint" value="{service.url}" /> </bean> <!-- activities client --> <bean id="activitiesClient" class="aws.flow.sample.MyActivitiesClientImpl" scope="workflow"> </bean> <!-- workflow implementation --> <bean id="workflowImpl" class="aws.flow.sample.MyWorkflowImpl" scope="workflow"> <property name="client" ref="activitiesClient" /> <aop:scoped-proxy proxy-target-class="false" /> </bean> <!-- WorkflowTest --> <bean id="workflowTest" class="com.amazonaws.services.simpleworkflow.flow.junit.spring.SpringWorkflowTest"> <property name="workflowImplementations"> <list> <ref bean="workflowImpl" /> </list> </property> <property name="taskListActivitiesImplementationMap"> <map> <entry> <key> <value>list1</value> </key> <ref bean="activitiesImplHost1" /> </entry> </map> </property> </bean> </beans>

Implementações de atividades fictícias

Você pode usar as implementações de atividades reais durante o teste, mas se desejar executar testes de unidade apenas da lógica do fluxo de trabalho, você deve usar atividades fictícias. Isso pode ser feito fornecendo uma implementação fictícia da interface de atividades para a classe WorkflowTest. Por exemplo:

@RunWith(FlowBlockJUnit4ClassRunner.class) public class BookingWorkflowTest { @Rule public WorkflowTest workflowTest = new WorkflowTest(); List<String> trace; private BookingWorkflowClientFactory workflowFactory = new BookingWorkflowClientFactoryImpl(); @Before public void setUp() throws Exception { trace = new ArrayList<String>(); // Create and register mock activity implementation to be used during test run BookingActivities activities = new BookingActivities() { @Override public void sendConfirmationActivity(int customerId) { trace.add("sendConfirmation-" + customerId); } @Override public void reserveCar(int requestId) { trace.add("reserveCar-" + requestId); } @Override public void reserveAirline(int requestId) { trace.add("reserveAirline-" + requestId); } }; workflowTest.addActivitiesImplementation(activities); workflowTest.addWorkflowImplementationType(BookingWorkflowImpl.class); } @After public void tearDown() throws Exception { trace = null; } @Test public void testReserveBoth() { BookingWorkflowClient workflow = workflowFactory.getClient(); Promise<Void> booked = workflow.makeBooking(123, 345, true, true); List<String> expected = new ArrayList<String>(); expected.add("reserveCar-123"); expected.add("reserveAirline-123"); expected.add("sendConfirmation-345"); AsyncAssert.assertEqualsWaitFor("invalid booking", expected, trace, booked); } }

Como alternativa, você pode fornecer uma implementação fictícia do cliente de atividades e injetá-la em sua implementação de fluxo de trabalho.

Teste de objetos de contexto

Se a implementação do seu fluxo de trabalho depender dos objetos de contexto da estrutura - por exemplo, o DecisionContext -, você não precisará fazer nada de especial para testar esses fluxos de trabalho. Quando um teste é executado por meio do WorkflowTest, ele injeta automaticamente objetos de contexto de teste. Quando a implementação do seu fluxo de trabalho acessar os objetos de contexto - por exemplo, usando DecisionContextProviderImpl - ela obterá a implementação de teste. Você pode manipular esses objetos de contexto de teste no código de teste (método @Test) para criar casos de teste interessantes. Por exemplo, se o fluxo de trabalho criar um temporizador, você poderá fazer com que o temporizador seja acionado chamando o método clockAdvanceSeconds na classe WorkflowTest para adiantar a hora do relógio. Você também pode acelerar o relógio para fazer com que os temporizadores sejam acionados antes do que seriam normalmente usando a propriedade ClockAccelerationCoefficient no WorkflowTest. Por exemplo, se o fluxo de trabalho criar um temporizador para uma hora, você poderá definir o ClockAccelerationCoefficient como 60 para fazer com que o temporizador acione em um minuto. Por padrão, o ClockAccelerationCoefficient é definido como 1.

Para obter mais detalhes sobre os pacotes com.amazonaws.services.simpleworkflow.flow.test and com.amazonaws.services.simpleworkflow.flow.junit, consulte a documentação do AWS SDK for Java.