OSGiとモジュール性
モジュール性があることで、特にチームとしてソフトウェアを作成することが楽しくなります。 Liferayでのモジュール開発の利点は次のとおりです。
-
Liferayのランタイムフレームワークは、軽量、高速、安全です。
-
フレームワークは OSGi 標準を使用します。 他のプロジェクトでOSGiを使用した経験がある場合は、既存の知識を生かすことができます。
-
モジュールは、サービスレジストリにサービスを公開し、サービスレジストリからサービスを利用します。 サービス契約はサービス・プロバイダーとコンシューマから疎結合されており、レジストリが契約を自動的に管理します。
-
モジュールの依存関係は、コンテナによって自動的に、かつ動的に管理されます(再起動は必要ありません)。
-
コンテナは モジュールのライフサイクル を動的に管理します。 Liferayの実行中にモジュールのインストール、開始、更新、停止、およびアンインストールができるため、デプロイメントを簡単に行うことができます。
-
パッケージが明示的に エクスポート されているモジュールのクラスのみが公開されます。OSGi はデフォルトで他のすべてのクラスを非表示にします。
-
モジュールとパッケージは 意味的にバージョン管理され 、他のパッケージの特定のバージョンへの依存関係を宣言します。 これにより、同じパッケージの異なるバージョンに依存する2つのアプリケーションが、それぞれ独自のバージョンのパッケージに依存することができます。
-
チームメンバーは、モジュールを並行して開発、テスト、および改善できます。
-
既存の開発者ツールと環境を使用してモジュールを開発できます。
OSGiによるモジュール型ソフトウェア開発には多くのメリットがありますが、ここではその一部をご紹介します。 一度モジュール開発を始めると、他の方法での開発には戻れなくなるかもしれません。
Liferayでは、一般的に3種類のモジュールを使用します。
-
API モジュールはインターフェースを定義します。
-
実装モジュールは、インターフェイスを実装する具象クラスを提供します。
-
クライアント モジュールは API を使用します。
Gogo シェルでユーザーが名前を入力したときにあいさつ文を表示する簡単なコマンドを開発することで、それぞれを作成する方法を学習します。

モジュールプロジェクトがどのように見えるかを確認し、Liferayのモジュール開発機能が実際に動作しているのを見てみましょう。
Gogo シェルコマンドの例をデプロイする
新しいLiferay インスタンスを起動し、以下を実行します。
docker run -it -m 8g -p 8080:8080 liferay/portal:7.4.3.132-ga132
http://localhost:8080でLiferayにサインインします。 メールアドレス test@liferay.com とパスワード testを使用してください。 プロンプトが表示されたら、パスワードを learnに変更します。
次に、次の手順に従って例をデプロイします。
-
liferay-r9u2.zipをダウンロードして解凍します。curl https://resources.learn.liferay.com/examples/liferay-r9u2.zip -Ounzip liferay-r9u2.zip -
サンプルモジュールをデプロイします。
cd liferay-r9u2.zip./gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq) -
Dockerコンテナコンソールでデプロイを確認します。
STARTED com.acme.r9u2.api_1.0.0 STARTED com.acme.r9u2.impl_1.0.0 STARTED com.acme.r9u2.osgi.commands_1.0.0 -
Gogo Shellを開きます。
-
Gogo シェルコマンドフィールドに、
r9u2:greetコマンドを入力して、あいさつ文を生成します。r9u2:greet "Captain Kirk" -
出力を確認します。
Hello Captain Kirk!
この例のクライアントモジュールは、APIおよび実装モジュールを利用して、r9u2:greet Gogo シェルコマンドから返されるコンテンツを生成します。 次に、各モジュールを調べます。
API
APIモジュールが最初です。 これは、プロバイダーが実装し、コンシューマが使用する契約を定義します。 その構造は次のとおりです。
[project root]
└── r9u2-api
│ ├── bnd.bnd
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── com/acme/r9u2
│ └── Greeter.java
│
└── [Gradle files]
r9u2-apiモジュールフォルダには、bnd.bndメタデータファイル、 build.gradle スクリプト、およびJavaコードが含まれています。
非常にシンプルです。 Javaソースファイル以外には、Gradleビルドスクリプト(任意のビルドシステムを使用できます)とbnd.bndという構成ファイルの2つのファイルしかありません。 bnd.bndファイルは、モジュールの説明と設定を行います。
Bundle-Name: Acme R9U2 API
Bundle-SymbolicName: com.acme.r9u2.api
Bundle-Version: 1.0.0
Export-Package: com.acme.r9u2
build.gradle ファイルでは、モジュールの依存関係を指定します。
dependencies {
compileOnly group: "com.liferay.portal", name: "release.portal.api"
}
これは、LiferayリリースのAPI JARという1つのアーティファクトに依存しています。 これは、Liferay製品のリリースに関連するLiferay、Bnd、OSGiのアーティファクトを詰め込んだ大きなJARです。
モジュールの名前は Acme R9U2 API です。 そのシンボリック名—一意性を保証する名前—は com.acme.r9u2.apiです。 次にそのセマンティックバージョンが宣言され、そのパッケージがエクスポートされます。つまり、他のモジュールで使用できるようになります。 このモジュールのパッケージは、他のモジュールが実装できるAPIに過ぎません。
最後に、Javaクラスがあります。この場合はインターフェイスです。
@ProviderType
public interface Greeter {
public void greet(String name);
}
インターフェイスの@ProviderTypeアノテーションは、インターフェイスを実装しているものがプロバイダーであることをサービスレジストリに通知します。 インターフェイスの1つのメソッドはStringを要求し、何も返しません。
これで完了です。 ご覧のとおり、モジュールの作成は他のJavaプロジェクトの作成と大差ありません。
実施
インターフェイスはAPIを定義しているだけです。何かをするためには、それを実装する必要があります。 これが、実装(またはプロバイダー)モジュールの目的です。 Greeter APIの実装モジュールは次のようになります。
[project root]
└── r9u2-impl
│ ├── bnd.bnd
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── com/acme/r9u2/internal
│ └── R9U2Greeter.java
│
└── [Gradle files]
APIモジュールと同じ構造、つまりビルドスクリプト、bnd.bnd構成ファイル、および実装クラスがあります。 唯一の違いはファイルのコンテンツです。 bnd.bndファイルが少し異なります。
Bundle-Name: Acme R9U2 Implementation
Bundle-SymbolicName: com.acme.r9u2.impl
Bundle-Version: 1.0.0
バンドル名、シンボリック名、バージョンはすべて API と同様に設定されます。
最後に、 Export-Package 宣言はありません。 クライアント (プロジェクトの 3 番目のモジュール) は API を使用することだけを望んでいます。API が返すはずのものを返している限り、実装がどのように機能するかは気にしません。 クライアントは API への依存関係を宣言するだけで済み、サービス レジストリが実行時に適切な実装を挿入します。
残っているのは、実装を提供するクラスだけです。
@Component(service = Greeter.class)
public class R9U2Greeter implements Greeter {
@Override
public void greet(String name) {
System.out.println("Hello " + name + "!");
}
}
greetメソッドの例では、指定された名前を使用して熱烈な挨拶文を出力します。
実装モジュールのbuild.gradleファイルは以下のとおりです。
dependencies {
compileOnly group: "com.liferay.portal", name: "release.portal.api"
compileOnly project(":r9u2-api")
}
このファイルには、モジュールのGreeterクラスが必要なため、r9u2-apiモジュールプロジェクトへのコンパイル時の依存関係が含まれています。
実装モジュールについてはこれですべてです。
クライアント
コンシューマまたはクライアントは、APIモジュールが定義し実装モジュールが実装するAPIを使用します。 Liferayにはさまざまな種類のコンシューマモジュールがあります。 ポートレット は最も一般的なコンシューマー モジュール タイプですが、それ自体がトピックであるため、この例では Apache Felix Gogo シェルのコマンドを作成することで単純なままになっています。 もちろん、コンシューマはさまざまなAPIを利用して機能を提供することができます。
コンシューマモジュールは、他のモジュールタイプと同じ構造を持っています。
[project root]
└── r9u2-osgi-commands
│ ├── bnd.bnd
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── com/acme/r9u2/internal/osgi/commands
│ └── R9U2OSGiCommands.java
│
└── [Gradle files]
ここでも、ビルドスクリプト、bnd.bndファイル、およびJavaクラスがあります。 このモジュールのbnd.bndファイルは、プロバイダーのものとほとんど同じです。
Bundle-Name: Acme R9U2 OSGi Commands
Bundle-SymbolicName: com.acme.r9u2.osgi.commands
Bundle-Version: 1.0.0
ここでは何も新しいことはありません。プロバイダーに対して宣言したものと同じものを宣言します。
クライアント モジュールは、API モジュールと release.portal.api アーティファクトに依存します。 以下は r9u2-osgi-commands モジュールの build.gradle ファイルです。
dependencies {
compileOnly group: "com.liferay.portal", name: "release.portal.api"
compileOnly project(":r9u2-api")
}
Javaクラスでは、もう少し続きがあります。
@Component(
property = {"osgi.command.function=greet", "osgi.command.scope=r9u2"},
service = R9U2OSGiCommands.class
)
public class R9U2OSGiCommands {
public void greet(String name) {
_greeter.greet(name);
}
@Reference
private Greeter _greeter;
}
上記のメソッドは、Greeterのgreetメソッドを呼び出します。 com.acme.r9u2.Greeterは、実装モジュールが登録するOSGiサービスタイプです。 レジストリから Greeter サービスを取得するには、 @Reference アノテーションを Greeter フィールド _greeterに追加する必要があります。
R9U2OSGiCommandsクラスは、独自のタイプのOSGiサービスを提供します。 2つのプロパティは、r9u2というスコープでgreet というコマンド関数を使用してGogoシェルコマンドを定義します。 デプロイされたR9U2OSGiCommandsコンポーネントは、Stringを入力として受け取るGogo シェルコマンドr9u2:greetを提供します。
この最も基本的な例を見れば、モジュールベースの開発が簡単で分かりやすいことがわかるでしょう。 API-Provider-Consumer契約によって疎結合が促進され、ソフトウェアの管理、拡張、およびサポートが容易になります。
典型的なLiferayアプリケーション
Liferayのソースから典型的なアプリケーションを見てみると、通常は少なくとも4つのモジュールがあります。
- APIモジュール
- サービス(プロバイダー)モジュール
- テストモジュール
- Web(コンシューマ)モジュール
これは、ユーザーがコメント、ブログ、または他のアプリケーションで@usernameの命名法を使用して他のユーザーに言及できるようにする、Mentionsアプリケーションなど、一部の小さなアプリケーションで見られるものとまったく同じです。 ドキュメントとメディアライブラリなどの大規模なアプリケーションには、さらに多くのモジュールがあります。 ドキュメントおよびメディア ライブラリの場合、異なるドキュメント ストレージ バックエンドごとに個別のモジュールがあります。 Wikiの場合、Wikiエンジンごとに個別のモジュールがあります。
モジュールとして機能のバリエーションをカプセル化すると、拡張性が向上します。 Liferay がまだサポートしていないドキュメント ストレージ バックエンドがある場合は、そのためのモジュールを開発してソリューションに Liferay のドキュメント ストレージ API を実装し、Liferay のドキュメントおよびメディア ライブラリを拡張できます。 Liferayのwikiが提供する言語よりも気に入ったWiki言語がある場合は、そのモジュールを作成してLiferayのwikiを拡張できます。
開発を始める準備はできましたか。 さらに学習するためのリソースのいくつかを以下に紹介します。