No, I'm not going to update my prior posts with new screen shots. Things have been cleaned up and integration between the tools is now much tighter. However, there is no change to what lies underneath.
An overhaul of that magnitude would in most products warrant a major release change, however, the crew @ 1060 Research is too honest and rated the two-week effort with only a minor release.
In my previous three posts I looked at the core of Resource Oriented Computing. In this and couple more posts I am going to discuss a couple of very common patterns. Today the spotlight is on the mapper.
== Intermezzo (you can safely skip this) BEGIN ==
Before I embark on that quest - creating a real service as I do so - I want to spend a moment on what is known as the magic/silver bullet fallacy. Also known as the deus ex machina fallacy :
- There is no such thing as a free lunch (no, not even in the Google/Apple/... offices). You can safely ignore any paradigm that claims it will solve problems effortlessly. That goes against the second law of thermodynamics. ROC claims no such thing. What it does claim is that it allows you to solve problems a lot more efficiently !
- I can explain it to you but I can't understand it for you. I got this bon mot from the Skype status of a very smart developer. Ironically he didn't quite understand ROC, but that is not the point. The point is that effort is involved.
== Intermezzo (time to pay attention again) END ==
Amazon Web Services has grown into a large platform with a plethora of services (ec2, s3, ...). You can integrate these into your own solutions using the SDK or an HTTP service. In the latter case, each service has its own URL, which also depends on the region that you want to use the service in.
I'm ignoring the SDK for now. Integrating 3rd party libraries is a topic for another post.
In order to keep track of all these URLs, Amazon has published a regions.xml resource. As you can see, it contains all the URLs for all the services in all the regions. Woot !
But what if I just want to know what the Hostname is for the S3 service in the eu-west-1 region ? Well, that's the service I am going to build. It will define this resource : res:/amazonaws/baseurl/{service}-{region}.
For a first iteration we are going to download the regions.xml file and include it into our module itself as a file resource, res:/resources/xml/regions.xml.
<rootspace
name="amazonaws sdk import"
public="false"
uri="urn:com:amazonaws:sdk:import">
<fileset>
<regex>res:/resources/xml/.*</regex>
</fileset>
</rootspace>
The regions.xml file is an XML document. Processing an XML document can be done in many ways, I like using XSLT which takes the data and applies a transformation stylesheet to it. So we are also going to require a stylesheet, regions.xsl, which is also going to be a file resource, res:/resources/xsl/regions.xsl. We could define another fileset for that, but with a small change to the above we can cater for both :
<rootspace
name="amazonaws sdk import"
public="false"
uri="urn:com:amazonaws:sdk:import">
<fileset>
<regex>res:/resources/(xml|xsl)/.*</regex>
</fileset>
</rootspace>
The stylesheet itself looks like this :
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:nk="http://1060.org"
exclude-result-prefixes="nk">
<xsl:output
method="text"
encoding="UTF-8"
media-type="text/plain"/>
<xsl:param name="service" nk:class="java.lang.String"/>
<xsl:param name="region" nk:class="java.lang.String"/>
<xsl:template match="/XML">
<xsl:if test="Regions/Region[Name=$region]/Endpoint[ServiceName=$service]/Hostname">
<xsl:value-of select="Regions/Region[Name=$region]/Endpoint[ServiceName=$service]/Hostname"/>
</xsl:if>
<xsl:if test="not(Regions/Region[Name=$region]/Endpoint[ServiceName=$service]/Hostname)">
<xsl:text>unknown</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
There, that's that. NetKernel comes with a lot of tools for processing and transforming data and since its roots lie in the XML era, it is no surprise that an XSLT transformation engine is readily available. It is part of the xml:core toolset. Including xml:core changes the above space to this :
<rootspace
name="amazonaws sdk import"
public="false"
uri="urn:com:amazonaws:sdk:import">
<fileset>
<regex>res:/resources/(xml|xsl)/.*</regex>
</fileset>
<import>
<!-- active:xsltc -->
<uri>urn:org:netkernel:xml:core</uri>
</import>
</rootspace>
Right, as you probably deduced, I've put our bill-of-material in a private space, as I don't want any of it publicly available. Before we get to the public part, lets see for a moment what we can do in this space :
The full declarative request is :
<request>
<identifier>active:xsltc</identifier>
<argument name="operand">res:/resources/xml/regions.xml</argument>
<argument name="operator">res:/resources/xsl/regions.xsl</argument>
<argument name="region">data:text/plain,eu-west-1</argument>
<argument name="service" >data:text/plain,s3</argument>
<representation>java.lang.String</representation>
</request>
And no, I haven't done anything strange or new with that (although it is a new - and very welcome - feature of the Resource Trace Tool), it is the equivalent of :
active:xsltc+operand@res:/resources/xml/regions.xml+operator@res:/resources/xsl/regions.xsl+region@data:text/plain,eu-west-1+service@data:text/plain,s3
Which is certainly a level up from a simple resource request like res:/resources/xml/regions.xml (try that first if you're uncomfortable with the above) but you can see how it is actually merely pulling together four resource requests as input for the active:xsltc resource request. This process is called composing.
If you've looked at the documentation for active:xsltc, you're might wonder why I have the extra "region" and "service" arguments. Well, they are varargs (variable arguments) which in this case are "optional parameters provided to the stylesheet runtime". You'll find them used in the stylesheet.
What is left is to map our proposed resource res:/amazonaws/baseurl/{service}-{region} to the above request in a public space :
<rootspace
name="amazonaws sdk"
public="true"
uri="urn:com:amazonaws:sdk">
<mapper>
<config>
<endpoint>
<grammar>
<simple>res:/amazonaws/baseurl/{service}-{region}</simple>
</grammar>
<request>
<identifier>active:xsltc</identifier>
<argument name="operand">res:/resources/xml/regions.xml</argument>
<argument name="operator">res:/resources/xsl/regions.xsl</argument>
<argument method="as-string" name="region">[[arg:region]]</argument>
<argument method="as-string" name="service">[[arg:service]]</argument>
<representation>java.lang.String</representation>
</request>
</endpoint>
</config>
<space>
<import>
<uri>urn:com:amazonaws:sdk:import</uri>
<private/>
</import>
</space>
</mapper>
</rootspace>
While this may not all be immediately clear, it should be clear what happens. An incoming resource request of the form res:/amazonaws/baseurl/{service}-{region} gets mapped to an active:xsltc resource request which is issued into the wrapped space. The response representation goes the other way.
The method="as-string" attribute turns values into resources. Don't worry about that too much at this point. I could have specified those arguments as follows :
<argument name="region">data:text/plain,[[arg:region]]</argument>
<argument name="service">data:text/plain,[[arg:service]]</argument>
but then I'd have had to import the Layer1 space as I explained in my post about spaces.
Check out the result :
If you remember from last time (extra credits to you !) how to make this available in your browser as http://localhost:8080/amazonaws/baseurl/s3-eu-west-1 ... you've just created a real and useful service. Congratulations !
Having regions.xml as a file in your module is fine, but it would be better if we could use the regions.xml that Amazon has published. You can (this will require internet access to work). First replace
<argument name="operand">res:/resources/xml/regions.xml</argument>
with
<argument name="operand">https://raw.github.com/aws/aws-sdk-java/master/src/main/resources/etc/regions.xml</argument>
Then redeploy the module and check out the result :
The problem is that your space doesn't know yet how to resolve a http:// resource. Not a problem though, NetKernel has a toolkit available that does know :
<rootspace
name="amazonaws sdk import"
public="false"
uri="urn:com:amazonaws:sdk:import">
<fileset>
<regex>res:/resources/(xml|xsl)/.*</regex>
</fileset>
<import>
<!-- active:xsltc -->
<uri>urn:org:netkernel:xml:core</uri>
</import>
<import>
<!-- http:// scheme -->
<uri>urn:org:netkernel:client:http</uri>
</import>
</rootspace>
Redeploy, try again and you'll get the same result as before.
Summary
- We've looked at an interesting pattern today, the mapper pattern.
- We've composed resources together into new resources, we wrote no custom code.
- We've made use of tools provided in NetKernel.
- We have build a real service.