| <?php | |
| /******************************************************************************* | |
| * Copyright (c) 2014 Eclipse Foundation and others. | |
| * All rights reserved. This program and the accompanying materials | |
| * are made available under the terms of the Eclipse Public License v1.0 | |
| * which accompanies this distribution, and is available at | |
| * http://eclipse.org/legal/epl-v10.html | |
| * | |
| * Contributors: | |
| * Christopher Guindon (Eclipse Foundation) - Initial implementation | |
| *******************************************************************************/ | |
| ?> | |
| <!-- Main content area --> | |
| <div id="midcolumn"> | |
| <h1 id="jemo-architecture"><a class="anchor" href="#jemo-architecture"></a>1. Jemo Architecture</h1> | |
| <p>Jemo provides a homogeneous application runtime environment which spans multiple networks | |
| and device topologies. | |
| The application server will seamlessly scale out without boundaries and be able to take advantage | |
| of computational capacity anywhere the base runtime is present.</p> | |
| <p>To achieve this goal, Jemo relies on two basic abstractions the <code>CloudRuntime</code> which abstracts away | |
| the CSP specific services | |
| and the <code>Module</code> interface. Jemo offers the following development patterns, each one modeled by a sub | |
| interface of <code>Module</code>. | |
| A Jemo application (also called Jemo <code>plugin</code>) consists of one or more modules, i.e. | |
| classes each one implementing one of the sub interfaces of <code>Module</code> interface.</p> | |
| <table class="tableblock frame-all grid-all stretch"> | |
| <caption class="title">Table 1. Jemo development patterns</caption> | |
| <colgroup> | |
| <col style="width: 50%;"> | |
| <col style="width: 50%;"> | |
| </colgroup> | |
| <thead> | |
| <tr> | |
| <th class="tableblock halign-left valign-top">Development Pattern</th> | |
| <th class="tableblock halign-left valign-top">Interface to implement</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Web service</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>WebServiceModule</code></p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">event processing</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>EventModule</code></p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Batch processing</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>BatchModule</code></p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Fixed processing</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>FixedModule</code></p></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <p>When your app code is ready you have to deploy the plugin to a running Jemo instance. | |
| Jemo on this instance receives the jar file for this plugin, extracts metadata about it and stores both the jar | |
| and the metadata | |
| to the cloud via the selected CloudRuntime implementation. | |
| All the Jemo instances are notified about the new plugin.</p> | |
| <h3 id="the-webservicemodule"><a class="anchor" href="#the-webservicemodule"></a>1.1. The WebServiceModule</h3> | |
| <p>Every time an HTTP request is received by a Jemo instance for a module implementing the web service pattern, | |
| Jemo looks at the modules metadata to match the current endpoint with an existing module. | |
| If a match is found, Jemo downloads the jar of the plugin that contains the module | |
| from the cloud and deploys it locally to serve the request. | |
| After the request is served and after a period of inactivity the Jemo instance unloads the plugin.</p> | |
| <div class="listingblock"> | |
| <div class="title">Web service pattern</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">String getBasePath(); | |
| void process(HttpServletRequest request, HttpServletResponse response);</code></pre> | |
| </div> | |
| </div> | |
| <p>The <code>getBasePath</code> method provides the web service endpoint, while the <code>process</code> method | |
| is the place where the business logic | |
| for serving HTTP requests should be implemented.</p> | |
| <h3 id="the-eventmodule"><a class="anchor" href="#the-eventmodule"></a>1.2. The EventModule</h3> | |
| <p>Event processing is achieved via Jemo messages that are broadcasted across running Jemo instances. A Jemo | |
| instance eventually picks up the message | |
| and consumes it. A Jemo message declares the plugin id (app id) that generated the message, the target class | |
| that needs to consume the message, along | |
| with all other parameters needed to carry out the computation by the consumer.</p> | |
| <div class="listingblock"> | |
| <div class="title">Event processing pattern</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">JemoMessage process(JemoMessage message) throws Throwable;</code></pre> | |
| </div> | |
| </div> | |
| <p>The <code>process</code> method provides code to consume the JemoMessage.</p> | |
| <h3 id="the-batchmodule"><a class="anchor" href="#the-batchmodule"></a>1.3. The BatchModule</h3> | |
| <p>Batch processing is supported by scheduling a module to run the <code>processBatch</code> method once per | |
| a | |
| fixed amount of time, a.k.a. the batch frequency.</p> | |
| <div class="listingblock"> | |
| <div class="title">event processing pattern</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">void processBatch(String location, boolean isCloudLocation) throws Throwable;</code></pre> | |
| </div> | |
| </div> | |
| <p>The <code>processBatch</code> method provides the code for the batch calculation to be run by Jemo. | |
| Optionally, the developer can override the <code>getLimits</code> method of the <code>Module</code> | |
| interface, | |
| in order to control where and when the batch process run. | |
| The following table displays how you can control how many module instances run and where.</p> | |
| <table class="tableblock frame-all grid-all stretch"> | |
| <caption class="title">Table 2. Module Limit Batch Options</caption> | |
| <colgroup> | |
| <col style="width: 25%;"> | |
| <col style="width: 75%;"> | |
| </colgroup> | |
| <thead> | |
| <tr> | |
| <th class="tableblock halign-left valign-top">Method</th> | |
| <th class="tableblock halign-left valign-top">Description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>setMaxActiveBatchesPerGSM(N)</code></p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Run N instances of this module | |
| across | |
| Jemo instances.</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>setMaxActiveFixedPerLocation(N)</code></p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Run N instances of this module | |
| across | |
| Jemo instances within a location.</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>setMaxActiveFixedPerInstance(N)</code></p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Run N instances of this module on | |
| each | |
| running Jemo instance.</p></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <p>If you don’t override the <code>getLimits</code> method, the default is to have at max 1 active | |
| batch | |
| process per location, that runs every 1 minute.</p> | |
| <h3 id="the-fixedmodule"><a class="anchor" href="#the-fixedmodule"></a>1.4. The FixedModule</h3> | |
| <p>Finally, with fixed processing you can write code in a module and select how many module instances and on | |
| what cluster or network topology. | |
| Options are summarized in the following table.</p> | |
| <table class="tableblock frame-all grid-all stretch"> | |
| <caption class="title">Table 3. Module Limit Fixed Processing Options</caption> | |
| <colgroup> | |
| <col style="width: 30%;"> | |
| <col style="width: 70%;"> | |
| </colgroup> | |
| <thead> | |
| <tr> | |
| <th class="tableblock halign-left valign-top">Method</th> | |
| <th class="tableblock halign-left valign-top">Description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>setMaxActiveFixedPerGSM(N)</code> | |
| </p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Run N fixed processes of | |
| this module across Jemo instances</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>setMaxActiveFixedPerLocation(N)</code> | |
| </p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Run N fixed processes of | |
| this module across Jemo instances within a location.</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>setMaxActiveFixedPerInstance(N)</code> | |
| </p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Run N fixed processes of | |
| this module on each running Jemo instance.</p></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div class="listingblock"> | |
| <div class="title">Fixed processing pattern</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">void processFixed(final String location, final String instanceId) throws Throwable;</code></pre> | |
| </div> | |
| </div> | |
| <p>Developers used to deploy their apps with kubernetes will find the fixed processing pattern familiar. | |
| Notice that Kubernetes is used by the Jemo setup process to spin up a cluster of Jemo instances running | |
| on the selected CSP. | |
| This is different to traditional Kubernetes use, since in our case it is used to create a cluster of | |
| instances of | |
| the application server, rather than instances of the applications. For the latter, Jemo leverages cloud | |
| services as abstracted by | |
| the selected CloudRuntime.</p> | |
| <h2 id="getting-started"><a class="anchor" href="#getting-started"></a>2. Getting started</h2> | |
| <p>In this section we show how to develop a Jemo application by implementing all the offered development patterns. | |
| We start by creating a new maven project and provide the following content to the | |
| <code>pom.xml</code> | |
| file.</p> | |
| <div class="listingblock"> | |
| <div class="title">pom.xml</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-xml hljs" data-lang="xml"><?xml version="1.0" encoding="UTF-8"?> | |
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| <modelVersion>4.0.0</modelVersion> | |
| <groupId>org.eclipse.jemo</groupId> | |
| <artifactId>jemo-trader-app</artifactId> | |
| <version>1.0</version> | |
| <packaging>jar</packaging> | |
| <name>Jemo Trader App</name> | |
| <description>This is a demo project showing how to use Eclipse Jemo development patterns to create an application. | |
| As an example we model a Market with traders and stocks. | |
| </description> | |
| <properties> | |
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
| <maven.compiler.source>1.8</maven.compiler.source> | |
| <maven.compiler.target>1.8</maven.compiler.target> | |
| <jemo.username>[JEMO_USER]</jemo.username> | |
| <jemo.password>[JEMO_PASSWORD]</jemo.password> | |
| <jemo.id>1</jemo.id> | |
| <jemo.endpoint>https://localhost</jemo.endpoint> | |
| </properties> | |
| <repositories> | |
| <repository> | |
| <id>jemo-local</id> | |
| <url>${jemo.endpoint}/jemo</url> | |
| </repository> | |
| </repositories> | |
| <dependencies> | |
| <dependency> | |
| <groupId>org.eclipse.jemo</groupId> | |
| <artifactId>module-api</artifactId> | |
| <version>1.0</version> | |
| <scope>provided</scope> | |
| </dependency> | |
| </dependencies> | |
| <build> | |
| <plugins> | |
| <plugin> | |
| <artifactId>maven-assembly-plugin</artifactId> | |
| <configuration> | |
| <descriptorRefs> | |
| <descriptorRef>jar-with-dependencies</descriptorRef> | |
| </descriptorRefs> | |
| </configuration> | |
| <executions> | |
| <execution> | |
| <id>make-assembly</id> | |
| <phase>package</phase> | |
| <goals> | |
| <goal>single</goal> | |
| </goals> | |
| </execution> | |
| </executions> | |
| </plugin> | |
| <plugin> | |
| <artifactId>maven-deploy-plugin</artifactId> | |
| <configuration> | |
| <skip>true</skip> | |
| </configuration> | |
| </plugin> | |
| <plugin> | |
| <groupId>org.eclipse.jemo</groupId> | |
| <artifactId>eclipse-jemo-maven-plugin</artifactId> | |
| <version>1.0</version> | |
| <executions> | |
| <execution> | |
| <phase>deploy</phase> | |
| <goals> | |
| <goal>deploy</goal> | |
| </goals> | |
| <configuration> | |
| <outputJar>${project.build.finalName}-jar-with-dependencies</outputJar> | |
| <!-- Set the Jemo plugin id to upload here --> | |
| <id>${jemo.id}</id> | |
| <username>${jemo.username}</username> | |
| <password>${jemo.password}</password> | |
| <endpoint>${jemo.endpoint}</endpoint> | |
| </configuration> | |
| </execution> | |
| </executions> | |
| </plugin> | |
| </plugins> | |
| </build> | |
| </project></code></pre> | |
| </div> | |
| </div> | |
| <p>The only maven dependency needed is that of the <code>org.eclipse.jemo:module-api:1.0</code> | |
| artifact. | |
| This dependency is not found under any maven repository. Instead, it is offered by Jemo. | |
| That’s why we have to declare a <code><repositoy></code> element pointing to the url of | |
| a | |
| running Jemo instance | |
| e.g. <a href="http://localhost/jemo" class="bare">http://localhost/jemo</a> if you run Jemo locally. | |
| When maven downloads the dependencies, | |
| it will do an HTTP request to Jemo, to download the <code>module-api</code> artifact. Jemo packs the | |
| needed classes along with a generated | |
| pom file and sends it back over HTTP.</p> | |
| <p>To allow the above interaction and in fact any interaction of our module with Jemo, | |
| we need to use the <code>eclipse-jemo-maven-plugin</code> and provide the Jemo plugin id (app id) | |
| and | |
| jemo <code>username</code> and <code>password</code>. | |
| The Jemo plugin id is a number that uniquely identifies a plugin (Jemo app) across all the running | |
| jemo | |
| instances. | |
| The <code>username</code> and <code>password</code> values are generated by Jemo the first time you | |
| run | |
| it and printed in a log statement of the form:</p> | |
| <div class="listingblock"> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">System Authorisation Configured for the First Time: Admin username: *******@jemo.eclipse.org Password: *********** store this in a safe place as it will not be repeated</code></pre> | |
| </div> | |
| </div> | |
| <h3 id="demo-application-description"><a class="anchor" href="#demo-application-description"></a>2.1. | |
| Demo | |
| application description</h3> | |
| <p>In this demo we are going to build a simple trading app with Jemo that exercises the Jemo | |
| development | |
| patterns. | |
| The demo app consists of traders and stocks. Each trader has an account balance, a set of stocks | |
| he | |
| owns and can set target values | |
| for buying or selling stocks. The source code can be found on the <a href="https://github.com/eclipse/jemo.git" target="_blank">jemo repository</a> under the <demo>demos/jemo-trader-app</demo> directory.</p> | |
| <h4 id="jemo-module-lifecycle"><a class="anchor" href="#jemo-module-lifecycle"></a>2.1.1. Jemo | |
| Module | |
| Lifecycle</h4> | |
| <p>Jemo modules have a lifecycle modeled as methods of the Module interface. Jemo calls each one | |
| of | |
| these methods in different stages | |
| of the module’s lifecycle.</p> | |
| <table class="tableblock frame-all grid-all stretch"> | |
| <caption class="title">Table 4. Lifecycle methods</caption> | |
| <colgroup> | |
| <col style="width: 25%;"> | |
| <col style="width: 75%;"> | |
| </colgroup> | |
| <thead> | |
| <tr> | |
| <th class="tableblock halign-left valign-top">Method</th> | |
| <th class="tableblock halign-left valign-top">Description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>construct</code> | |
| </p> | |
| </td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Called when the module | |
| is | |
| created for the first time.</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>installed</code> | |
| </p> | |
| </td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Called the first time | |
| the | |
| module is registered.</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>upgraded</code> | |
| </p> | |
| </td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Called whenever the | |
| module | |
| is replaced by a new version.</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>start</code></p> | |
| </td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Called every time the | |
| module | |
| is started.</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>stop</code></p> | |
| </td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Called every time the | |
| module | |
| is stopped.</p></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <p>For instance in our demo application we use the <code>construct</code> method to initialize | |
| the | |
| data repositories.</p> | |
| <div class="listingblock"> | |
| <div class="title">construct method</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java"> @Override | |
| public void construct(Logger logger, String name, int id, double version) { | |
| WebServiceModule.super.construct(logger, name, id, version); | |
| TRADER_REPOSITORY = new TraderRepository(getRuntime()); | |
| STOCK_REPOSITORY = new StockRepository(getRuntime()); | |
| STOCK_REPOSITORY.findMaxId().ifPresent(maxId -> CURRENT_STOCK_ID = maxId + 1); | |
| }</code></pre> | |
| </div> | |
| </div> | |
| <p>In addition, we use the <code>installed</code> method to create NoSQL tables for storing the | |
| traders and stocks as collections of json objects | |
| and we add some initial traders and stocks.</p> | |
| <div class="listingblock"> | |
| <div class="title">installed method</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java"> @Override | |
| public void installed() { | |
| log.info("Installed phase. Initializing the database..."); | |
| TRADER_REPOSITORY.init(); | |
| STOCK_REPOSITORY.init(); | |
| // Create 20 stocks. | |
| final Stock[] stocks = IntStream.range(1, CURRENT_STOCK_ID) | |
| .mapToObj(id -> new Stock(String.valueOf(id), 100f)) | |
| .toArray(Stock[]::new); | |
| STOCK_REPOSITORY.save(stocks); | |
| // Create 10 traders and assign 2 stock to each one of them. | |
| final Trader[] traders = IntStream.range(1, 11) | |
| .mapToObj(id -> { | |
| final int stockIndex = 2 * (id - 1); | |
| final Trader trader = new Trader(String.valueOf(id), 1000f).acquire(stocks[stockIndex]); | |
| if (stockIndex + 1 < CURRENT_STOCK_ID) { | |
| trader.acquire(stocks[stockIndex + 1]); | |
| } | |
| return trader; | |
| }) | |
| .toArray(Trader[]::new); | |
| TRADER_REPOSITORY.save(traders); | |
| }</code></pre> | |
| </div> | |
| </div> | |
| <h4 id="implementing-the-web-service-pattern"><a class="anchor" | |
| href="#implementing-the-web-service-pattern"></a>2.1.2. | |
| Implementing the Web Service Pattern</h4> | |
| <p>The app exposes the following REST endpoints:</p> | |
| <table class="tableblock frame-all grid-all stretch"> | |
| <caption class="title">Table 4. Endpoinds</caption> | |
| <colgroup> | |
| <col style="width: 15%;"> | |
| <col style="width: 10%;"> | |
| <col style="width: 75%;"> | |
| </colgroup> | |
| <thead> | |
| <tr> | |
| <th class="tableblock halign-left valign-top">Endpoint</th> | |
| <th class="tableblock halign-left valign-top">HTTP operation</th> | |
| <th class="tableblock halign-left valign-top">Description</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>market/traders</code> | |
| </p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">GET</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Returns all the existing | |
| traders</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>market/traders/{id}</code></p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">GET</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Returns the trader with | |
| the | |
| specified id</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>market/traders/{id}</code></p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">PUT</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Updates (replaces) the | |
| state | |
| of the trader with the specified id with a new state provided as a json payload</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>market/traders/{id}</code></p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">PATCH</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Partially updates the | |
| state | |
| of the trader with the specified id with part of the state provided as a json | |
| payload</p> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>market/stocks</code> | |
| </p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">GET</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Returns all the existing | |
| stocks</p></td> | |
| </tr> | |
| <tr> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock"> | |
| <code>market/stocks/{id}</code></p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">GET</p></td> | |
| <td class="tableblock halign-left valign-top"><p class="tableblock">Returns the stock with | |
| the | |
| specified id</p></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <p>Tho implemented this, we declare a class <code>Market</code></p> | |
| <div class="listingblock"> | |
| <div class="title">Market.java</div> | |
| <div class="content"> | |
| <pre>public class Market implements WebServiceModule { | |
| ... | |
| @Override | |
| public String getBasePath() { | |
| return "/market"; | |
| } | |
| @Override | |
| public void process(HttpServletRequest request, HttpServletResponse response) throws Throwable { | |
| final String endpoint = request.getRequestURI().substring(request.getRequestURI().indexOf(getBasePath())); | |
| switch (request.getMethod()) { | |
| case "GET": | |
| Matcher matcher; | |
| log(INFO, "endpoint: " + endpoint); | |
| if ((matcher = ONE_TRADER_PATTERN.matcher(endpoint)).find()) { | |
| getTrader(matcher.group(1), response); | |
| } else if (TRADERS_PATTERN.matcher(endpoint).find()) { | |
| getAllTraders(response); | |
| } else if ((matcher = ONE_STOCK_PATTERN.matcher(endpoint)).find()) { | |
| getStock(matcher.group(1), response); | |
| } else if (STOCKS_PATTERN.matcher(endpoint).find()) { | |
| getAllStocks(response); | |
| } else { | |
| response.sendError(400); | |
| } | |
| break; | |
| case "PUT": | |
| if ((matcher = ONE_TRADER_PATTERN.matcher(endpoint)).find()) { | |
| updateTrader(matcher.group(1), request, response); | |
| } else { | |
| response.sendError(400); | |
| } | |
| break; | |
| case "PATCH": | |
| if ((matcher = ONE_TRADER_PATTERN.matcher(endpoint)).find()) { | |
| partialUpdateTrader(matcher.group(1), request, response); | |
| } else { | |
| response.sendError(400); | |
| } | |
| break; | |
| default: | |
| response.sendError(400); | |
| } | |
| } | |
| ... | |
| }</pre> | |
| </div> | |
| </div> | |
| <p>By implementing the <code>getBasePath</code> method to return <code>"/market"</code>, we end | |
| up | |
| with an endpoint of the form <code>/1/v1.0/market</code>. | |
| Jemo, uses a prefix of the form <code>/{plugin_id}/v{version_number}</code>, where | |
| <code>plugin_id</code> | |
| is defined on <code>pom.xml</code> | |
| and <code>version_number</code> is determined by the pom version. | |
| </div> | |
| <h4 id="implementing-the-event-processing-pattern"><a class="anchor" | |
| href="#implementing-the-event-processing-pattern"></a>2.1.3. | |
| Implementing the Event Processing Pattern</h4> | |
| <p>Whenever a trader changes his target values for buying or selling stocks, | |
| the demo app triggers an event by sending a JemoMessage. | |
| This message declares the class to consume it to be the <code>MarketMatcher</code> class, | |
| while | |
| also provide a parameter valued with the | |
| trader id, whose targets have been changed.</p> | |
| <div class="listingblock"> | |
| <div class="title">triggering events</div> | |
| <div class="content"> | |
| <pre> private void partialUpdateTrader(String id, HttpServletRequest request, HttpServletResponse response) throws IOException { | |
| log(INFO, "partialUpdateTrader"); | |
| final Optional<Trader> optionalTrader = TRADER_REPOSITORY.findById(id); | |
| if (optionalTrader.isPresent()) { | |
| final Trader trader = optionalTrader.get(); | |
| final Trader newState = Util.fromJSONString(Trader.class, Util.toString(request.getInputStream())); | |
| final boolean differsInTargetValue = newState.differsInTargetValue(trader); | |
| trader.setStockIdToBuyTargetValue(newState.getStockIdToBuyTargetValue()); | |
| trader.setStockIdToSellTargetValue(newState.getStockIdToSellTargetValue()); | |
| TRADER_REPOSITORY.save(trader); | |
| if (differsInTargetValue) { | |
| triggerEvent(trader.getId()); | |
| } | |
| respondWithJson(200, response, trader); | |
| } else { | |
| respondWithJson(404, response, null); | |
| } | |
| } | |
| private void triggerEvent(String traderId) { | |
| JemoMessage msg = new JemoMessage(); | |
| msg.setModuleClass(MarketMatcher.class.getName()); | |
| msg.setId("1"); | |
| msg.setPluginId(1); | |
| msg.getAttributes().put(TRADER_ID, traderId); | |
| msg.send(JemoMessage.LOCATION_LOCALLY); | |
| }</pre> | |
| </div> | |
| </div> | |
| <div class="paragraph"> | |
| <p>The <code>MarketMatcher</code> class implements the <code>EventModule</code> interface, overrides the | |
| <code>process</code> | |
| method to consume the message. | |
| It attempts to match the trader whose buy/sell target values have changed with other traders | |
| that may be interested in selling/buying respectively | |
| the same stock. In case that a match is found, it carries on with the transaction and | |
| updates | |
| the states of involved traders and stock.</p> | |
| </div> | |
| <div class="listingblock"> | |
| <div class="title">MarketMatcher.java</div> | |
| <div class="content"> | |
| <pre>public class MarketMatcher implements EventModule { | |
| public static final String TRADER_ID = "TRADER_ID"; | |
| @Override | |
| public JemoMessage process(JemoMessage msg) throws IOException { | |
| log(INFO, "Consuming message..."); | |
| final Trader sourceTrader = TRADER_REPOSITORY.findById((String) msg.getAttributes().get(TRADER_ID)).get(); | |
| final List<Trader> traders = TRADER_REPOSITORY.findAll(); | |
| Collections.shuffle(traders); | |
| for (Map.Entry<String, Float> entry : sourceTrader.getStockIdToBuyTargetValue().entrySet()) { | |
| final Optional<Trader> targetTrader = traders.stream() | |
| .filter(trader -> trader != sourceTrader && (trader.sellTargetValue(entry.getKey()) != null && trader.sellTargetValue(entry.getKey()) <= entry.getValue())) | |
| .findFirst(); | |
| if (targetTrader.isPresent()) { | |
| final Trader seller = targetTrader.get(); | |
| final Float value = seller.sellTargetValue(entry.getKey()); | |
| trade(sourceTrader, seller, entry.getKey(), value); | |
| break; | |
| } | |
| } | |
| for (Map.Entry<String, Float> entry : sourceTrader.getStockIdToSellTargetValue().entrySet()) { | |
| final Optional<Trader> targetTrader = traders.stream() | |
| .filter(trader -> trader != sourceTrader && trader.buyTargetValue(entry.getKey()) != null && trader.buyTargetValue(entry.getKey()) >= entry.getValue()) | |
| .findFirst(); | |
| if (targetTrader.isPresent()) { | |
| final Trader buyer = targetTrader.get(); | |
| final Float value = buyer.buyTargetValue(entry.getKey()); | |
| trade(buyer, sourceTrader, entry.getKey(), value); | |
| break; | |
| } | |
| } | |
| return null; | |
| } | |
| private void trade(Trader buyer, Trader seller, String stockId, Float value) { | |
| log(INFO, String.format("Matching buyer [%s] with seller [%s] for stock [%s] and value [%s]...", buyer.getId(), seller.getId(), stockId, value)); | |
| final Stock stock = STOCK_REPOSITORY.findById(stockId).get(); | |
| buyer.buy(stock, value); | |
| seller.sell(stock, value); | |
| stock.setValue(value); | |
| TRADER_REPOSITORY.save(buyer, seller); | |
| STOCK_REPOSITORY.save(stock); | |
| TRANSACTIONS.add(new Transaction(LocalDateTime.now(), buyer.getId(), seller.getId(), stockId, value)); | |
| } | |
| }</pre> | |
| </div> | |
| <h4 id="implementing-the-batch-processing-pattern"><a class="anchor" | |
| href="#implementing-the-batch-processing-pattern"></a>2.1.4. | |
| Implementing the Batch Processing Pattern</h4> | |
| <p>The demo application models a simplistic IPO event that happens regularly and adds a new | |
| stock to | |
| the market. | |
| A existing trader is selected randomly as the first owner of the stock. | |
| To implement this behaviour, we declare the <code>MarketIPO</code> class implementing the | |
| <code>Module</code> | |
| interface and override the <code>processBatch</code> | |
| method. With the <code>getLimits</code> method we control when and where the batch runs. | |
| In this case we instruct Jemo to run a single module instance across all the running Jemo | |
| instances, every 30 seconds.</p> | |
| <div class="listingblock"> | |
| <div class="title">MarketIPO.java</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">public class MarketIPO implements BatchModule { | |
| private static Random RANDOM = new Random(); | |
| @Override | |
| public void processBatch(String location, boolean isCloudLocation) throws Throwable { | |
| final Trader trader = TRADER_REPOSITORY.findById(String.valueOf(RANDOM.nextInt(10) + 1)).get(); | |
| final Stock stock = new Stock(String.valueOf(CURRENT_STOCK_ID++), 100f); | |
| log(INFO, String.format("An IPO occurred - Trader [%s] owns the stock [%s]", trader.getId(), stock.getId())); | |
| trader.acquire(stock); | |
| TRADER_REPOSITORY.save(trader); | |
| STOCK_REPOSITORY.save(stock); | |
| } | |
| @Override | |
| public ModuleLimit getLimits() { | |
| return ModuleLimit.newInstance() | |
| .setMaxActiveBatchesPerGSM(1) | |
| .setBatchFrequency(Frequency.of(TimeUnit.SECONDS, 30)) | |
| .build(); | |
| } | |
| }</code></pre> | |
| </div> | |
| </div> | |
| <h4 id="implementing-the-fixed-processing-pattern"><a class="anchor" | |
| href="#implementing-the-fixed-processing-pattern"></a>2.1.5. | |
| Implementing the Fixed Processing Pattern</h4> | |
| <p>Let’s assume we want to notify a consumer (or just log for simplicity) for all the | |
| transactions processed by each Jemo instance for the last 30 seconds. | |
| To achieve this, we implement the fixed processing pattern as shown in | |
| <code>MarketWatch</code> | |
| class.</p> | |
| <div class="listingblock"> | |
| <div class="title">MarketWatch.java</div> | |
| <div class="content"> | |
| <pre>public class MarketWatch implements FixedModule { | |
| public static final List<Transaction> TRANSACTIONS = new ArrayList<>(); | |
| public AtomicBoolean running = new AtomicBoolean(false); | |
| @Override | |
| public void start() { | |
| running.set(true); | |
| } | |
| @Override | |
| public void stop() { | |
| running.set(false); | |
| } | |
| @Override | |
| public void processFixed(String location, String instanceId) throws Throwable { | |
| log(INFO, String.format("Process fixed Location [%s] - instance [%s]: [%s]", location, instanceId, TRANSACTIONS)); | |
| while (running.get()) { | |
| // We could send the transactions to a consumer. For demo purposes we just log them. | |
| TRANSACTIONS.forEach(txn -> log(INFO, String.format("Txn processed in Location [%s] - instance [%s]: [%s]", location, instanceId, txn))); | |
| TRANSACTIONS.clear(); | |
| TimeUnit.SECONDS.sleep(30); | |
| } | |
| } | |
| @Override | |
| public ModuleLimit getLimits() { | |
| return ModuleLimit.newInstance() | |
| .setMaxActiveFixedPerInstance(1) | |
| .build(); | |
| } | |
| }</pre> | |
| </div> | |
| </div> | |
| <p>With the <code>getLimits</code> method we instruct Jemo to run one process on each Jemo instance. </p> | |
| <p>With the <code>processFixed</code> method we define an infinite loop that logs the | |
| transactions | |
| processed the last 30 seconds by this instance and then | |
| sleep for 30 secs.</p> | |
| <p>While a fixed process is meant to run forever, there are cases where Jemo needs to restart | |
| the module, e.g. in case of upgrades. Make sure you provide the proper code to <code>start</code> and | |
| <code>stop</code> methods to initialise state or free resources. | |
| E.g. in our example, we make sure that the loop in the <code>processFixed</code> method terminates. | |
| Jemo guarantees to run the number of fixed processes you have requested at all time. | |
| This behaviour is very similar with the kubernetes behaviour, where you would declare how many pods you want | |
| to run and when one pod goes down, kubernetes would create another one to replace it. | |
| </p> | |
| <h3 id="deploy-the-demo-application-on-jemo"><a class="anchor" | |
| href="#deploy-the-demo-application-on-jemo"></a>2.2. | |
| Deploy | |
| the Demo application on Jemo</h3> | |
| <p>To deploy the demo application on Jemo, make sure you have a Jemo instance running and point to | |
| it in the pom file. Then, run:</p> | |
| <div class="listingblock"> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-bash hljs" | |
| data-lang="bash">> mvn deploy</code></pre> | |
| </div> | |
| </div> | |
| <p>The following logs are printed by the Jemo instance specified on the <code>pom.xml</code> | |
| file. | |
| </p> | |
| <div class="listingblock"> | |
| <div class="title">deployment logs</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">{JEMO-{8f20729d-dab0-4ed1-9125-d7e116bd2766}} [1_jemo-tutorial-1-1.0.jar] has not been downloaded, and will now be downloaded and unpacked. | |
| {JEMO-{8f20729d-dab0-4ed1-9125-d7e116bd2766}} [1_jemo-tutorial-1-1.0.jar][2019-03-26T11:58:08.000] loading module classes [org.eclipse.jemo.tutorial.market.MarketIPO, org.eclipse.jemo.tutorial.market.MarketMatcher, org.eclipse.jemo.tutorial.market.Market] | |
| {1:1.0:Market} Construct phase... | |
| {1:1.0:Market} Installed phase. Initializing the database... | |
| {JEMO-{8f20729d-dab0-4ed1-9125-d7e116bd2766}} [1][1.000000][Market] will process HTTP/HTTPS/WEBSOCKET requests from the base path: /1/v1.0/market</code></pre> | |
| </div> | |
| </div> | |
| <p>Whenever an IPO occurs, you can observe a log of the form:</p> | |
| <div class="listingblock"> | |
| <div class="title">IPO logs</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">{1:1.0:MarketIPO} An IPO occurred - Trader [10] owns the stock [21]</code></pre> | |
| </div> | |
| </div> | |
| <p>Whenever a trader changes his target values, you can observe a log of the following form where | |
| the 3rd line is only logged when a match is found:</p> | |
| <div class="listingblock"> | |
| <div class="title">trader update logs</div> | |
| <div class="content"> | |
| <pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">{1:1.0:Market} updateTrader | |
| {1:1.0:MarketMatcher} Consuming message... | |
| {1:1.0:MarketMatcher} Matching buyer [8] with seller [2] for stock [3] and value [105.0]...</code></pre> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ./end #midcolumn --> | |
| <!-- Start of the right column --> | |
| <!-- ./end #rightcolumn --> |