Amazon AWS Simple Workflow
AWS Simple Workflow(SWF) from Amazon is a unique workflow solution comparing to traditional workflow products such as JBPM and OSWorkflow. SWF is extremely scalable and engineer friendly(in that flow is defined with Java code) while it comes with limitations and lots of gotchas.
Always use Flow Framework
The very first thing to know is, it's almost impossible to build a SWF application correctly without Flow Framework. Even though the low level SWF RESTful service API is public and available in SDK, for most workflow with parallelism, timer or notification, consider all possibilities of how each event can interlace with another, it's beyond manageable to write correct code with low-level API to cover all use cases. For this matter SWF is quite unique comparing to other thin-client AWS technologies.
The SWF flow framework heavily depends on AspectJ for various purposes. If you are not familiar with AspectJ in Eclipse and Maven, this article talks about challenges you may encounter while working with Flow Framework in Eclipse and Maven based on AWS Java SDK 1.3.26, and helps you understand what's going on.
Understand what needs to be done
Before following the official tutorial, it's important to understand what needs to happen and what development environment should achieve. Here's a list of steps, some of which is done by engineer and some is done by development environment which is either Eclipse or Maven. Engineer should be able to write and run fully functioning workflow with Eclipse only or Maven only.
- Write Workflow interface that defines interaction point of workflow, including how workflow is started, what signal we want to send to affect running workflow execution and the read-only call to get state of execution. Write activity interface. This step is documented here in official tutorial.
- Have Eclipse or Maven do APT code generation for activity and workflow interfaces. Note that client code generation has nothing to do with load-time vs. compile-time weaving. Client code is always generated at compile time of workflow and activity interfaces. AspectJ weaving applies to the workflow implementation and generated activity client code. For a FooWorkflow interface, if AspectJ is configured property, 10 classes should be generated at compile-time. They are
- FooWorkflowCliet
- FooWorkflowClientImpl
- FooWorkflowClientFactory
- FooWorkflowClientFactoryImpl
- FooWorkflowClientExternal
- FooWorkflowClientExternalImpl
- FooWorkflowClientExternalFactory
- FooWorkflowClientExternalFactoryImpl
- FooWorkflowSelfClient
- FooWorkflowSelfClientImpl
- For a FooActivities interface, 2 classes should be generated by AspectJ. Please verify if Eclipse or Maven correctly creates these generated java source and compile them to classes directory. If not, later part of this article will help troubleshoot.
- FooActivitiesClient
- FooActivitiesClientImpl
- Write implementation of Workflow interface using generated client code. The generated client code is used in Workflow implementation to call activity, run child workflow or restart itself, etc. Without correct client code this step can not be done.
- Now it comes to AspectJ weaving for Asynchronous and ExponentialRetry annotations. This step is to make sure workflow implementation is executed correctly at runtime by injecting additional code logic around the calls to methods annotated with two annotations. Normally it is the workflow implementation class and activity client code, FooActivityClientImpl.java, that calls annotated methods, so both workflow implementation and activity client code need AspectJ weaving. For this step user can choose between load-time weaving and compile-time weaving.
- For load-time weaving, workflow implementation is compiled like normal classes, the aop.xml file must exist in classpath and define the patterns of classes to apply advices and JVM must be launched with "-javaagent:/path/to/a/aspectjweaver.jar".
- For compile-time weaving, workflow implementation is compiled with AspectJ compiler and numerous inner classes are generated within workflow implementation and activity client code, around each call to annotated methods. There's no special requirement to JVM at runtime so the aop.xml file is not required.
- It's very important to write unit test for every workflow implementation. I found almost everyone, me included, makes mistake in the first version without unit test. Without weaving unit test can't run property, and it doesn't always fail either. It'd be hard to troubleshoot so it's important to verify previous step is done correctly before running unit tests. Following the tutorial, I also learned that the SWF junit classes does not work for junit 4.10 or newer version, 4.7 is the latest working version I tried.
- Launch WorkflowWorker and ActivityWorker with spring or not.
Eclipse
Client code generation
These steps are required in Ecilpse to enable APT code generation:
- Workflow and activity interfaces are annotated with @Workflow and @Activities.
- Annotation processing is enabled in Project properties/Java Compiler/Annotation Processing.
- AWS Java SDK jar and build-tool jar are in classpath, this usually means com.amazonaws:aws-java-sdk and com.amazonaws:aws-java-sdk-flow-build-tools is in pom.xml
If all three steps are done code generation should happen. Java code should be created to .apt_generated. If .apt_generated is empty verify the steps above.
Compile time weaving
Eclipse does compile-time weaving when
- Project is converted into an AspectJ project
- AWS Java SDK java is in AspectJ build path
One way to verify if Eclipse is configured right to interpret annotation is to look for the markers on left side of source editor for each call to method with @Asynchronous and @ExponentialRetry annotations.
Note that the marks only indicate Eclipse sees the annotation and knows to apply aspects, but doesn't necessarily mean it does right things for these aspects. Another way verify compile-time weaving is to find compiled inner classes created by AspectJ. There should be several inner classes named AjcClosure#. If you see the AjcClosure# for WorkflowImp and ActivitiesClientImpl classes, it's a pretty positive sign that compile-time weaving is happening properly.
$ find target/classes -type f | grep Ajc
target/classes/.../FooActivitiesClientImpl$AjcClosure1.class
target/classes/.../FooWorkflowImpl$AjcClosure1.class
target/classes/.../FooWorkflowImpl$AjcClosure3.class
target/classes/.../FooWorkflowImpl$AjcClosure5.class
...
Load time weaving
Load time weaving means classes are compiled with standard Java compiler, while JVM is launched with "-javaagent" argument pointing to an aspectjweaver jar file at runtime. Load-time weaving require aop.xml to exist in classpath.
Eclipse, Maven consistency
With M2E eclipse plugin, the compile destination directory in Eclipse is read from Maven so both Eclipse and Maven compiles classes into the same directory. Therefore with M2E it becomes very important to keep Eclipse and Maven configured consistently. If not, after Maven compiles code Eclipse often doesn't know the classes are generated differently. As a result the same code in Eclipse can behavior differently.Maven
Client code generation
Two things to be aware of to enable client code generation in Maven
- Add build tool dependency. Official Maven repository doesn't have it anymore so you need to install it from AWS Java SDK to local repository manually
- Configure APT maven plugin
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-flow-build-tools</artifactId>
<version>1.3.26</version>
</dependency>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.0-alpha-5</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
</plugin>
To verify, look for the 10 client classes in target/classes after a Maven build.
I had various troubles with Maven compiler plugin 3.0. Compiler plugin 2.5.1 or 2.5 works correctly.
Note that AspectJ maven plugin can do APT code generation as well so it seems simpler to have one AspectJ maven plugin do both APT code generation and compile-time weaving. However this does not work as the plugin can only do only one thing to a class. This means it can generate client code for workflow and activity interface, weave workflow implementation, but it can't continue to weave generated activity client code. As a result, the application mostly works except that @ExponentialRetry will be ignored. This problem has been discussed here.
Here is a fully working example of module that does APT code generation for workflow interface without AspectJ weaving.
An example
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<complianceLevel>1.6</complianceLevel>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<sources>
<source>
<basedir>${basedir}/src/main/java</basedir>
</source>
<source>
<basedir>${basedir}/target/generated-sources/annotation</basedir>
</sources>
<aspectLibraries>
<aspectLibrary>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
I had various troubles with Maven compiler plugin 3.0. Compiler plugin 2.5.1 or 2.5 works correctly.
Note that AspectJ maven plugin can do APT code generation as well so it seems simpler to have one AspectJ maven plugin do both APT code generation and compile-time weaving. However this does not work as the plugin can only do only one thing to a class. This means it can generate client code for workflow and activity interface, weave workflow implementation, but it can't continue to weave generated activity client code. As a result, the application mostly works except that @ExponentialRetry will be ignored. This problem has been discussed here.
Here is a fully working example of module that does APT code generation for workflow interface without AspectJ weaving.
Compile-time weaving
An example
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<complianceLevel>1.6</complianceLevel>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<sources>
<source>
<basedir>${basedir}/src/main/java</basedir>
</source>
<source>
<basedir>${basedir}/target/generated-sources/annotation</basedir>
</sources>
<aspectLibraries>
<aspectLibrary>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
To verify, look for the AjcClosure inner classes in target/classes after a Maven build. It's important to weave classes in both src/main/java and target/generated-sources/annotation directories because the APT generated activity client code also needs AspectJ weaving.
This module is an example of compile-time weaved workflow implementation as well as activity APT code generation.
Load-time weaving means there's nothing special for compilation but JVM needs to be launched with "-javaagent:" argument. In turn, Maven surefire plugin needs to be customized in order to run unit tests properly. In the POM file
<properties> <aspectj.version>1.6.11</aspectj.version> </properties>
...
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
<scope>runtime</scope>
</dependency>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-javaagent:${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar</argLine>
</configuration>
</plugin>
This module is an example of compile-time weaved workflow implementation as well as activity APT code generation.
Load-time weaving
Load-time weaving means there's nothing special for compilation but JVM needs to be launched with "-javaagent:" argument. In turn, Maven surefire plugin needs to be customized in order to run unit tests properly. In the POM file
<properties> <aspectj.version>1.6.11</aspectj.version> </properties>
...
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
<scope>runtime</scope>
</dependency>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-javaagent:${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar</argLine>
</configuration>
</plugin>
Comments