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

モジュールプロジェクトがどのように見えるかを確認し、Liferayのモジュール開発機能が実際に動作しているのを見てみましょう。
Gogo Shellコマンドの例をデプロイする
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コンテナコンソールでデプロイメントを確認してください。
com.acme.r9u2.api_1.0.0 が起動しました com.acme.r9u2.impl_1.0.0 が起動しました com.acme.r9u2.osgi.commands_1.0.0 が起動しました -
Gogo Shell を開きます。
-
Gogo Shell コマンド フィールドに
r9u2:greetコマンドを入力して挨拶を生成します。r9u2:greet "キャプテン・カーク" -
出力結果を確認してください。
こんにちは、カーク船長!
この例のクライアント モジュールは、API および実装モジュールを利用して、 r9u2:greet Gogo Shell コマンドから返されるコンテンツを生成します。 次に、各モジュールを詳しく見ていきましょう。
API
まずAPIモジュールから始めます。 これは、プロバイダーが実施し、消費者が利用する契約を定義するものです。 その構造は以下のとおりです。
[プロジェクトルート]
└── r9u2-api
│ ├── bnd.bnd
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── com/acme/r9u2
│ └── Greeter.java
│
└── [Gradle ファイル]
r9u2-api モジュールフォルダには、 bnd.bnd メタデータファイル、 build.gradle スクリプト、および Java コードが含まれています。
とても簡単ですよね? Java ソースファイル以外には、Gradle ビルド スクリプト (任意のビルド システムを使用できます) と bnd.bnd という構成ファイル の 2 つのファイルしかありません。 bnd.bnd ファイルは、モジュールを記述および構成します。
バンドル名: Acme R9U2 API
バンドルシンボル名: com.acme.r9u2.api
バンドルバージョン: 1.0.0
エクスポートパッケージ: com.acme.r9u2
build.gradle ファイルは、モジュールの依存関係を指定します。
dependencies {
compileOnly group: "com.liferay.portal", name: "release.dxp.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 の実装モジュールは次のようになります。
[プロジェクトルート]
└── r9u2-impl
│ ├── bnd.bnd
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── com/acme/r9u2/internal
│ └── R9U2Greeter.java
│
└── [Gradle ファイル]
API モジュールと同じ構造を持ちます。ビルド スクリプト、 bnd.bnd 設定ファイル、および実装クラスです。 唯一の違いはファイルの内容だけです。 bnd.bnd ファイルは少し異なります。
バンドル名: Acme R9U2 実装
バンドルシンボル名: com.acme.r9u2.impl
バンドルバージョン: 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.dxp.api"
compileOnly project(":r9u2-api")
}
コンパイル時に r9u2-api モジュール プロジェクトへの依存関係が含まれます。これは、モジュールの Greeter クラスを必要とするためです。
実装モジュールとは、以上です。
クライアント
コンシューマーまたはクライアントは、APIモジュールが定義し、実装モジュールが実装するAPIを使用します。 Liferayには、さまざまな種類のコンシューマーモジュールがあります。 ポートレット は最も一般的なコンシューマーモジュールタイプですが、それ自体がトピックであるため、この例では Apache Felix Gogo シェル用のコマンドを作成することでシンプルにしています。 なお、消費者は当然ながら、さまざまなAPIを利用して機能を提供することができます。
コンシューマーモジュールは、他のモジュールタイプと同じ構造を持っています。
[プロジェクトルート]
└── r9u2-osgi-commands
│ ├── bnd.bnd
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── com/acme/r9u2/internal/osgi/commands
│ └── R9U2OSGiCommands.java
│
└── [Gradle ファイル]
ここでも、ビルド スクリプト、 bnd.bnd ファイル、および Java クラスがあります。 このモジュールの bnd.bnd ファイルは、プロバイダのファイルとほぼ同じです。
バンドル名: Acme R9U2 OSGi コマンド
バンドルシンボル名: com.acme.r9u2.osgi.commands
バンドルバージョン: 1.0.0
ここに目新しいことは何もありません。プロバイダーに対して宣言したのと同じことを宣言するだけです。
クライアント モジュールは、API モジュールと release.dxp.api アーティファクトに依存します。 r9u2-osgi-commands モジュールの build.gradle ファイルは以下のとおりです。
dependencies {
compileOnly group: "com.liferay.portal", name: "release.dxp.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 つのプロパティは、 greet というコマンド関数を持つ Gogo シェル コマンドを、 r9u2 というスコープで定義します。 デプロイされたR9U2OSGiCommandsコンポーネントは、String型を入力として受け取る Gogo Shell コマンド「r9u2:greet」を提供します。
この最も基本的な例からも、モジュールベースの開発が簡単で分かりやすいことがお分かりいただけるでしょう。 APIプロバイダーとコンシューマー間の契約は疎結合を促進し、ソフトウェアの管理、拡張、およびサポートを容易にします。
典型的なLiferayアプリケーション
Liferayのソースコードから典型的なアプリケーションを見てみると、通常、少なくとも4つのモジュールが含まれていることがわかります。
- APIモジュール
- サービス(プロバイダー)モジュール
- テストモジュール
- Web(コンシューマ)モジュール
これは、コメント、ブログ、その他のアプリケーションでユーザーが @username という命名規則を使用して他のユーザーをメンションできるメンションアプリケーションなど、一部の小規模なアプリケーションで見られるものと全く同じです。 ドキュメントやメディアライブラリのような大規模なアプリケーションには、より多くのモジュールが含まれています。 文書およびメディアライブラリの場合、異なる文書保存バックエンドごとに個別のモジュールが用意されています。 Wikiの場合、異なるWikiエンジンごとに個別のモジュールが存在する。
機能のバリエーションをモジュールとしてカプセル化することで、拡張性が向上する。 Liferayがまだサポートしていないドキュメントストレージバックエンドを使用している場合は、そのバックエンド用のモジュールを開発することで、LiferayのドキュメントストレージAPIをソリューションに実装し、Liferayのドキュメントおよびメディアライブラリを拡張できます。 LiferayのWikiで提供されているものよりも気に入ったWikiの方言がある場合は、それ用のモジュールを作成してLiferayのWikiを拡張することができます。
もうワクワクしてきましたか? 開発を始める準備はできていますか? さらに詳しく知りたい場合は、以下の資料をご参照ください。