[Docker] Announcing the release of Docker 17.03 CE

原文はこちら。
https://blogs.oracle.com/linux/announcing-the-release-of-docker-1703-ce

Docker 17.03 CE (Community Edition) for Oracle Linux 7のリリースを発表できうれしく思っています。
Dockerを使うとOracle LinuxシステムやDockerをサポートする他のOSとの間でアプリケーションの作成・配布が可能です。Dockerは、アプリケーションのパッケージ、実行を司るDocker Engineと、Software-as-a-Service (SaaS) クラウドでアプリケーションを共有するDocker HubやDocker Storeから構成されています。

Important

Docker 17.03 CE以後のバージョンはOracle Linux 7 (x86_64)でのみ動作します。Oracle Linux 6におけるDockerの最終リリースは1.12.6です。

Notable Updates

アップストリームのDockerリリースサイクルを変更に伴い、新しいバージョンスキームに変わっています。日付変数(YY.MM)をバージョン名に使用し、アップストリームバージョンのリリース時期がわかるようになっています。
Dockerのアップストリームのデフォルトストレージドライバが、devicemapperからoverlay2に変更されました。この変更により、-n ftype =1オプションを有効にして作成されていないXFS形式のファイルシステムを使用しているシステムで問題が発生する可能性があります。XFSをファイルシステムとして選択した場合、Oracle Linux 7のルートパーティションは自動的に-n ftype=0でフォーマットされるため、Oracleはdevicemapperを使うようデフォルトストレージドライバを戻し、Dockerを新規インストールシステムですぐに使えるようにしています。
現在overlay2ストレージドライバをお使いの場合、アップグレード時にストレージの移行が必要かどうかを判断するため、アップグレードの前に、Oracle Linux 7 Docker User's GuideのUpgrading Dockerの章をチェックしてください。
Oracle® Linux 7Docker User's Guide
https://docs.oracle.com/cd/E52668_01/E87205/html/index.html
2.1 Upgrading Docker
https://docs.oracle.com/cd/E52668_01/E87205/html/docker_install_upgrade_upgrade.html
overlay2ストレージドライバを使っている場合、SELinuxのEnforcingモードをサポートしませんので、Permissiveモードへの変更もしくはSELinuxを無効にしてください。
Docker Swarmの機能をTechnology Previewとして提供します。

Resources

Oracle LinuxへのDockerのインストールやアップグレードに関する詳細は、Oracle Linux 7 Docker User's Guideをご覧ください。
Oracle® Linux 7Docker User's Guide
https://docs.oracle.com/cd/E52668_01/E87205/html/index.html
Oracle製品はDockerイメージとしてOracle Container Registryからご利用いただけます。
Oracle Container Registry
https://container-registry.oracle.com/

[JavaScript, Database] Node-oracledb 2.0.13-Development is now on GitHub

原文はこちら。
https://blogs.oracle.com/opal/node-oracledb-2013-development-is-now-on-github

Node-oracledb 2.0.13-DevelopmentがGitHubから利用いただけるようになりました。Node-oracledbはNode.jsでOracle Databaseを使うためのインターフェースです。

Top features:

  • Version 2はODPI-C抽象化レイヤーをベースにしています。
    ODPI Oracle Database Programming Interface for Drivers and Applications
    https://github.com/oracle/odpi
  • サポートされるデータ型が増えました。
完全な変更履歴(Changelog)は以下をご覧ください。
Change Log
https://github.com/oracle/node-oracledb/blob/dev-2.0/CHANGELOG.md
node-oracledb 2.0.13-Developmentのドキュメントは以下からどうぞ。
node-oracledb 2.0 Documentation for the Oracle Database Node.js Add-on
https://github.com/oracle/node-oracledb/blob/dev-2.0/doc/api.md
是非テストいただき、2.0 Development リリースサイクルの間に、出来るだけ早期に問題をレポートいただきたく思います。
node-oracledb Issue tracker
https://github.com/oracle/node-oracledb/issues
これはDevelopment Releaseなので、まだ粗い部分があることは承知しています。GitHubのIssueを使ってトラッキングを開始します。
Tracking node-oracledb 2.0.13-Development issues
https://github.com/oracle/node-oracledb/issues/716
完全なインストール手順は以下からご覧いただけます。
Installing node-oracledb Version 2
https://github.com/oracle/node-oracledb/blob/dev-2.0/INSTALL.md
しかし、node-oracledb 2.0はまだnpmに上がっていませんので、GitHubから以下のようにインストールする必要があります。
oracledb on npm
https://www.npmjs.com/package/oracledb
npm install oracle/node-oracledb.git#dev-2.0
Oracle Client 12.2、12.1、11.2のいずれかのライブラリ(例えばOracle Instant Client BasicもしくはBasic Lightパッケージ)をPATHもしくはLD_LIBRARY_PATH(またはそれに相当するもの)に通す必要があります。
Oracle Instant Client
http://www.oracle.com/technetwork/database/features/instant-client/
macOSをご利用の場合、Oracle Clientライブラリを~/libもしくは/usr/local/libに配置する必要があります。ODPI-Cを利用するとインストールが少々簡単になります。OracleのHeaderファイルはもう必要ありません。OCI_LIB_DIRとOCI_INC_DIRという環境変数は不要になっています。C++11をサポートするコンパイラ、Python 2.7は必要ですが、node-oracledbバイナリ1個だけでOracle Client 11.2、12.1、12.2で動作し、node-oracledbビルドをマシン間でコピーする際の移植性が向上しています。 古いクライアント・ライブラリがサポートしていない新しいOracle Databaseの機能を使用しようとすると、ランタイム・エラーが発生するため、デプロイメント環境に似た環境でテストを行ってください。
このリリースにおけるその他の変更は以下の通りです。
  • Lob.close()close()メソッドの非同期版ではなく、LOBを即座に無効にします。そのため、他のメソッド呼び出しは全て無効になります。
  • Version 1で、LOB、ResultSetまたはその他のデータベース操作を実行中に接続を閉じようとした場合、この正しくないアプリケーション・ロジックの結果NJS-030、NJS-031またはNJS-032の「接続を解放できません」というエラーが発生していました。Version 2では接続を閉じることができますが、開いている接続に依存する操作は失敗します。
  • 一部のNJSおよびDPIのエラーメッセージと番号が変更されました。これは特に、ODPI-C使用によるDPIエラーに当てはまります。
  • 現在、Node.js 4、6、および8と互換性があります。
  • (Stringとして)カラム型LONGと(Bufferとして)LONG RAWを取得できるようになりました。これらの型のストリーミングはサポートされていないため、Node.jsとV8のメモリ制限に達すると、DBに格納された値を完全に取得できない場合があります。ストリーミングできるLOBを使用するようにアプリケーションを変換する必要があります。
  • TIMESTAMP WITH TIME ZONE日付型のサポートが追加されました。これらは、LOCAL TIME ZONEを使用してnode-oracledb内のDateオブジェクトにマッピングされます。TIME ZONEコンポーネントは、Node.jsのDateオブジェクトでは使用できません。
  • ROWIDのサポートが追加されました。明示的なfetchAsStringは必要なくなりました。データはデフォルトでStringとして取得します。
  • Added support for UROWIDのサポートが追加されました。データはStringとして取得します。
  • NCHAR 列および NVARCHAR2 列のクエリがサポートされました。データベース文字セットおよびデータベースの各国語文字セットによってはDMLのバインディングでデータを正しく挿入できないことがあります。
  • NCLOB 列のクエリがサポートされるようになりました。NCLOB データをストリームもしくはStringとして取得できます。データベース文字セットおよびデータベースの各国語文字セットによってはDMLのバインディングでデータを正しく挿入できないことがあります。
  • LOB fetchAsString およびfetchAsBuffer クエリ、LOBバインドにおけるサイズ制限がなくなりました。Node.jsおよびV8のメモリ制限ゆえに一度に大きなLOBを操作できないのは今まで通りです。V1では11.2クライアントライブラリを使ってnode-oracledbと紐付けたユーザーにのみ制限が影響しました。
  • エラーを生成するステートメントを、ステートメント・キャッシュから削除するようになりました。表定義が変更されている間に実行されているアプリケーションは、古くなったキャッシュエントリのために、使用不能なSQL文で終わらなくなっています。アプリケーションはまだエラーが発生することがありますが、それをトリガーにして無効なキャッシュエントリを削除し、以降の実行が成功するようにします。ODPI-Cには、いまの説明よりもずっと優れた方法でやる仕組みが備わっていますが、お尋ねになると、退屈させることになるかもしれません。もしくはODPI-Cのソースコードをチェックすることができます。
    ODPI-Cソースコード(odpi/src/dpiStmt.c)
    https://github.com/oracle/odpi/blob/v2.0.0-rc.1/src/dpiStmt.c#L535-L536
    ご注意いただきたいのは、Oracleのベスト・プラクティスは、アプリケーション実行中に決して表定義を変更しない、ということです。いくつかのテストフレームワークが表定義を変更することを知っていますが。。
これらの改善はすべて、ODPI-Cが基礎部分を取り扱ってくれているおかげです。ODPI-Cの使用は、Node.jsに公開できる数多くのものを既にサポートしているので、将来の機能のための大きな飛躍的進展といえます。ODPI-Cプロジェクトは、以前はnode-oracledbだけが使用するDPI層の拡張でしたが、現在では、ODPI-CはPython cx_Oracle 6で使用されており、他のさまざまなプロジェクトで使用されてきています。
node-oracledb DPI Layer Extension
https://github.com/oracle/node-oracledb/tree/v1.13.1/src/dpi
Python interface to Oracle Database conforming to the Python DB API 2.0 specification
https://github.com/oracle/python-cx_Oracle
たとえば、Tamás Gulácsiは、ODPI-Cを使用するGoドライバを開発してきました(彼のブランチをチェックしてください)。
v5 with ODPI ?
https://github.com/rana/ora/issues/203
An Oracle database driver for the Go programming language.
https://github.com/rana/ora/tree/dpi
Kubo TakehiroもRust以外のプログラミング言語の選択を決める前に、Oracle Rustドライバの開発を開始しました!
Oracle driver for Rust
https://github.com/kubo/rust-oracle-wip
我々が表明しているnode-oracledbの計画では、node-oracledb 1.xのメンテナンスはNode 4のEoLにあわせ2018年4月1日で終了します。そのため、node-oracledb 2.0.13-Developmentのテストを開始します。
Node-oracledb 1.x and 2.x: Plans for 2017
https://github.com/oracle/node-oracledb/issues/601

[Database] Python cx_Oracle 6.0 RC 1 is now on PyPI

原文はこちら。
https://blogs.oracle.com/opal/python-cx_oracle-60-rc-1-is-now-on-pypi

Python cx_Oracle 6.0のRC1(Release Candidate 1)がPyPIからご利用いただけるようになりました。是非お試しいただいて、フィードバックください。
python-cx_Oracle Issueページ
https://github.com/oracle/python-cx_Oracle/issues
Python cx_Oracle はOracle DatabaseのPython Interfaceです。Version 6は新しいODPI-C抽象化層をベースにしており、これもまた現在Release Candidateフェーズです。
ODPI-C (Oracle Database Programming Interface for Drivers and Applications)
https://github.com/oracle/odpi
この層のおかげで、cx_Oracleコード自体を大幅にシンプルにすることができました。

final beta以後、cx_Oracle RC1に少し微調整が入っていますので、リリースノートをご覧ください。
cx_Oracle Release Notes
Version 6.0 rc 1 (June 2017)
http://cx-oracle.readthedocs.io/en/latest/releasenotes.html#version-6-0-rc-1-june-2017
変更点のいくつかをご紹介します。

  • ODPI-CがLONGおよびLONG RAW列のバッファサイズを自動的に管理するため、Cursor.setoutputsize() メソッドは不要になりました。
  • セッションプールを作成したり、Python 2.7でパスワードを変更したりするために、(文字列に加えて)unicodeを使用することができます。

ODPI-Cを使うことで、Python Wheelを作成でき、インストールがより簡単になりました。
PyPIからcx_Oracle 6.0 RC 1をインストールするには、以下のコマンドを使ってください。
python -m pip install cx_Oracle --pre
cx_Oracle 6.0rc1 - Python interface to Oracle
https://pypi.python.org/pypi/cx_Oracle/6.0rc1
あと、実行時にOracle Client 12cR2、12cR1、11gR2のライブラリ(Oracle Instant Client Basicパッケージ)にPATHを通しておく必要があります。
Oracle Instant Client
http://www.oracle.com/technetwork/database/features/instant-client/
cx_Oracleのドキュメントは以下のURLからどうぞ。
cx_Oracle documentation
http://cx-oracle.readthedocs.io/en/latest/

[Linux, Cloud] Oracle Ksplice on Oracle Linux in Bare Metal Cloud

原文はこちら。
https://blogs.oracle.com/wim/oracle-ksplice-on-oracle-linux-in-bare-metal-cloud

Oracle Cloudを使用することの大きな利点の1つは、完全なOracle Linuxサポートが含まれていることです。Oracle Cloudを使用すると、Oracle Linux Premier Supportで得られるサービスはすべて含まれており、追加費用は必要ありません。

Oracle Kspliceはそのようなサービスです。
Oracle Ksplice
http://www.ksplice.com/
Oracle Cloud以外でOracle Kspliceを使用する場合、インストール時にOracle LinuxサーバーをUnbreakable Linux Network (ULN)に登録し、生成されたアクセス・キーを使用してKsplice Uptrackを構成します。
Unbreakable Linux Network (ULN)
http://linux.oracle.com
ですが、Oracle Cloud(Oracle Public CloudとOracle Bare Metal Cloud Services、以下BMCS)でお使いの場合、非常に簡単です。
Oracle Cloud
https://cloud.oracle.com/ja_JP/home
Oracle Cloud上のインスタンスはすべて、kspliceサーバに即座にアクセスできます。

BMCSに既存のOracle Linuxインスタンスを持つお客様またはご利用の方の場合、Kspliceを有効にするための簡単な手順をいくつか実行する必要があります。現在デフォルトでイメージにuptrackツールを追加している作業をしているため、もうすぐすると、この構成作業をする必要はなくなります。

今すぐKspliceを有効にするには:

opcユーザー(またはroot)としてOracle Linuxインスタンスにログインします。
# sudo bash
uptrackクライアントをダウンロードします。
# wget -N https://www.ksplice.com/uptrack/install-uptrack
cURLがお好みであれば、こちらで。
# curl -O https://www.ksplice.com/uptrack/install-uptrack
クライアントをインストールし、このキーを使っていることを確認しましょう。このキーはBMCSでのみ有効で、一般的な識別子です。
# sh install-uptrack dfc21b3ced9af52f6a8760c1b1860f928ba240970a3612bb354c84bb0ce5903e --autoinstall
このコマンドを使って、ダウンロード済みのスクリプトを展開し、unpackユーティリティ(Kspliceクライアントツール)をインストールします。接続エラーを無視するには以下の手順が必要です。

もう一つ、上記のキーが機能するためには、uptrackツールの接続先を特定のアップデートサーバーに指定する必要があります。
/etc/uptrack/uptrack.confを編集します。
# The location of the Uptrack updates repository.

update_repo_url=https://oraclecloud-updates-ksplice.oracle.com/update-repository
これで終わりです。
# uptrack-upgrade
Nothing to be done.
Your kernel is fully up to date.
Effective kernel version is 4.1.12-94.3.6.el6uek
BYOLのインスタンスの場合、上記の手順も自動化しますが、少なくともこれですぐに利用できるようになります。

[Java, Cloud] Develop Microservices application using CQRS and Event sourcing patterns in Oracle Cloud

原文はこちら。
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2017/03/30/develop-microservices-application-using-cqrs-and-event-sourcing-patterns

このエントリでは、CQRSとイベントソーシングパターンを使用して、イベント駆動のマイクロサービスアプリケーションを構築する方法を紹介します。 以下は、このエントリで説明する概念を簡潔にまとめたものです。詳細は、このブログの最後にあるリソースから入手してください。

What is a Microservice?

このアーキテクチャースタイルにはずばりこれ、という定義はありませんが、Adrian Cockcroftによれば、マイクロサービスアーキテクチャは、境界付けられたコンテキストを持つ要素を疎結合で構成したサービス指向アーキテクチャと定義しています。
Adrian Cockcroftについて
http://www.battery.com/our-team/member/adrian-cockcroft/

What is a Bounded Context?

Bounded Context(境界付けられたコンテキスト)とは、ドメインモデルやデータモデル、アプリケーションサービスといったシングルドメインの詳細をカプセル化するというコンセプトで、別の境界付けられたコンテキストやドメインとの統合ポイントを定義します。

What is CQRS?

Command Query Responsibility Segregation (CQRS) とは、ドメイン操作をクエリとコマンドの2つのカテゴリに分けるアーキテクチャパターンです。クエリは状態を変更せずに結果を返すだけですが、コマンドはドメインモデルの状態を変更する操作です。

Why CQRS?

アプリケーション・ライフサイクルでは、論理モデルがより複雑で構造化されると、ユーザーエクスペリエンスに影響を与える可能性があるため、ユーザーエクスペリエンスは、コアシステムとは独立していなければなりません。
スケーラブルでアプリケーションのメンテナンスを簡単にするため、読み取りモデルと書き込みモデル間の制約を減らす必要があります。読み書きを分ける理由は以下の通りです。
  • スケーラビリティ
    読み取りが書き込みを上回るので、それぞれのスケール要件が異なり、より適切に対応できる
  • フレキシビリティ
    別々の読み取り/書き込みモデル
  • 複雑性の軽減
    複雑さを別々の問題にシフト

What is Event sourcing?

イベントソーシングは、異なるイベント中心のアプローチを使ってビジネスエンティティを永続化することで、アトミック性を実現します。
WhyEventSourcing
https://github.com/cer/event-sourcing-examples/wiki/WhyEventSourcing
エンティティの現在の状態を格納するのではなく、アプリケーションは、エンティティの状態を変更した一連の「イベント」を格納します。アプリケーションは、イベントを再生してエンティティの現在の状態を再構築できます。イベントの保存は単一の操作であるため、本質的にアトミックであり、分散トランザクションに通常関連付けられる2PC(2フェーズコミット)を必要としません。

Overview

このエントリでは、CQRSとイベントソーシングパターンを適用して、追加、削除、および読み取り操作を伴う「カート」という単一の境界付けられたコンテキストで構成される単純なマイクロサービスアプリケーションを開発する方法について説明します。このサンプルには機能的な意味はありませんが、基礎となるパターンとその実装を理解するのに十分でしょう。次の図は、CQRSおよびイベントソーシングパターンを使用してアプリケーションを構築する際のアクティビティの概念フローを示しています。

cqrs-es_1.jpg
Figure 1 CQRS and Event sourcing
このエントリで紹介しているサンプルで利用しているテクノロジースタックは以下の通りです。
  • Spring Boot : https://projects.spring.io/spring-boot/
    アプリケーションの構築およびパッケージング
  • Axon : http://www.axonframework.org/
    CQRSとイベントソーシングのためのSpringを使ったフレームワーク。 AxonはJava用のオープンソースのCQRSフレームワークで、CQRSとイベントソーシング・アーキテクチャパターンを使用してアプリケーションを構築するのに役立つ、集約、リポジトリー、イベントバスといった最も重要なビルディングブロックの実装を提供します。また、上記のビルディングブロックの独自の実装を提供することもできます。
  • Oracle Application Container Cloud : https://cloud.oracle.com/ja_JP/acc
    アプリケーションのデプロイ先
このような前提を踏まえて、サンプルの構築をすすめていきましょう。

Identify Aggregate Root

第1のステップは、境界付けられたコンテキストを識別し、境界付けられたコンテキストのドメインエンティティを識別することです。これは('account'、 'order' ...といった)Aggregate Root(集約ルート)を定義するのに役立ちます。Aggregate(集約)とは、常に一貫性のある状態に保たれるエンティティまたはエンティティのグループです。Aggregate Rootは、この一貫性のある状態を維持する責任を負う集約ツリーの最上位にあるオブジェクトです。
今回は簡単のため、ドメインモデルの唯一のAggregate Rootとして「カート」を考えます。 通常のショッピングカートのように、カート内のアイテムは、そのカートへの追加または削除されたものによって調整されます。

Define Commands

このAggregate Rootには2個のコマンドがあります。
  • Cartへの追加Command
    AddToCartCommand クラスでモデリング
  • Cartからの削除Command
    RemoveFromCartCommand クラスでモデリング
public class AddToCartCommand { 

    private final String cartId; 
    private final int item; 

    public AddToCartCommand(String cartId, int item) { 
        this.cartId = cartId; 
        this.item = item; 
    } 

    public String getCartId() { 
        return cartId; 
    } 

    public int getItem() { 
        return item; 
    } 
} 

public class RemoveFromCartCommand { 

    private final String cartId; 
    private final int item; 

    public RemoveFromCartCommand(String cartId, int item) { 
        this.cartId = cartId; 
        this.item = item; 
    } 

    public String getCartId() { 
        return cartId; 
    } 

    public int getItem() { 
        return item; 
    } 
} 
ご存じのとおり、これらのコマンドはただのPOJOで、システム内で何が起こる必要があるのか、また必要な情報とともにシステム内で発生する必要のあるものごとを捕捉するために使います。Axonフレームワークでは、インターフェイスの実装やクラスの拡張を行うコマンドは必要ありません。

Define Command Handlers

コマンドにはハンドラが1つだけあります。以下のクラスはカートへの追加およびカートからの削除コマンドのハンドラを表します。
@Component  
public class AddToCartCommandHandler {  

    private Repository repository;  

    @Autowired  
    public AddToCartCommandHandler(Repository repository) {  
        this.repository = repository;  
    }  

    @CommandHandler  
    public void handle(AddToCartCommand addToCartCommand){  
        Cart cartToBeAdded = (Cart) repository.load(addToCartCommand.getCartId());  
        cartToBeAdded.addCart(addToCartCommand.getItem());  
    }  
}  

@Component  
public class RemoveFromCartHandler {  

    private Repository repository;  

    @Autowired  
    public RemoveFromCartHandler(Repository repository) {  
        this.repository = repository;  
    }  

    @CommandHandler  
    public void handle(RemoveFromCartCommand removeFromCartCommand){  
        Cart cartToBeRemoved = (Cart) repository.load(removeFromCartCommand.getCartId());  
        cartToBeRemoved.removeCart(removeFromCartCommand.getItem());  
    }  
}
Spring BootともにAxonを使用しているため、上で定義したSpring Beanには@CommandHandlerというアノテーションが付いたメソッドがあり、これをコマンドハンドラとして扱うことができます。@Componentアノテーションは、アプリケーション起動時にこれらのBeanをスキャンし、任意のAuto wired ResourceをこのBeanに注入します。Aggregateに直接アクセスするのではなく、AxonフレームワークのドメインオブジェクトであるRepositoryが、Aggregateの取得と永続化を抽象化します。

Application Startup

以下のAppConfigurationクラスは、アプリケーション・デプロイ時に初期化され、パターン実装時に必要なコンポーネントを作成するSpring構成クラスです。
@Configuration 
@AnnotationDriven 
public class AppConfiguration { 
 
    @Bean 
    public DataSource dataSource() { 
        return DataSourceBuilder 
                .create() 
                .username("sa") 
                .password("") 
                .url("jdbc:h2:mem:axonappdb") 
                .driverClassName("org.h2.Driver") 
                .build(); 
    } 

    /**
    * Event store to store events
    */ 
    @Bean 
    public EventStore jdbcEventStore() { 
        return new JdbcEventStore(dataSource()); 
    } 

    @Bean 
    public SimpleCommandBus commandBus() { 
        SimpleCommandBus simpleCommandBus = new SimpleCommandBus(); 
        return simpleCommandBus; 
    } 

    /**
    *  Cluster event handlers that listens to events thrown in the application.
    */ 
    @Bean 
    public Cluster normalCluster() { 
        SimpleCluster simpleCluster = new SimpleCluster("simpleCluster"); 
        return simpleCluster; 
    } 


    /**
    * This configuration registers event handlers with defined clusters
    */ 
    @Bean 
    public ClusterSelector clusterSelector() { 
        Map<String, Cluster> clusterMap = new HashMap<>(); 
        clusterMap.put("msacqrses.eventhandler", normalCluster()); 
        return new ClassNamePrefixClusterSelector(clusterMap); 
    } 

    /**
    *The clustering event bus is needed to route events to event handlers in the clusters. 
    */ 
    @Bean 
    public EventBus clusteringEventBus() { 
        ClusteringEventBus clusteringEventBus = new ClusteringEventBus(clusterSelector(), terminal()); 

        return clusteringEventBus; 
    } 

    /**
    * Event Bus Terminal publishes domain events to the cluster
    *
    */ 
    @Bean 
    public EventBusTerminal terminal() { 
        return new EventBusTerminal() { 
            @Override 
            public void publish(EventMessage... events) { 
                normalCluster().publish(events); 
            } 
            @Override 
            public void onClusterCreated(Cluster cluster) { 
            } 
        }; 
    } 

    /**
    * Command gateway through which all commands in the application are submitted
    *
    */ 

    @Bean 
    public DefaultCommandGateway commandGateway() { 
        return new DefaultCommandGateway(commandBus()); 
    } 

    /**
    * Event Repository that handles retrieving of entity from the stream of events.
    */ 
    @Bean 
    public Repository<Cart> eventSourcingRepository() { 
        EventSourcingRepository eventSourcingRepository = new EventSourcingRepository(Cart.class, jdbcEventStore()); 
        eventSourcingRepository.setEventBus(clusteringEventBus()); 

        return eventSourcingRepository; 
    } 
} 
Axonが提供する、このクラスで初期化された主要なインフラストラクチャコンポーネントを見てみましょう。

Command bus

図1の通り、command bus(コマンドバス)は、それぞれのコマンドハンドラにコマンドをルーティングするコンポーネントです。Axon Frameworkには、コマンドをコマンド・ハンドラに渡すために使用可能な種々のCommand Busが付属しています。 AxonのCommand Busの実装の詳細は以下のURLを参照してください。
Axon Framework 2.0.9 Reference Guide
Command Handling
http://www.axonframework.org/docs/2.0/command-handling.html
この例では、SpringのアプリケーションコンテキストでBeanとして構成されているSimpleCommandBusを使用します。

Command Gateway

Command Bus直接コマンドを送信できますが、通常はCommand Gatewayを使用することをお勧めします。Command Gatewayを使用することで、開発者はコマンドのインターセプト、障害時の再試行の設定といった機能を実行できます。この例では、Command Busを直接使用せず、コマンドを送信するSpring Beanとして構成されているDefaultCommandGatewayを使用します。これはAxonがデフォルトで提供しているものです。

Event Bus

図1の通り、Aggregate Rootで開始されたコマンドは、イベントとして送信され、Event Storeで永続化されます。Event Busは、イベントをイベントハンドラにルーティングする基盤です。Event Busはメッセージディスパッチの観点からCommand Busに似ていますが、基本的に異なるものです。

Command Busは、近い将来に何が起こるかを定義するコマンドとともに動作し、コマンドを解釈するコマンドハンドラは1つだけです。それに対し、Event Busでは、イベントをルーティングし、イベントに対してゼロ個以上のハンドラを使って、過去に発生したアクションを定義します。

Axonは、Event Busの複数の実装を定義していますが、今回はSpring Beanとして再度ワイヤリングされたClusteringEventBusを使用します。AxonのEvent Bus実装の詳細については、以下のURLを参照してください。
Axon Framework 2.0.9 Reference Guide
6. Event Processing
http://www.axonframework.org/docs/2.0/event-processing.html

Event Store

リポジトリはドメインオブジェクトの現在の状態ではなくドメインイベントを格納するため、イベントストアを構成する必要があります。Axon Frameworkでは、JDBC、JPA、ファイルシステムなどの複数の永続化方式を使用してイベントを格納できます。今回は、JDBCイベントストアを使用します。

Event Sourcing Repository

今回はAggregate Rootを永続メカニズムの表現から作成せず、Event sourcingリポジトリを使って構成できるイベントストリームから作成します。ドメインイベントを発行する予定であるため、前に定義したEvent Busを使用してリポジトリを設定します。

Database

今回はデータストアとしてインメモリデータベース(h2)を使います。Spring Bootのapplication.propertiesにはデータソース構成の設定が含まれています。
# Datasource configuration 
spring.datasource.url=jdbc:h2:mem:axonappdb 
spring.datasource.driverClassName=org.h2.Driver 
spring.datasource.username=sa 
spring.datasource.password= 
spring.datasource.validation-query=SELECT 1; 
spring.datasource.initial-size=2 
spring.datasource.sql-script-encoding=UTF-8 

spring.jpa.database=h2 
spring.jpa.show-sql=true 
spring.jpa.hibernate.ddl-auto=create 
前述の通り、この例ではJDBCイベントストアを使ってシステムで生成されるイベントを格納します。これらのイベントをAxon Frameworkが指定する(Axon Frameworkイベントインフラストラクチャの)デフォルト表に格納します。以下のスタートアップクラスを使って、この例で必要なデータベース表を作成します。
@Component  
public class Datastore {  

    @Autowired  
    @Qualifier("transactionManager")  
    protected PlatformTransactionManager txManager;  

    @Autowired  
    private Repository repository;  

    @Autowired  
    private javax.sql.DataSource dataSource;  
    // create two cart entries in the repository used for command processing   
    @PostConstruct  
    private void init() {  

        TransactionTemplate transactionTmp = new TransactionTemplate(txManager);  
        transactionTmp.execute(new TransactionCallbackWithoutResult() {  
            @Override  
            protected void doInTransactionWithoutResult(TransactionStatus status) {  
                UnitOfWork uow = DefaultUnitOfWork.startAndGet();  
                repository.add(new Cart("cart1"));  
                repository.add(new Cart("cart2"));  
                uow.commit();  
            }  
        });  

        // create a database table for querying and add two cart entries  
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);  
        jdbcTemplate.execute("create table cartview (cartid VARCHAR , items NUMBER )");  
        jdbcTemplate.update("insert into cartview (cartid, items) values (?, ?)", new Object[]{"cart1", 0});  
        jdbcTemplate.update("insert into cartview (cartid, items) values (?, ?)", new Object[]{"cart2", 0});  
    }
    ...
}
このスタートアップクラスは、コマンド処理に使う2個のカートエントリをリポジトリに作成し、クエリ処理に使うデータベース表(cartview)を作成します。

ここまでで実施したことをまとめておきます。
  • Aggregate RootとしてCartを識別し、Cartへのアイテム追加および削除のコマンドおよびコマンドハンドラを定義した
  • CQRS およびイベントソーシングに必要な基盤コンポーネントを初期化するスタートアップクラスを定義した
  • データベース表の作成ならびにこのサンプルが必要とするデータ設定のためのスタートアップクラスも定義した
以下で定義するAggregateRootの"Cart"を見ていきましょう。

Aggregate Root

public class Cart extends AbstractAnnotatedAggregateRoot { 
    @AggregateIdentifier 
    private String cartid; 

    private int items; 

    public Cart() { 
    } 

    public Cart(String cartId) { 
        apply(new CartCreatedEvent(cartId)); 
    } 

    @EventSourcingHandler 
    public void applyCartCreation(CartCreatedEvent event) { 
        this.cartid = event.getCartId(); 
        this.items = 0; 
    } 

    public void removeCart(int removeitem) { 

        /**
        * State is not directly changed, we instead apply event that specifies what happened. Events applied are stored.
        */ 
        if(this.items > removeitem && removeitem > 0) 
            apply(new RemoveFromCartEvent(this.cartid, removeitem, this.items)); 
    } 

    @EventSourcingHandler 
    private void applyCartRemove(RemoveFromCartEvent event) { 
    /**
    * When events stored in the event store are applied on an Entity this method is 
    * called. Once all the events in the event store are applied, it will bring the Cart * to the most recent state.
    */ 

        this.items -= event.getItemsRemoved(); 
    } 

    public void addCart(int item) { 
        /**
        * State is not directly changed, we instead apply event that specifies what happened. Events applied are stored.
        */ 
        if(item > 0)     
            apply(new AddToCartEvent(this.cartid, item, this.items)); 
    } 

    @EventSourcingHandler 
    private void applyCartAdd(AddToCartEvent event) { 
    /**
    * When events stored in the event store are applied on an Entity this method is 
    * called. Once all the events in the event store are applied, it will bring the 
    * Cart to the most recent state.
    */ 

        this.items += event.getItemAdded(); 
    } 

    public int getItems() { 
        return items; 
    } 

    public void setIdentifier(String id) { 
        this.cartid = id; 
    } 

    @Override 
    public Object getIdentifier() { 
        return cartid; 
    } 
} 
以下はAggregate Rootの定義のキーポイントです。
  1. @AggregateIdentifier は、エンティティのIDを示すフィールドをマークする、JPAにおける @Id と似ている
  2. ドメイン駆動デザインでは、ドメインエンティティに関連するビジネスロジックを含めることを推奨しており、そのロジックが上記定義にあるビジネスメソッドである。詳細はReferencesのセクションを参照のこと。
  3. コマンドが呼び出されると、ドメインオブジェクトをリポジトリから取得し、それぞれのメソッド(例えばaddCart)がそのドメインオブジェクト(ここではCart)で呼び出される。
    1. ドメインオブジェクトは直接状態を変更せずに、適切なイベントを適用する
    2. イベントはイベントストアに保存され、そのイベントに対応するハンドラが呼び出され、ドメインオブジェクトの変更が発生する。
  4. CartというAggregate Rootは更新(つまりコマンドによる状態変更)にのみ利用されることに注意する必要がある。全てのクエリリクエストは、(次セクションで説明する)別のデータベースエンティティで処理される。
ではイベントならびにCartエンティティから呼び出されるドメインイベントを管理するイベントハンドラを見ていきましょう。

Events

前のセクションで説明したように、Cartエンティティで呼び出される2つのコマンド、Cartへの追加とCartからの削除があります。これらのコマンドがAggregate Rootで実行されると、以下に示すAddToCartEventとRemoveFromCartEventという2つのイベントを生成します。
public class AddToCartEvent {  

    private final String cartId;  
    private final int itemAdded;  
    private final int items;  
    private final long timeStamp;  

    public AddToCartEvent(String cartId, int itemAdded, int items) {  
        this.cartId = cartId;  
        this.itemAdded = itemAdded;  
        this.items = items;  
        ZoneId zoneId = ZoneId.systemDefault();  
        this.timeStamp = LocalDateTime.now().atZone(zoneId).toEpochSecond();  
    }  

    public String getCartId() {  
        return cartId;  
    }  

    public int getItemAdded() {  
        return itemAdded;  
    }  

    public int getItems() {  
        return items;  
    }  

    public long getTimeStamp() {  
        return timeStamp;  
    }  
}  

public class RemoveFromCartEvent {  
    private final String cartId;  
    private final int itemsRemoved;  
    private final int items;  
    private final long timeStamp;  

    public RemoveFromCartEvent(String cartId, int itemsRemoved, int items) {  
        this.cartId = cartId;  
        this.itemsRemoved = itemsRemoved;  
        this.items = items;  
        ZoneId zoneId = ZoneId.systemDefault();  
        this.timeStamp = LocalDateTime.now().atZone(zoneId).toEpochSecond();  
    }  

    public String getCartId() {  
        return cartId;  
    }  

    public int getItemsRemoved() {  
        return itemsRemoved;  
    }  

    public int getItems() {  
        return items;  
    }  

    public long getTimeStamp() {  
        return timeStamp;  
    }  
}

Event Handlers

上述のイベントは以下のイベントハンドラで処理されます。
@Component  
public class AddToCartEventHandler {  

    @Autowired  
    DataSource dataSource;  

    @EventHandler  
    public void handleAddToCartEvent(AddToCartEvent event, Message msg) {  
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);  

        // Get current state from event  
        String cartId = event.getCartId();  
        int items = event.getItems();  
        int itemToBeAdded = event.getItemAdded();  
        int newItems = items + itemToBeAdded;  


        //  Update cartview  
        String updateQuery = "UPDATE cartview SET items = ? WHERE cartid = ?";  
        jdbcTemplate.update(updateQuery, new Object[]{newItems, cartId});  
    }  
}

@Component  
public class RemoveFromCartEventHandler {  

    @Autowired  
    DataSource dataSource;  

    @EventHandler  
    public void handleRemoveFromCartEvent(RemoveFromCartEvent event) {  

        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);  

        // Get current state from event  
        String cartId = event.getCartId();  
        int items = event.getItems();  
        int itemsToBeRemoved = event.getItemsRemoved();  
        int newItems = items - itemsToBeRemoved;  

        // Update cartview  
        String update = "UPDATE cartview SET items = ? WHERE cartid = ?";  
        jdbcTemplate.update(update, new Object[]{newItems, cartId});  

    }  
}
お気づきの通り、イベントハンドラはcartviewというデータベース表を更新します。この表はCartエンティティをクエリする際に使われます。コマンドをあるドメインで実行している間、イベントソーシングでCQRSを実現することにより、クエリリクエストは別のドメインで処理されます。

Controllers

この例では、2個のSpringコントローラクラスを定義しています。一つはCartドメインの更新、一つはCartドメインのクエリのためのクラスです。以下のようにブラウザからRESTエンドポイントを呼び出すことができます。

  • http://<host>:<port>/add/cart/<noOfItems>
  • http://<host>:<port>/remove/cart/<noOfItems>
  • http://<host>:<port>/view

@RestController  
public class CommandController {  

    @Autowired  
    private CommandGateway commandGateway;  

    @RequestMapping("/remove/{cartId}/{item}")  
    @Transactional  
    public ResponseEntity doRemove(@PathVariable String cartId, @PathVariable int item) {  
        RemoveFromCartCommand removeCartCommand = new RemoveFromCartCommand(cartId, item);  
        commandGateway.send(removeCartCommand);  

        return new ResponseEntity<>("Remove event generated. Status: "+ HttpStatus.OK, HttpStatus.OK);  
    }  

    @RequestMapping("/add/{cartId}/{item}")  
    @Transactional  
    public ResponseEntity doAdd(@PathVariable String cartId, @PathVariable int item) {  

        AddToCartCommand addCartCommand = new AddToCartCommand(cartId, item);  
        commandGateway.send(addCartCommand);  

        return new ResponseEntity<>("Add event generated. Status: "+ HttpStatus.OK, HttpStatus.OK);  
    }  
}  

@RestController  
public class ViewController {  

    @Autowired  
    private DataSource dataSource;  

    @RequestMapping(value = "/view", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)  
    public ResponseEntity getItems() {  

        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);  
        List<Map<String, Integer>> queryResult = jdbcTemplate.query("SELECT * from cartview ORDER BY cartid", (rs, rowNum) -> {  
            return new HashMap<String, Integer>() {{  
            put(rs.getString("CARTID"), rs.getInt("ITEMS"));  
            }};  
        });  

        if (queryResult.size() > 0) {  
            return new ResponseEntity<>(queryResult, HttpStatus.OK);  
        }
        else {  
            return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);  
        }  
    }  
}

Deployment

Spring Bootを使ってアプリケーションを実行可能なJarファイルとしてパッケージングし、アプリケーションをOracle Application Container Cloud Serviceにデプロイします。以下のSpring Bootクラスでアプリケーションを初期化します。
@SpringBootApplication  
public class AxonApp {  

    // Get PORT and HOST from Environment or set default  
    public static final Optional<String> host;  
    public static final Optional<String> port;  
    public static final Properties myProps = new Properties();  

    static {  
        host = Optional.ofNullable(System.getenv("HOSTNAME"));  
        port = Optional.ofNullable(System.getenv("PORT"));  
    }  

    public static void main(String[] args) {  
        // Set properties  
        myProps.setProperty("server.address", host.orElse("localhost"));  
        myProps.setProperty("server.port", port.orElse("8128"));  

        SpringApplication app = new SpringApplication(AxonApp.class);  
        app.setDefaultProperties(myProps);  
        app.run(args);  
    }  
} 
以下のコンテンツを持つXMLファイルを作り、pom.xmlと同じディレクトリに配置します。このファイルは、Oracle Application Container Cloud Serviceへデプロイされるデプロイメント・アセンブリを指定するものです。
<assembly 
    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
    <id>dist</id>
    <formats>
        <format>zip</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <files>
        <file>
            <source>manifest.json</source>
            <outputDirectory></outputDirectory>
        </file>
    </files>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}</directory>
            <outputDirectory></outputDirectory>
            <includes>
                <include>${project.artifactId}-${project.version}.jar</include>
            </includes>
        </fileSet>
    </fileSets>
</assembly> 
To let Application Container cloud know the jar to run once the application is deployed, you need to create a “manifest.json” file specifying the jar name as shown below:
{
        "runtime": {
            "majorVersion": "8"
        },
        "command": "java -jar AxonApp-0.0.1-SNAPSHOT.jar",
        "release": {},
        "notes": "Axon Spring Boot App"
    }

下図はこのサンプルのプロジェクト構造を示しています。
Figure 2 Project Structure
アプリケーションJarファイルは上記のmanifestファイルとともにZipにまとめられ、Application Container Cloud Serviceにデプロイするためにアップロードされます。Spring BootアプリケーションのApplication Container Cloud Serviceへのデプロイについて詳細は以下のURLをご覧ください。
Deploying an Application to Oracle Application Container Cloud Service
http://www.oracle.com/webfolder/technetwork/tutorials/obe/cloud/apaas/deployment/deployment.html
アプリケーションが無事にデプロイ完了したら、以下のURLを使ってCartのサービスを呼び出すことができます。
  • http://<host>:<port>/view
  • http://<host>:<port>/add/cart/<noOfItems>
  • http://<host>:<port>/remove/cart/<noOfItems>
まず RESTエンドポイント "view" を呼び出すと、スタートアップクラスに追加した2つのカートと、追加されたアイテムの数が表示されます。他の2つのRESTサービスを呼び出してCartからアイテムを追加や削除、view RESTサービスを使用して更新された項目数を取得できます。REST呼び出しの結果は、その時点でのCart内のアイテム個数ならびにCartを示すシンプルなJSON構造として取得できます。

Conclusion

このエントリでは、CQRSとイベントソーシングパターンを使用したマイクロサービスアプリケーションの開発について紹介しました。この分野における他の先進的な概念や最新のアップデートについては、以下のリソースを参照してください。

References

[Cloud, JavaScript] Mocha.js for Test Automation of Node.js REST API on Oracle Developer Cloud Service

原文はこちら。
https://community.oracle.com/community/cloud_computing/platform-as-a-service-paas/oracle-developer-cloud-service/blog/2017/06/04/mochajs-for-test-automation-of-nodejs-rest-api-on-oracle-developer-cloud-service

このエントリでは、Oracle Developer Cloud Service上でMocha.jsという人気のあるテスティングフレームワークを使ったNode.jsベースのRESTサービスアプリケーションのテスト自動化を取り扱います。このNode.jsアプリケーションをOracle Application Container Cloud Serviceにデプロイします。

(注意)Developer Cloud Serviceを使ったNode.js RESTアプリケーションの開発、およびApplication Containerへのデプロイに関する詳細は、以下のエントリをご覧ください。
Oracle Developer Cloud to build and deploy Nodejs REST project on Application Container Cloud
https://community.oracle.com/community/cloud_computing/platform-as-a-service-paas/oracle-developer-cloud-service/blog/2016/09/07/oracle-developer-cloud-to-build-and-deploy-nodejs-rest-project-on-application-container-cloud

Tech Stack Used

Eclipse: Node.jsアプリケーション開発に使用するIDE
Express.js: REST Webサービス開発に使用するNode.jsモジュール
Grunt: デプロイ用にNode.jsコードアーカイブを生成するためのツール
Testing Framework: Mocha.js
Oracle Developer Cloud: Node.jsアプリケーションのCI、CDおよびその一環でのテスト自動化
Oracle Application Container Cloud: Node.jsベースのREST Webサービスデプロイ先のクラウドサービス

About Mocha framework:

Mochaは機能が豊富なJavaScriptテストフレームワークで、Node.jsならびにブラウザで動作します。Mochaテストフレームワークの主目的は非同期テストをシンプルにすることにあります。Mochaテストは所定の順番で動作します。これにより、柔軟で正確なレポートが作成しつつ、捕捉されない例外を正しいテストケースにマッピングできます。

Some of the salient features of Mocha framework:

  • ブラウザのサポート
  • シンプルな非同期サポート(promiseを含む)
  • テストカバレッジのレポート
  • テスト実行のためのJavaScript API
  • 捕捉されない例外を正しいテストケースにマッピング
  • 非同期テストタイムアウトのサポート
  • テスト再実行のサポート
  • テスト固有のタイムアウト
  • グローバル変数のリーク検知
  • 正規表現に一致するテストをオプションで実行
  • done()への複数呼び出しの検知
  • 利用したい任意のアサーションライブラリの利用

Setting up Mocha for Node.js application Testing:

以下のブログエントリに従ってNode.js RESTサービスアプリケーションを開発すると、Oracle Application Container Cloud Service上のNode.jsにデプロイされたRESTアプリケーションがあります。 このエントリではMochaTestという名前を付けました(もちろん自由に名前を付けることができます)。
Oracle Developer Cloud to build and deploy Nodejs REST project on Application Container Cloud
https://community.oracle.com/community/cloud_computing/platform-as-a-service-paas/oracle-developer-cloud-service/blog/2016/09/07/oracle-developer-cloud-to-build-and-deploy-nodejs-rest-project-on-application-container-cloud
サンプルを簡単にするため、Mocha.jsのテストスクリプトを 'MochaTest'フォルダに含めましたが、分離の観点から別のフォルダを選択することもできます。今回は、Mocha.jsベースのテスト自動化がDeveloper Cloud Serviceを使ってどのように動作するかを確認するためのサンプルアプリケーションゆえ、テストスクリプトを同じアプリケーションプロジェクトに保存することにしました。しかし、大規模なプロジェクトでは、包括的なテストスクリプトを使用して、テストスクリプトのために個別のGitリポジトリとプロジェクト(Developer Cloud Serviceの同じプロジェクトではあるものの、アプリケーション・プロジェクト・フォルダは異なります)を選択することができます。

下図はEclipseのMochaTestプロジェクトのスクリーンショットです。
2017-05-25 13_21_58-Java EE - Eclipse.png

Files for the Nodejs REST service:

  1. Gruntfile.js – ビルドタスクを定義
  2. manifest.json - main.jsという実行対象のmainファイルを定義(ACCSへのデプロイにおいて重要なファイル)
  3. main.js – 実際に実行されるサービスのコード
  4. package.json – 依存性を定義するファイル

File(s) for the test scripts:

test.js

このファイルにはNode.js REST Webサービスのテストスクリプトが含まれています。

Mocha.jsフレームワークの詳細を理解するには以下のリンクを参照してください。
Mocha - the fun, simple, flexible JavaScript test framework
https://mochajs.org/

A peek into the code snippets of the Nodejs application:

Gruntfile.js

アーカイブをOracle Application Container Cloud Serviceに正常にデプロイするため、manifest.jsonファイルを含めることが重要です。expressやbody-parserといったモジュールは、main.jsにおいてRESTサービスの実装に使用されるため、デプロイメントアーカイブ 'nodeappl.zip' に含める必要があります。
module.exports = function(grunt) {  
      
      require('load-grunt-tasks')(grunt);  

      grunt.initConfig({  
        compress: {  
          main: {  
            options: {  
              archive: 'nodeappl.zip',  
              pretty: true  
            },  
           expand: true,  
            cwd: './',  
            src: ['./main.js','./manifest.json','./package.json','./node_modules/express/**/*','./node_modules/body-parser/**/*'],  
            dest: './'  
          }  
        }  
      });

      grunt.registerTask('default', ['compress']);  
    };  

manifest.json

属性 majorVersion はNode.jsのランタイムバージョンを定義します。これは今回は0.12で、属性 command はアーカイブデプロイメントで実行対象のmain.jsというメインサービスファイルを定義する必要があります。
{  
    "runtime":{
        "majorVersion":"0.12"
    },
    "command": "node main.js",
    "release": {},
    "notes": ""
}
(訳注)
Oracle Application Container Cloud Serviceで利用可能なランタイムバージョンは以下のURLを参照してください。
Oracle® Cloud Using Oracle Application Container Cloud Service
Creating an Application
http://docs.oracle.com/en/cloud/paas/app-container-cloud/csjse/creating-application.html#GUID-695C9491-2927-4E7A-AA14-754CEFE07D35__THISTABLEDESCRIBESADDITIONALFIELDSI-620AD53E

package.json

テストスクリプトを実行するためのコマンド 'mocha test.js' を実行する npm testが利用できるよう、Mochaテストフレームワーク・モジュールを開発依存性として含める必要があります。
{
    "name": "MochaTest",  
    "version": "0.0.1",  
    "scripts": {  
        "start": "node main.js",  
        "test": "mocha test.js"  
    },  
    "dependencies": {  
        "body-parser": "^1.13.2",  
        "express": "^4.13.1",  
        "grunt": "^0.4.5",  
        "grunt-contrib-compress": "^1.3.0",  
        "grunt-hook": "^0.3.1",  
        "load-grunt-tasks": "^3.5.2",  
        "request": ""  
    },  
    "devDependencies": {  
        "mocha": "^3.3.0"  
    }  
}  

main.js

main.js ファイルにはget/post関数が含まれています。get関数は、静的なメッセージおよびfalseというエラー属性をレスポンスとして返します。post関数の 'add' は2個の数値をサービスの入力として受け取り、その和を返します。

Node.jsモジュールであるexpressおよびbodyParserはRESTサービス作成のために使っています。
ar express = require("express");  
    var bodyParser = require("body-parser");  
    var app = express();  
    app.use(bodyParser.urlencoded());  
    app.use(bodyParser.json());  
    var router = express.Router();  
      
      
    router.get('/',function(req,res){  
      res.json({"error" : false, "message" : "Hello Abhinav!"});  
    });  
      
      
    router.post('/add',function(req,res){  
      res.json({"error" : false, "message" : "success", "data" : req.body.num1 + req.body.num2});  
    });  
      
      
    app.use('/',router);  
      
      
    app.listen(process.env.PORT || 3000,function(){  
      console.log("I am listening at PORT 3000");  
    })  

A peek into the code snippet of the test script for the Nodejs application:

test.js

スクリプト中に3個のテストがあり、1個目はサービスが立ち上がっているかどうかを確かめるもの、2個目はadd関数が正しく動作しているかどうかを確かめるもの、最後の3個目はランダムなリソースをリクエストした場合に404を返すことを確かめるものです。

It uses the ‘request’ Node.jsモジュールを使ってテスト用アプリケーションURLを取得します。assert Node.jsモジュールも使っています。
var assert = require('assert');  
var request = require('request');  
// UNIT test begin  

describe("Unit Tests for the REST Service",function(){  

    // #1 should return home page  
    it("should find the service to be running",function(){  
        // calling home page api  
        request('https://mochatest-paasdemo015.apaas.us6.oraclecloud.com', function (error, response, body) {
            assert.equal(response.statusCode, 200, 'rest url is up');    
        });  
    });

    it("should add two number",function(){  
        request({  
            url: 'https://mochatest-paasdemo015.apaas.us6.oraclecloud.com/add',  
            method: 'POST',  
            json: {num1: 10, num2: 20}  
        }, function(error, response, body){  
            assert.equal(body.data, 30, 'correct total');  
        });  
    });  

    it("should return 404",function(){  
        request('https://mochatest-paasdemo015.apaas.us6.oraclecloud.com/random',
        function (error, response, body) {  
            assert.equal(response.statusCode, 404, '404 error for random url');    
        });
    })  
});

Build Configuration:

テストケースを実行するため、個別のビルドジョブをDeveloper Cloud Serviceで構成します。下図はビルドジョブの呼び出しの流れを示したものです。


下図は、Node.jsアプリケーションをビルド、デプロイするNodeApplBuildJobビルドジョブの構成画面のスクリーンショットです。ビルドジョブの名前は自由に設定できます。今回は‘NodeApplBuildJob’としています。これはNode.jsアプリケーションなので、JDKはデフォルトのままにしておきます。

Node.jsアプリケーションコードをアップロードしたリポジトリを選択します。前述の通り、Node.jsアプリケーション関連コードを含む同じプロジェクトに、このテストスクリプトが含まれています。


SCMポーリングをトリガーとして設定します。これにより、コードをGitリポジトリにアップロードするたびに、上のビルドトリガー図に示したように、'NodeApplBuildJob'が呼び出されます。
2017-05-26 17_18_10-Build_ BlogProject - Oracle Developer Cloud Service.png
今回は、実行シェル構築ステップを使います。ビルドステップの一環として、まずフォルダをプロジェクトフォルダ 'MochaTest'に変更します。以下のスクリーンショットのように、npm installを使用して、必要なNode.jsモジュールをすべてインストールします。続いて、grunt コマンドを使用して、デプロイ用のzipアーカイブを作成します。
2017-05-26 17_18_36-Build_ BlogProject - Oracle Developer Cloud Service.png
post buildのタブで、gruntビルドスクリプト実行の結果生成されたzipアーカイブを構成します。また、Application Container Cloud ServiceのNodeコンテナ上へのアーカイブのデプロイも構成します。
2017-05-26 17_19_07-Build_ BlogProject - Oracle Developer Cloud Service.png

下図では、Developer Cloud Serviceでビルド済みのアーカイブをApplication Container Cloud Serviceにデプロイするためにデプロイメントを構成しています。
2017-05-26 18_31_44-Deploy _ BlogProject.png
2017-05-26 18_31_11-Deploy _ BlogProject.png


以下は、Mochaテストスクリプトを実行するNodeTestBuildJobのビルドジョブ設定スクリーンショットです。
NodeApplBuildジョブの場合と同様に、ビルドジョブに名前を付けることができます。今回はNodeTestBuildJobという名前を付けました。これはNode.jsアプリケーションのため、JDKはデフォルトのままにしておくことができます。
2017-05-26 18_37_57-Build_ BlogProject - Oracle Developer Cloud Service.png
Node.jsアプリケーションは、test.jsがアプリケーションコードと同じリポジトリにあるため、同じリポジトリを使います。
2017-05-26 18_38_12-Build_ BlogProject - Oracle Developer Cloud Service.png

アプリケーションのビルドジョブであるNodeApplBuildJobの実行完了後、テストビルドジョブが呼び出されます。
2017-05-26 18_38_25-Build_ BlogProject - Oracle Developer Cloud Service.png

ここでも実行シェルビルドステップを使用します。test.jsがMochTestプロジェクトフォルダにあるので、フォルダを MochaTest に変更します。Node.jsモジュールのMochaを開発依存性としてインストールし、npm testを使用してpackage.jsonのscriptsの箇所に記載した 'mocha test.js'コマンドを実行を開始します。
2017-05-26 18_38_40-Build_ BlogProject - Oracle Developer Cloud Service.png

テストスクリプトをビルドジョブ実行時に実行します。以下がテスト結果で、ビルドジョブコンソールで見ることができます。
2017-05-26 18_39_02-Build_ BlogProject - Oracle Developer Cloud Service.png

[Docker, Cloud, Java] Dynamic load balancing for Docker based JavaEE microservices on Oracle Container Cloud

原文はこちら。
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2017/04/04/dynamic-load-balancing-for-docker-based-javaee-microservices-on-oracle-container-cloud

このエントリではDockerベースのJava EEマイクロサービスをHAProxyを使ってHA/負荷分散モードで実行する方法をご紹介します。全てOracle Container Cloud上で実行します。
HAProxy - The Reliable, High Performance TCP/HTTP Load Balancer
http://www.haproxy.org/
Oracle Container Cloud Service
https://cloud.oracle.com/ja_JP/container
簡単に概要をご紹介します。
  • WildFly Swarmを使うJava EEマイクロサービス
    シンプルなJAX-RSベースのRESTアプリケーション
  • HAProxy
    アプリケーションの複数インスタンスへの負荷分散に利用
  • Docker
    個々のコンポーネント、つまりマイクロサービスおよび負荷分散サービスをDockerイメージにパッケージング
  • Oracle Container Cloud
    Oracle Container Cloud上でサービスを構成し、スケーラブルかつ負荷分散しながらサービスを実行します。

Application

このアプリケーションは、株価を取得する非常にシンプルなJAX-RSを使うREST APIです。
@GET 
public String getQuote(@QueryParam("ticker") final String ticker) { 


    Response response = ClientBuilder.newClient(). 
            target("https://www.google.com/finance/info?q=NASDAQ:" + ticker). 
            request().get(); 


    if (response.getStatus() != 200) { 
        //throw new WebApplicationException(Response.Status.NOT_FOUND); 
        return String.format("Could not find price for ticker %s", ticker); 
    } 
    String tick = response.readEntity(String.class); 
    tick = tick.replace("// [", ""); 
    tick = tick.replace("]", ""); 


    return StockDataParser.parse(tick)+ " from "+ System.getenv("OCCS_CONTAINER_NAME"); 
}  
WildFly Swarmは単なるJava EEランタイムとして利用しています。シンプルなWARベースのJava EEプロジェクトを作成し、Swarm Mavenプラグインを使って、必要なパーツを自動的に検出して構成し、WARからfat JARを作成するという、"魔法"を織り込みます。
WildFly Swarm
http://wildfly-swarm.io/
<build> 
    <finalName>occ-haproxy</finalName> 
    <plugins> 
         
        <plugin> 
            <groupId>org.wildfly.swarm</groupId> 
            <artifactId>wildfly-swarm-plugin</artifactId> 
            <version>1.0.0.Final</version> 
            <executions> 
                <execution> 
                    <goals> 
                        <goal>package</goal> 
                    </goals> 
                </execution> 
            </executions> 
        </plugin> 
 
        <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-compiler-plugin</artifactId> 
            <version>3.1</version> 
            <configuration> 
                <source>1.7</source> 
                <target>1.7</target> 
                <compilerArguments> 
                    <endorseddirs>${endorsed.dir}</endorseddirs> 
                </compilerArguments> 
            </configuration> 
        </plugin> 
        <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-war-plugin</artifactId> 
            <version>2.3</version> 
            <configuration> 
                <failOnMissingWebXml>false</failOnMissingWebXml> 
            </configuration> 
        </plugin> 
        <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-dependency-plugin</artifactId> 
            <version>2.6</version> 
            <executions> 
                <execution> 
                    <phase>validate</phase> 
                    <goals> 
                        <goal>copy</goal> 
                    </goals> 
                    <configuration> 
                        <outputDirectory>${endorsed.dir}</outputDirectory> 
                        <silent>true</silent> 
                        <artifactItems> 
                            <artifactItem> 
                                <groupId>javax</groupId> 
                                <artifactId>javaee-endorsed-api</artifactId> 
                                <version>7.0</version> 
                                <type>jar</type> 
                            </artifactItem> 
                        </artifactItems> 
                    </configuration> 
                </execution> 
            </executions> 
        </plugin> 
    </plugins> 
</build> 
WildFly Swarmの代わりに、別のJava EEベースのfat JARスタイルのフレームワーク、例えばPayara MicroやKumuluzEE、Apache TomEE embeddedなどを利用することもできます。
Payara Micro
http://www.payara.fish/payara_micro
KumuluzEE
https://ee.kumuluz.com/
TomEE Embedded
http://tomee.apache.org/advanced/tomee-embedded/index.html
では詳細に入っていきましょう。

Dynamic load balancing

Oracle Container Cloudを使用した水平方向へのスケーラビリティは非常に単純で、アプリケーションの追加インスタンスを生成するだけです。これは、アプリケーション・コンシューマ(ユーザーまたは他のアプリケーション)が個々のインスタンスの詳細を取り扱う必要がないよう、ロードバランサを用意していて、ロードバランサの座標(ホスト /ポート)だけを知っていればよい場合に有効です。問題は、ロードバランサが新しく生成されたアプリケーションインスタンス/コンテナを認識しないことです。Oracle Container Cloudは統合スタックを作成することができます。これにより、バックエンド(この例ではREST API)と(HAProxy)ロードバランサ・コンポーネントの両方を単一ユニットとして構成し、簡単に管理および編成できるだけでなく、動的なHAProxyの化身(Avatar)のためにレシピを提供することができます。

HAProxy on steroids

Oracle Container Cloud Githubリポジトリのアーティファクトを使い、confdおよびrunit用にカスタマイズされたDockerイメージの上に、特殊化された(Docker)HAProxyイメージを作成します。
HAProxy Load Balancer Image
https://github.com/oracle/docker-images/tree/0c7f9a90e4420e313f3aeba865cd064c2d138463/ContainerCloud/images/haproxy
confd - Manage local application configuration files using templates and data from etcd or consul
https://github.com/kelseyhightower/confd
runit - a UNIX init scheme with service supervision
http://smarden.org/runit/
confdは構成管理ツールであり、今回は実行中のアプリケーションインスタンスを動的に発見するために使用します。Oracle Container Cloudサービス内のネイティブ・サービス・ディスカバリに問い合せ、新しいアプリケーションインスタンスを検出する、新しいアプリケーション・インスタンスを検出するミニ・サービス・ディスカバリ・モジュールと考えてください。
Oracle® Cloud Using Oracle Container Cloud Service
Managing Entries in the Service Discovery Database to Enable Container Communication
http://docs.oracle.com/en/cloud/iaas/container-cloud/contu/managing-entries-service-discovery-database-enable-container-communication.html

Configuring our application to run on Oracle Container Cloud

Build Docker images

まず必要なDockerイメージを作成します。デモのため、Docker Hubのパブリックリポジトリ(abhirockzz)を使いますが、ご自身のパブリックリポジトリやプライベートリポジトリをお使いいただくことができます。
Docker engineが起動していることを確認してください。

Build the application Docker image

Dockerfileは以下のような感じです。
FROM anapsix/alpine-java:latest 
RUN mkdir app  
WORKDIR "/app" 
COPY target/occ-haproxy-swarm.jar . 
EXPOSE 8080 
CMD ["java", "-jar", "occ-haproxy-swarm.jar"] 
以下のコマンドを実行してイメージを作成します。
docker build –t <registry>/occ-wfly-haproxy:<tag> . e.g. docker build –t abhirockzz/occ-wfly-haproxy:latest .

Build Docker images for runit, confd, haproxy

依存するイメージを順にビルドしていきましょう。まずは


以下のコマンドを実行します。
cd ContainerCloud/images 
cd runit 
make image 
cd ../confd 
make image 
cd ../nginx-lb 
make image 

Check your local Docker repository

ここまででローカルのDockerリポジトリに必要な全てのイメージがそろいました。


Push Docker images

ではDockerイメージをレジストリにPushしましょう(今回の場合、筆者のパブリックDockerレジストリにPushします)。これにより、Oracle Container Cloudからアプリケーションスタックのデプロイ時にPullすることができます。Pushは以下のコマンドを実行します。
各セットアップ毎にレジストリやリポジトリの名前を調整する必要があります。
docker login 
docker push abhirockzz/occ-wfly-haproxy 
docker push abhirockzz/haproxy 
docker logout

Create the Stack

Stack作成のために、docker-composeに非常に似ているYAMLフォーマットの構成ファイルを利用します。この例では、サービス名(rest-api)をロードバランサ(HAProxy)サービスで参照しています。


これは、Oracle Container Cloudサービス・レジストリ内のキーに関する情報をHAProxyサービスに提供します。このレジストリを使って(前述の)confdサービスが実際に新しいアプリケーション・インスタンスを自動検出します。8080は公開されたポート番号ですが、これ自体がサービスレジストリのキーの一部でもあるため、ハードコードされています。

StacksメニューからNew Stackを選択してプロセスを開始します。


Advanced EditorをクリックしてYAMLコンテンツを入力します。



これで個々のサービスが見えるはずです。Stack Nameを指定して[Save]をクリックします。



Initiate Deployment


Stacksメニューに戻って、新規作成されたStackを[Deploy]をクリックしてデプロイします。


ロードバランシングをテストするため、rest-api(バックエンド)サービスの3個のインスタンスをデプロイし、1個の負荷分散(HAProxy)サービスと紐付けます。


数秒後、全てのコンテナが実行中状態になっていることを確認できるはずです。今回は、この3個のサービスと1個のha-proxyロードバランサのインスタンスが実行中になっています。


Service Discoveryメニューをチェックして書くインスタンスが存在することを確認しましょう。前述の通り、これはconfdサービスがアプリケーションの新規インスタンスを自動検出することでイントロスペクトされています(自動的にこのレジストリに追加されます)。

Test

HAProxyを経由してアプリケーションにアクセスすることができます。HAProxyコンテナが実行しているホストのパブリックIPを知っておく必要があります。(下図の通り)バックエンドのアプリケーションにアクセスするためにポート番号8886が既にマップ済みです。


以下のcURLコマンドを実行して確認しましょう。
for i in `seq 1 9`; do curl -w "\n" -X GET "http://<haproxy-container-public-IP>:8886/api/stocks?ticker=ORCL"; done 
9回呼び出し、(3個のインスタンス間で)負荷分散が動作していることを確認します。以下がその結果です。ハイライトされたテキストはレスポンスを返したインスタンスを指し示しています。3個のインスタンス間で負荷分散が均等になされています。


Scale up... and check again

スタックをスケールアップすることも簡単に繰り返し実施できます。デプロイメントに移動し、Change Scalingをクリックします。


少々したら、追加されたアプリケーション・インスタンス(5個目のインスタンス)が確認できることでしょう。コマンドを再度実行し、負荷分散が想定通り動作していることを確認しましょう。


[Cloud] Java EE based microservice on Oracle Cloud with Payara Micro

原文はこちら。
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

(訳注)
  1. 元記事がやや古い(2017/03/09)ため、この当時はこのようなやり方を使った、とご理解ください。
  2. @khasunuma さんから、以下のコメントをいただきました。どうもありがとうございます!
    • 2017/06/14時点での最新のPayaraを使うと、Mavenプラグインは独自のものが用意されている
    • Mavenを使わず、コマンドラインからでもUber JARを作成できる
このエントリでは、Payara Microを使って、Java EEベースのマイクロサービスを構築する方法をご紹介します。
Payara Micro
http://www.payara.fish/payara_micro
Oracle Cloud (PaaS) Stackの以下のサービスを活用します。
  • 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
このエントリでは、fat JARの方法を使うことにします。

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
@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()); 
    } 
} 
StockDataEventQualifier.java
/**
 * Custom CDI qualifier to stamp CDI stock price CDI events
 * 
 */ 
@Qualifier 
@Retention(RUNTIME) 
@Target({METHOD, FIELD, PARAMETER, TYPE}) 
public @interface StockDataEventQualifier { 
} 
StockPriceScheduler.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");  
    }  
}  
RESTConfig.java
/**
 * JAX-RS configuration class
 * 
 */ 
@ApplicationPath("api") 
public class RESTConfig extends Application{ 
     
}
StockDataParser.java
/**
 * 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)
<dependency> 
    <groupId>javax</groupId> 
    <artifactId>javaee-api</artifactId> 
    <version>7.0</version> 
    <scope>provided</scope> 
</dependency> 
Mavenプラグインを使ってfat JARを生成します
<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
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クライアントをローカルマシンにインストールしておく必要があります。Gitを使ってもいいですし、お好みのものを使うことができます。
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をパッケージングするステップ


以下はコマンドの例です。
zip -j accs-payara-micro.zip target/mystocks.jar manifest.json  
manifest.json の例です。
{ 
    "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" 
} 
デプロイ可能なZipファイルをアーカイブするためのビルド後のアクションを有効化します。


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
Chomeブラウザにプラグインとしてインストールできるクライアントを使うことをお勧めします。例えば、Simple WebSocket Clientなどがよいでしょう。
Simple WebSocket Client
https://chrome.google.com/webstore/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo?hl=en

[Cloud] Accessing Oracle Process Cloud Service REST API using OAuth

原文はこちら。
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2017/02/19/accessing-oracle-process-cloud-service-rest-api-using-oauth

Oracle Process Cloud service (PCS) はREST APIを提供しており、これを使って他のアプリケーションをPCSと統合できます。REST APIの詳細はリファレンスを参照ください。
REST API for Oracle Process Cloud Service, Version 4.0
https://docs.oracle.com/en/cloud/paas/process-cloud/cprrb/index.html
Oracle Process Cloud ServiceのREST APIは基本認証だけでなく、OAuthトークンを利用することもできます。このエントリでは、OAuthトークンを使ってPCSのREST APIにアクセスし、プロセスの新規インスタンスを作成する方法をご紹介します。

このエントリで説明するシナリオは、JCS-SXにデプロイ済みのWebアプリケーションが、PCSにデプロイ済みのビジネスプロセス("Funds Transfer Process") を呼び出すというものです。この"Funds Transfer"プロセスは、シンプルなプロセスで、リクエストメッセージに含まれるある属性を検証し、必要に応じて人による承認へと進めるものです。このWebアプリケーションはOAuthサーバからOAuthトークンを取得し、トークンを認証のためにPCS REST APIに渡します。

下図はJCS-SX、PCS、OAuthサーバ間のやりとりの概要を図示したものです。

このユースケースでは、JCS-SXインスタンスとPCSインスタンスがともに同じアイデンティティドメインでプロビジョニングされている前提です。同一アイデンティティドメインでプロビジョニングされる場合、OAuthを使った通信に必要なリソースやクライアントはトークン取得のために利用するOAuthサーバと一緒に自動的に構成されます。マイサービス(My Services)のOAuth管理(OAuth Administration)タブを開き、以下のOAuthリソースおよびデフォルトで登録済みのクライアントを確認できます。詳細は以下のURLをご覧ください。
Oracle® Cloud Administering Oracle Cloud Identity Management Release 17.2
Managing OAuth Resources and Clients
https://docs.oracle.com/en/cloud/get-started/subscriptions-cloud/csimg/managing-oauth-resources-and-clients.html
Note: OAuth管理にアクセスするためには、アイデンティティドメイン管理者ロールが必要です。


Note: クライアント識別子(Id、上図の赤枠で囲んだ部分)および、JCS-SX OAuthクライアントの[機密の表示](Show Secret)をクリックすると確認可能なIdに対応する機密 (secret) は、Webアプリケーションがクライアントのアクセストークン取得ならびにPCS REST APIのアクセスするために利用します。

JCS-SX OAuthクライアントを使って、PCS REST APIをWebアプリケーションから呼び出すため、PCSリソースがクライアントからアクセス可能であることを確認しておきましょう。リソースへのアクセス可否は、[クライアントの登録]セクションのJCS-SX OAuthクライアントで、アクションパレット内の変更(Modify)メニューをクリックすることで管理できます(下図)。
pcs_oauth_blog_image_2.png

Note: このエントリでは、ビジネスプロセス(Funds Transfer Process)をPCSにデプロイされていることを前提とします。参考のためにAppendixセクションにこのビジネスプロセスのエクスポート・アーカイブがあります。

前提条件が整えば、WebアプリケーションがPCS REST APIを呼び出すために使うクライアントアクセストークンの取得に取りかかることができます。このサンプルでは、OAuthグラントタイプ(クライアント資格証明とパスワード)を使い、以下の手順でクライアントアクセストークンを取得します。
  1. クライアント資格証明を使ってクライアントアサーションを取得
  2. 取得したクライアントアサーションを使ってアクセストークンを取得
Note: Oracle Platform Service内でWebサービスを呼び出す場合、OWSMポリシーを使えばID伝播を実現でき、明示的にOAuthトークンを処理する必要はありません。今回はOAuthトークンを使ってPCSで認証するための説明を目的としているため、OWSMポリシーを使いません。

これらの手順を具体的なコード・スニペットを使って詳細に説明します。

呼び出し対象のビジネスプロセスの詳細情報と、OAuthトークンサーバへアクセスするために必要な詳細情報をHashMapに保存します。

Note: 説明の都合上、クライアント機密、ユーザー名、パスワードはjava.HashMapに格納していますが、資格証明の安全な管理を確実にするためには、Oracle Credential Store Framework (CSF) の利用を強く推奨します。文末のReferencesセクションから詳細情報を確認してください。
public static HashMap populateMap() {  
    HashMap map = new HashMap();  
    // PCS  
    map.put("PCS_URL", "https://<PCS_HOST>:443/bpm/api/3.0/processes");  
    map.put("PCS_PROCESS_DEF_ID", "default~MyApplication!1.0~FundsTransferProcess");  
    map.put("PCS_FTS_SVC_NAME", "FundsTransferProcess.service");  
    // OAuth  
    map.put("TOKEN_URL", "https://<ID_DOMAIN_NAME>.identity.<DATA_CENTER>.oraclecloud.com/oam/oauth2/tokens");  
    map.put("CLIENT_ID", "<CLIENT_ID>");  
    map.put("SECRET", "<SECRET>");  
    map.put("DOMAIN_NAME", "<ID_DOMAIN_NAME>");  
    map.put("USER_NAME","<PCS_USER_NAME>");  
    map.put("PASSWORD","<PCS_USER_PWD>");  
    return map;  
}  

public String getOAuthToken() throws Exception {  
    String token = "";  
    String authString = entryMap.get("CLIENT_ID")+":"+entryMap.get("SECRET");  
           
    Map clientAssertionMap = getClientAssertion(authString);  
    token = getAccessToken(authString,clientAssertionMap);  

    return token;  
}  
Note: 上記コードで指定した、PCS_PROCESS_DEF_IDおよび PCS_FTS_SVC_NAME をキーとする値は参考のためです。PCSにfunds transferビジネスプロセスをデプロイした後、以下のcURLコマンドを実行してビジネスプロセスの詳細を取得できます。取得した値を使って置き換えてください。
curl -u <PCS_USER_NAME>:<PCS_USER_PWD> -H "Content-Type:application/json" -H "Accept:application/json" -X GET https://<PCS_HOST>:443/bpm/api/4.0/process-definitions
getOAuthTokenメソッドは、OAuthサーバ(トークンエンドポイント)へアクセスし基本認証ヘッダとしてclient_id:client_secret を渡すことで、クライアントアサーションを取得するための実装です。これらの詳細情報は、前述のOAuth管理タブから取得できます。以下のコードスニペットはその実装例です。
private Map<String,String> getClientAssertion(String authString) throws Exception{  
      
    resource = client.resource( entryMap.get("TOKEN_URL")+"");  
      
    ClientResponse res = null;  
    String payload = "grant_type:client_credentials";  
      
    MultiPart multiPart = new MultiPart().bodyPart(new BodyPart(payload.toString(), MediaType.APPLICATION_JSON_TYPE));  
      
    MultivaluedMap formData = new MultivaluedMapImpl();  
    formData.add("grant_type", "client_credentials");  
              
    try {  
    res =   
        resource.header("X-USER-IDENTITY-DOMAIN-NAME",  entryMap.get("DOMAIN_NAME"))  
        .header("Authorization", "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes("UTF-8")))  
        .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")  
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)  
        .accept(MediaType.APPLICATION_JSON_TYPE)  
        .post(ClientResponse.class,formData);  
    } catch (Exception e) {  
        System.out.println("In catch: "+e);  
        e.printStackTrace();  
        throw e;  
    }  
      
    String output = res.getEntity(String.class);  
    JSONObject newJObject = null;  
    org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();  
    try {  
           
         newJObject = (JSONObject) parser.parse(output);  
          
        } catch (org.json.simple.parser.ParseException e) {  
            e.printStackTrace();  
    }  
            
    Map<String,String> assertionMap = new HashMap <String,String>();  
      
    assertionMap.put("assertion_token",newJObject.get("access_token")+"");  
    assertionMap.put("assertion_type",newJObject.get("oracle_client_assertion_type")+"");  
      
    if (res != null && res.getStatus() != 200) {  
        System.out.println("Server Problem (getClientAssertion): "+res.getStatusInfo());  
        throw new Exception (res.getStatusInfo().getReasonPhrase());  
    }  
    return assertionMap;  
}  
上記のコードでは、Jerseyクライアントを使ってトークンサーバにアクセスし、クライアントアサーションとクライアントアサーションタイプを取得するとともに、ペイロード内でgrant_type:client_credentialsも渡しています。以下のコードスニペットでは、password grant_typeを使い、ユーザー名とパスワードを先ほど取得したクライアントアサーションとともに渡すことで、クライアントアクセストークンをトークンサーバから取得しています。
private String getAccessToken(String authString,Map clientAssertionMap) throws Exception{  
    resource = client.resource(entryMap.get("TOKEN_URL")+"");  
      
    String clientAssertionType = (String) clientAssertionMap.get("assertion_type");  
    String clientAssertion = (String) clientAssertionMap.get("assertion_token");  
                                            
    ClientResponse res = null;  
      
    MultivaluedMap formData = new MultivaluedMapImpl();  
    formData.add("grant_type", "password");  
    formData.add("username", entryMap.get("USER_NAME"));  
    formData.add("password", entryMap.get("PASSWORD"));  
    formData.add("client_assertion_type", clientAssertionType);          
    formData.add("client_assertion", clientAssertion);          
      
    try {  
    res =   
        resource.header("X-USER-IDENTITY-DOMAIN-NAME",  entryMap.get("DOMAIN_NAME"))  
        .header("Authorization", "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes("UTF-8")))  
        .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")  
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)  
        .accept(MediaType.APPLICATION_JSON_TYPE)  
        .post(ClientResponse.class,formData);  
    } catch (Exception e) {  
        e.printStackTrace();  
        throw e;  
    }  
      
    String output = res.getEntity(String.class);  
      
    JSONObject newJObject = null;  
    org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();  
    try {  
         
       newJObject = (JSONObject) parser.parse(output);  
      
    } catch (org.json.simple.parser.ParseException e) {  
       e.printStackTrace();  
    }  
      
   String token = newJObject.get("access_token")+"";  
      
    if (res != null && res.getStatus() != 200) {  
        System.out.println("Server Problem (getAccessToken): "+res.getStatusInfo());  
        throw new Exception (res.getStatusInfo().getReasonPhrase());  
    }  
    return token;  
}  
これでクライアントアクセストークンを使ってPCSリソースにアクセスできるようになりました。以下のコードスニペットは、PCS REST APIを呼び出し、Funds Transferビジネスプロセスの新規プロセスインスタンスを生成しようとしています。ペイロードには、インスタンス作成対象のプロセス情報(定義ID、サービス名など)と、JSPページでユーザーが入力した入力パラメータが含まれています。先ほどの手順で取得したOAuthトークンをAuthorizationヘッダーに設定していることに注意してください。
public String invokeFundsTransferProcess(String token,FundsTransferRequest ftr) throws Exception {  
   
    StringBuffer payload = new StringBuffer();  
    payload.append("{");  
    payload.append("\"processDefId\":\""+entryMap.get("PCS_PROCESS_DEF_ID").toString()+"\",");  
    payload.append("\"serviceName\":\""+entryMap.get("PCS_FTS_SVC_NAME").toString()+"\",");  
    payload.append("\"operation\":\"start\",");  
    payload.append("\"params\": {");  
    payload.append("\"incidentId\":\""+ftr.getIncidentId()+"\",");  
    payload.append("\"sourceAcctNo\":\""+ftr.getSourceAcctNo()+"\",");  
    payload.append("\"destAcctNo\":\""+ftr.getDestAcctNo()+"\",");  
    payload.append("\"amount\":"+ftr.getAmount()+",");  
    String tsfrType;  
    if(ftr.getTransferType().equals("tparty"))  
        tsfrType = "intra";  
    else  
        tsfrType = "inter";  

    payload.append("\"transferType\":\""+tsfrType+"\"");  
    payload.append("}, \"action\":\"Submit\"");  
    payload.append("}");  
   
    MultiPart multiPart = new MultiPart().bodyPart(new BodyPart(payload.toString(), MediaType.APPLICATION_JSON_TYPE));  

    resource = client.resource(entryMap.get("PCS_URL").toString());  
    ClientResponse res = null;    
    try {  
    res =   
        resource.header("Authorization", "Bearer " + token)  
        .type("multipart/mixed")  
        .accept(MediaType.APPLICATION_JSON)  
        .post(ClientResponse.class, multiPart);  
    } catch (Exception e) {  
        e.printStackTrace();  
        throw e;  
    }  
      
    if (res != null && res.getStatus() != 200) {  
        System.out.println("Server Problem (PCSRestOAuthClient.invokeFundsTransferProcess): "+res.getStatusInfo() +" while invoking "+entryMap.get("PCS_URL").toString());  
        throw new Exception (res.getStatusInfo().getReasonPhrase());  
    }  
  
return res.getStatus()+"";  
}  
シンプルなJSPページを使ってユーザー入力を捕捉し、Funds Transferビジネスプロセスを起動します。

Funds Transferプロセスを開始出来た場合、下図のように、PCSのTrackingページで、生成済みかつ実行中のプロセスインスタンスを確認できます。



Known Issues:

ご利用のJDKによっては、PCS REST APIをJCS-SXから呼び出した場合に「javax.net.ssl.SSLHandshakeException: server certificate change is restricted during renegotiation」というエラーが出る可能性があります。その場合には、回避策として、JCS-SXの以下のシステムプロパティを設定して、サーバーを再起動してください。
  1. weblogic.security.SSL.minimumProtocolVersion をJCS-SXで TLSv1.2 に設定し、再起動する
  2. まだ問題が解決しない場合、jdk.tls.allowunsafeservercertchange を true に設定し、JCS-SXを再起動する

Appendix:

Funds Transferビジネスプロセス(PCSからエクスポートしたアプリケーション:MyApplication.zip

References: