原文はこちら。
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2017/03/09/java-ee-based-microservice-on-application-container-cloud-with-payara-micro
このエントリでは、Payara Microを使って、Java EEベースのマイクロサービスを構築する方法をご紹介します。
RealTimeStockTicker.java
コンテナにアプリケーションをデプロイするのではなく、アプリケーションとともにコンテナをパッケージングする点にご注意ください。
Payara MicroライブラリにJava EE APIは存在するので、Java EE APIはコンパイル時のみ必要です(scope = provided)。
JDKを選択します。
ビルドトリガーを設定します。このビルドジョブは、(git pushなどの)Gitリポジトリの更新に応じて呼び出されます。
ビルドステップを追加します。
以下はコマンドの例です。
ビルドが完了した後、以下のことが可能になっています。
アーティファクト
確認画面
Application Container Cloudのアプリケーションを確認します。
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2017/03/09/java-ee-based-microservice-on-application-container-cloud-with-payara-micro
このエントリでは、Payara Microを使って、Java EEベースのマイクロサービスを構築する方法をご紹介します。
Payara MicroOracle Cloud (PaaS) Stackの以下のサービスを活用します。
http://www.payara.fish/payara_micro
- Developer Cloud service
コードのホスト(Git Repository)、および(他のOracle PaaSサービスとの統合による) Continuous Integration & Continuous Deployment機能の提供 - Application Container Cloud service
Java EE マイクロサービスを実行するためのスケーラブルなaPaaS
Overview
Payara Micro?
Payara Microとは、マイクロサービススタイルのアプリケーションを構築するためのJava EEベースのソリューションです。少々説明しますと・・・- Java EE
Payara MicroはJava EE Web Profile標準ならびにWeb Profileに含まれないその他の仕様(例えばBatch、Concurrency Utilitiesなど)もサポートします。 - It’s a library
これらの機能は全てカプセル化されたJARファイルとして利用できます。
Development model
Payara Microは複数のデプロイメントスタイルを選択できます。- WAR
Java EEアプリケーションをWARファイルにパッケージし、以下のかたちでPayara Microとともに起動します。java –jar payara-micro-<version>.jar --deploy mystocks.war
- Embedded mode
ライブラリあので、Javaアプリケーション内にAPIを使って埋め込むことができます。 - Uber JAR
Payara MicroはMavenをサポートしているので、 exec pluginを使い、fat JARとしてPayara MicroライブラリとともにWARファイルをパッケージします。exec:javaプラグイン
http://www.mojohaus.org/exec-maven-plugin/java-mojo.html
Benefits
潜在的なメリットは以下のようです。- Microservices friendly
ライブラリとしてJava EEを使えるため、アプリケーション内で簡単に利用でき、柔軟な方法(WAR+JAR、もしくはただのfat JAR)でパッケージできます。また、PaaS、コンテナベースのプラットフォームといった複数の環境で実行できます。 - Leverage Java EE skill set
JAX-RS、JPA、EJB、CDIといったJava EE仕様の知識を活用できます。
About the sample application
JAX-RSやEJB、CDI、WebSocketといったAPIを使うシンプルなJava EEアプリケーションです。これはNYSEの仮株券の株価の追跡に役立つアプリケーションです。- 利用者はNASDAQに上場している株価をシンプルなRESTインターフェースでチェックできます。
- リアルタイムの株価追跡も可能ですが、この機能はOracle (ORCL) に対してのみ有効です。
- EJBスケジューラがORCLを定期的にチェックして株価を取得し、CDIイベントを発行します。(CDI Event Observerとしてマークされた)WebSocketコンポーネントがそのイベントを受け取り、接続済みのクライアントに対し最新の価格をアップデートします。
- JAX-RS RESTエンドポイントを使ってオンデマンドで任意の企業の株価を取得します。これは(双方向、完全二重型のWebSocketインタラクションとは異なる)典型的なリクエスト-レスポンスベースのHTTPインタラクションです。
Code
では、関連する部分のコードを見ていきましょう(簡単にするためにimport文は省略しています)。RealTimeStockTicker.java
StockDataEventQualifier.java@ServerEndpoint("/rt/stocks")
public class RealTimeStockTicker {
//stores Session (s) a.k.a connected clients
private static final List<Session> CLIENTS = new ArrayList<>();
/**
* Connection callback method. Stores connected client info
*
* @param s WebSocket session
*/
@OnOpen
public void open(Session s) {
CLIENTS.add(s);
Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.INFO, "Client connected -- {0}", s.getId());
}
/**
* pushes stock prices asynchronously to ALL connected clients
*
* @param tickTock the stock price
*/
public void broadcast(@Observes @StockDataEventQualifier String tickTock) {
Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.INFO, "Event for Price {0}", tickTock);
for (final Session s : CLIENTS) {
if (s != null && s.isOpen()) {
/**
* Asynchronous push
*/
s.getAsyncRemote().sendText(tickTock, new SendHandler() {
@Override
public void onResult(SendResult result) {
if (result.isOK()) {
Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.INFO, "Price sent to client {0}", s.getId());
} else {
Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.SEVERE, "Could not send price update to client " + s.getId(),
result.getException());
}
}
});
}
}
}
/**
* Disconnection callback. Removes client (Session object) from internal
* data store
*
* @param s WebSocket session
*/
@OnClose
public void close(Session s) {
CLIENTS.remove(s);
Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.INFO, "Client discconnected -- {0}", s.getId());
}
}
StockPriceScheduler.java/**
* Custom CDI qualifier to stamp CDI stock price CDI events
*
*/
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface StockDataEventQualifier {
}
RESTConfig.java/**
* Periodically polls the Google Finance REST endpoint using the JAX-RS client
* API to pull stock prices and pushes them to connected WebSocket clients using
* CDI events
*
*/
@Singleton
@Startup
public class StockPriceScheduler {
@Resource
private TimerService ts;
private Timer timer;
/**
* Sets up the EJB timer (polling job)
*/
@PostConstruct
public void init() {
/**
* fires 5 secs after creation
* interval = 5 secs
* non-persistent
* no-additional (custom) info
*/
timer = ts.createIntervalTimer(5000, 5000, new TimerConfig(null, false)); //trigger every 5 seconds
Logger.getLogger(StockPriceScheduler.class.getName()).log(Level.INFO, "Timer initiated");
}
@Inject
@StockDataEventQualifier
private Event<String> msgEvent;
/**
* Implements the logic. Invoked by the container as per scheduled
*
* @param timer the EJB Timer object
*/
@Timeout
public void timeout(Timer timer) {
Logger.getLogger(StockPriceScheduler.class.getName()).log(Level.INFO, "Timer fired at {0}", new Date());
/**
* Invoked asynchronously
*/
Future<String> tickFuture = ClientBuilder.newClient().
target("https://www.google.com/finance/info?q=NASDAQ:ORCL").
request().buildGet().submit(String.class);
/**
* Extracting result immediately with a timeout (3 seconds) limit. This
* is a workaround since we cannot impose timeouts for synchronous
* invocations
*/
String tick = null;
try {
tick = tickFuture.get(3, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException ex) {
Logger.getLogger(StockPriceScheduler.class.getName()).log(Level.INFO, "GET timed out. Next iteration due on - {0}", timer.getNextTimeout());
return;
}
if (tick != null) {
/**
* cleaning the JSON payload
*/
tick = tick.replace("// [", "");
tick = tick.replace("]", "");
msgEvent.fire(StockDataParser.parse(tick));
}
}
/**
* purges the timer
*/
@PreDestroy
public void close() {
timer.cancel();
Logger.getLogger(StockPriceScheduler.class.getName()).log(Level.INFO, "Application shutting down. Timer will be purged");
}
}
StockDataParser.java/**
* JAX-RS configuration class
*
*/
@ApplicationPath("api")
public class RESTConfig extends Application{
}
/**
* A simple utility class which leverages the JSON Processing (JSON-P) API to filter the JSON
* payload obtained from the Google Finance REST endpoint and returns useful data in a custom format
*
*/
public class StockDataParser {
public static String parse(String data){
JsonReader reader = Json.createReader(new StringReader(data));
JsonObject priceJsonObj = reader.readObject();
String name = priceJsonObj.getJsonString("t").getString();
String price = priceJsonObj.getJsonString("l_cur").getString();
String time = priceJsonObj.getJsonString("lt_dts").getString();
return (String.format("Price for %s on %s = %s USD", name, time, price));
}
}
A note on packaging
開発の観点から、前述の通り、通常のWARベースのJava EEアプリケーションをfat JARとしてPayara Microのコンテナとともにパッケージします。コンテナにアプリケーションをデプロイするのではなく、アプリケーションとともにコンテナをパッケージングする点にご注意ください。
Payara MicroライブラリにJava EE APIは存在するので、Java EE APIはコンパイル時のみ必要です(scope = provided)。
Mavenプラグインを使ってfat JARを生成します<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<dependencies>
<dependency>
<groupId>fish.payara.extras</groupId>
<artifactId>payara-micro</artifactId>
<version>4.1.1.164</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>payara-uber-jar</id>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>fish.payara.micro.PayaraMicro</mainClass>
<arguments>
<argument>--deploy</argument>
<argument>${basedir}/target/${project.build.finalName}.war</argument>
<argument>--outputUberJar</argument>
<argument>${basedir}/target/${project.build.finalName}.jar</argument>
</arguments>
<includeProjectDependencies>false</includeProjectDependencies>
<includePluginDependencies>true</includePluginDependencies>
<executableDependency>
<groupId>fish.payara.extras</groupId>
<artifactId>payara-micro</artifactId>
</executableDependency>
</configuration>
</execution>
</executions>
</plugin>
Setting up Continuous Integration & Deployment
以下の章でOracle Developer Cloud Serviceで実施した構成について取り扱います。Project & code repository creation
以下のエントリのProject & code repository creationの章をご覧になるか、詳細についてはサービスのドキュメントをご覧ください。Tracking JUnit test results in Developer Cloud service
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2016/10/05/junit-testing-using-oracle-developer-cloud
Oracle® Cloud Using Oracle Developer Cloud Service
Creating a Project
http://docs.oracle.com/cloud/latest/devcs_common/CSDCS/GUID-3317B279-A9C0-4566-A289-BD651A89D7B5.htm#GUID-7B30C8EC-6CDA-4F14-9791-8AE3BB3E8343
Configure source code in Git repository
ローカルシステムから先ほど作成したDeveloper CloudのGitリポジトリにプロジェクトをPushします。Oracle® Cloud Using Oracle Developer Cloud Serviceこの操作はコマンドラインから実施しますが、事前にGitクライアントをローカルマシンにインストールしておく必要があります。Gitを使ってもいいですし、お好みのものを使うことができます。
Pushing an Existing Local Git Repository to an Empty Oracle Developer Cloud Service Git Repository
http://docs.oracle.com/cloud/latest/devcs_common/CSDCS/GUID-B4C03296-8497-4356-8C74-2031D1FB96FC.htm#CSDCS-GUID-A33E83CE-845C-4393-8C93-936527033715
Git
https://git-scm.com/downloads
cd <project_folder>
git init
git remote add origin <developer_cloud_git_repo>
//e.g. https://john.doe@developer.us.oraclecloud.com/developer007-foodomain/s/developer007-foodomain-project_2009/scm/sample.git//john.doe@developer.us.oraclecloud.com/developer007-foodomain/s/developer007-foodomain-project_2009/scm/sample.git
git add .
git commit -m "first commit"
git push -u origin master //Please enter the password for your Oracle Developer Cloud account when prompted
Configure build
新しいジョブを作成しましょう。JDKを選択します。
Continuous Integration (CI)
Git リポジトリを選択します。ビルドトリガーを設定します。このビルドジョブは、(git pushなどの)Gitリポジトリの更新に応じて呼び出されます。
ビルドステップを追加します。
- Maven : WARとfat JAR作成のためのビルドステップ
- Execute Shell : 必要なデプロイメント・ディスクリプタ(Application Container Cloudではmanifest.jsonが必要です)とともにアプリケーションJARをパッケージングするステップ
以下はコマンドの例です。
manifest.json の例です。zip -j accs-payara-micro.zip target/mystocks.jar manifest.json
デプロイ可能なZipファイルをアーカイブするためのビルド後のアクションを有効化します。{
"runtime": {
"majorVersion": "8"
},
"command": "java -jar mystocks.jar --port $PORT --noCluster",
"release": {
"build": "23022017.1202",
"commit": "007",
"version": "0.0.1"
},
"notes": "Java EE on ACC with Payara Micro"
}
Execute Build
デプロイメントの構成前に、デプロイメントの構成が参照可能なアーティファクトを生成するためにビルドを呼び出す必要があります。ビルドが完了した後、以下のことが可能になっています。
- ビルドログの確認
- アーカイブ済みのアーティファクトの確認
アーティファクト
Continuous Deployment (CD) to Application Container Cloud
新たにデプロイメント用のConfigurationを作成します。- 必要事項を入力し、Deployment targetを構成します
- Application Container Cloudインスタンスを構成します
- 最後の確認ページの自動デプロイメントオプションを構成します
確認画面
Application Container Cloudのアプリケーションを確認します。
Test the CI/CD flow
ちょっとコードを変更し、Developer Cloud ServiceのGitリポジトリにPushしてみましょう。すると以下を確認できるはずです。- 自動的にビルドが呼び出され、成功したら
- 自動的にデプロイメントプロセスが呼び出され
- つづいて新しいバージョンのアプリケーションをApplication Container Cloudに再デプロイする
Test the application
- 特定の企業の株価をチェックする場合、GETでURLをつつきます。以下はその例です(このURLはサンプルで、実際にデプロイした環境にあわせる必要があります)。
https://acc-p-m-mydomain.apaas.em1.oraclecloud.com/mystocks/api/stocks?ticker=AAPL - リアルタイムフィードをサブスクライブする場合、WebSocketクライアントを使って指定のエンドポイントURLにアクセスします。以下はその例です。
wss://acc-p-m-mydomain.apaas.em1.oraclecloud.com/mystocks/rrt/stocks
Simple WebSocket Client
https://chrome.google.com/webstore/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo?hl=en