Integration and Interoperability Facilities Framework: Client Libraries Management Model
Contents
Building of Client Libraries
Detailed instructions for creating a gCube Maven components can be found [here]. In this section we concentrate on building Client Libraries as gCube components.
Building Profile
Assuming the streams library, which is a Maven component in gCube class DataAccess, the Maven coordinates of one of its development versions are:
<groupId>org.gcube.data.access</groupId> <artifactId>streams</artifactId> <version>1.0.0-SNAPSHOT</version>
Its POM specifies the following Maven parent:
<parent> <artifactId>maven-parent</artifactId> <groupId>org.gcube.tools</groupId> <version>1.0.0</version> <relativePath /> </parent>
The gCube Software profile of the library is as follows:
<?xml version="1.0" encoding="UTF-8"?> <Resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ID /> <Type>Service</Type> <Profile> <Description>Embedded Domain-Specific Language for Stream Transformations</Description> <Class>DataAccess</Class> <Name>streams</Name> <Version>1.0.0</Version> <Packages> <Software> <Name>streams</Name> <Version>1.0.0-SNAPSHOT</Version> <MavenCoordinates> <groupId>org.gcube.data.access</groupId> <artifactId>streams</artifactId> <version>1.0.0-SNAPSHOT</version> </MavenCoordinates> <Files> <File>streams-1.0.0-SNAPSHOT.jar</File> </Files> </Software> </Packages> </Profile> </Resource>
Notice that:
- the profile includes a single package and this package corresponds to the main build artefact of the component;
- the whole profile and the package have usual gCube coordinates;
- the package name is aligned with the Maven artifactId of the component;
- the package version is aligned with the Maven version of the component;
- the package includes MavenCoordinates that can be directly copied and pasted from the POM;
- the package does not specify dependencies;
- the package points to the main artefact of the Maven build;
The alignment between profile and POM can be exploited to simplify the management of the profile across different component versions. In particular, streams uses POM variables top define its profile:
<?xml version="1.0" encoding="UTF-8"?> <Resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ID /> <Type>Service</Type> <Profile> <Description>${description}</Description> <Class>DataAccess</Class> <Name>${artifactId}</Name> <Version>1.0.0</Version> <Packages> <Software> <Name>${artifactId}</Name> <Version>${version}</Version> <MavenCoordinates> <groupId>${groupId}</groupId> <artifactId>${artifactId}</artifactId> <version>${version}</version> </MavenCoordinates> <Files> <File>${build.finalName}.jar</File> </Files> </Software> </Packages> </Profile> </Resource>
Note that the variables must be resolved (i.e. the profile is interpolated) before the profile can be used within the system. In particular, the main destination of the profile is the gCube Software Archive (SA) which packages the component for registration within the system.
Package as a Service Archive
CLs are responsible for generating their own SA as part of its Maven build. This requires dedicated logic in the POM but has the advantage that:
- SA validity can be verified locally;
- no SA configurations are required in ETICS;
- the required build logic can be easily reused across components;
CLs use the Maven Assembly Plugin to generate their own SA, with an approach that makes optimal use of Maven variable interpolation in static files such as README
, svnpath.txt
, MAINTAINERS
, changelog.xml
, etc. In particular, the Assembly
plugin takes care of variable interpolation in the profile and other static files. The approach is best illustrated with a reference to the [sources] of the streams
library.
The entry for the Assembly
plugin in the POM is as follows:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptors> <descriptor>${distroDirectory}/descriptor.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>servicearchive</id> <phase>install</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>
Inside the artifact, there is a distro
folder including the static files, the profile.xml and the descriptor.xml. The descriptor.xml is used for building the service archive and its content can be found [here].
Testing of client libraries
Unit Testing using My-Container
When
When needing to exercise the functional features of a CL against the targeted service, the most convenient way to do it is through in-container testing, using my-container. My-container can be used as a convenience for running the tests through Maven or in Eclipse against a small distribution and for simple interactions, not requiring contacting the outside world.
How
To test your CL calls to the targeted web service through my container you need to add the relevant dependencies to its POM, declaring their scope as test. Assuming our web service is marked by the artifact id 'sample-service-multi-core', the declarations are:
<dependency> <groupId>org.gcube.tools</groupId> <artifactId>my-container</artifactId> <version>1.0.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.gcube.samples</groupId> <artifactId>sample-service-multi-core</artifactId> <version>1.0.0-SNAPSHOT</version> <type>gar</type> <scope>test</scope> </dependency>
Then you need to instruct Maven to install my-container and to deploy the service GAR in my-container for the service to be up and running. In the POM of the CL, instruct Maven to take it and place it in src/test/resources before the tests are ran:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.3</version> <executions> <execution> <id>install-service</id> <phase>generate-test-resources</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <includeArtifactIds>sample-service-multi-core</includeArtifactIds> <overWriteSnapshots>true</overWriteSnapshots> <includeTypes>gar</includeTypes> <excludeTransitive>true</excludeTransitive> <outputDirectory>src/test/resources</outputDirectory> <stripVersion>true</stripVersion> </configuration> </execution> <execution> <id>install-my-container</id> <phase>generate-test-resources</phase> <configuration> <artifactItems> <artifactItem> <groupId>org.gcube.tools</groupId> <artifactId>my-container</artifactId> <version>1.0.0</version> <type>tar.gz</type> <classifier>distro</classifier> <overWrite>false</overWrite> <outputDirectory>${project.basedir}</outputDirectory> </artifactItem> </artifactItems> <markersDirectory>${project.basedir}</markersDirectory> </configuration> <goals> <goal>unpack</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Note that when running the tests from the IDE, Eclipse does not go through all phases of a maven build, so either go to the shell and do:
> mvn generate-test-resources
so as to stage the resources necessary for testing, or do the same from within the IDE (simply running them as JUnit test won't do it).
JUnit Embedding
The testing code could be placed in the main() method of a test client, but the recommended approach is to embed it in a more suitable testing framework, such as JUnit. By doing so, we get a clear structure, proper integration with IDE and build tools, and a host of testing facilities which are standards de facto. We can simply annotate the test-suite as follows:
@RunWith(MyContainerTestRunner.class) public class MyTestSuite {...}
MyContainerTestRunner is a JUnit 4 test runner which replaces the default one to:
- create, configure, and start an instance of MyContainer before any other code in the test suite is executed by JUnit
- inject into the test-suite any port-type implementation or enpoint reference which we may need
- clearly name the output of any test with the name of the test itself
- stop the underlying instance of MyContainer after any other code in the test suite is executed by JUnit
Of course, we need to provide our Gar/s to the underlying MyContainer. We do that indirectly, by exposing static fields appropriately typed and annotated. The test runner will recognise these fields and pass the information they provide on to the instance of MyContainer that the runner handles, e.g.:
@RunWith(MyContainerTestRunner.class) public class MyTestSuite { @Deployment static Gar myGar = new Gar(new File("src/test/resources/sample-service-multi-core.gar")); @Test public void someTest() throws Exception {...} @Test public void anotherTest() throws Exception {...} ...
Integration Testing
When
My-container is a closed environment. Real integration testing ought to occur withing real installation of the GHN, not my-container.
To achieve this, it is not mandatory to go through full installation of the GHN distribution but we can do it with the use of the client-runtime-xxx jars. This kind of packaging offers a lightweight and fully embedded ghn distribution per infrastructure for use client-side. Clients can use this tool as a runtime dependency to satisfy base requirements for queries, notifications and more generally remote interactions through the gCore stack, which otherwise would require a physical installation client-side. The steps for using the ghn-client-runtime are to:
- get the jar for the target infrastucture (production / development) and add it in the classpath:
- start it from command line or programmatically
How
The proposed approach when testing with the ghn-client-runtime distro is to place the tests within the real main() code and not JUnit tests, as no automation should be sought here. Therefore, in this case it would be best to place the interactive tests outside the CL project, in a separate ...-integration-testsuite project. This is recommended as we do not want the client-runtime-xxx to be a dependency of our CL, as it won't build in ETICS (where the client-runtime-xxx does not exist - being an interactive test tool).
To bring the jar on the classpath, you need to add a dependency as in the example:
<dependency> <groupId>org.gcube</groupId> <artifactId>ghn-client-runtime</artivactId> <version>1.0.0-SNAPSHOT</version> <classifier>dev</classifier> <scope>test</scope> </dependency>
To start the client container programmatically, you call:
ClientRuntime.start();
Which installs the minimal GHN behind the scenes.
JUnit Embedding
If you wish to use JUnit instead, you could put the ClientRuntime.start() command in a static @BeforeClass method() and have multiple integration tests in a single file.