Documentation

OSGiとモジュール性

モジュール性があることで、特にチームとしてソフトウェアを作成することが楽しくなります。 Liferayでのモジュール開発の利点は次のとおりです。

  • Liferayのランタイムフレームワークは、軽量、高速、安全です。

  • このフレームワークは OSGi 規格を使用します。 他のプロジェクトでOSGiを使用した経験がある場合は、既存の知識を生かすことができます。

  • モジュールは、サービスレジストリにサービスを公開し、サービスレジストリからサービスを利用します。 サービス契約はサービス・プロバイダーとコンシューマから疎結合されており、レジストリが契約を自動的に管理します。

  • モジュールの依存関係は、コンテナによって自動的に、かつ動的に管理されます(再起動は必要ありません)。

  • コンテナは モジュールのライフサイクル を動的に管理します。 Liferayの実行中にモジュールのインストール、開始、更新、停止、およびアンインストールができるため、デプロイメントを簡単に行うことができます。

  • パッケージが明示的にエクスポートされているモジュールのクラスのみが公開されます。 OSGiは、デフォルトで他のすべてのクラスを非表示にします。

  • モジュールとパッケージはセマンティックにバージョン管理され、他のパッケージの特定のバージョンへの依存関係を宣言します。 これにより、同じパッケージの異なるバージョンに依存する2つのアプリケーションが、それぞれ独自のバージョンのパッケージに依存することができます。

  • チームメンバーは、モジュールを並行して開発、テスト、および改善できます。

  • 既存の開発者ツールと環境を使用してモジュールを開発できます。

OSGiによるモジュール型ソフトウェア開発には多くのメリットがありますが、ここではその一部をご紹介します。 一度モジュール開発を始めると、他の方法での開発には戻れなくなるかもしれません。

Liferayでは、一般的に3種類のモジュールを使用します。

  1. API モジュールはインターフェイスを定義します。

  2. 実装 モジュールは、インターフェイスを実装する具象クラスを提供します。

  3. クライアント モジュールはAPIを消費します。

Gogo シェルでユーザーが名前を入力したときにあいさつ文を表示する簡単なコマンドを開発することで、それぞれを作成する方法を学習します。

ユーザーに挨拶をするGogoシェルコマンド。

モジュールプロジェクトがどのように見えるかを確認し、Liferayのモジュール開発機能が実際に動作しているのを見てみましょう。

Gogo シェルコマンドの例をデプロイする

サンプルの使用を開始します。

  1. Liferay Dockerコンテナを起動します。

    docker run -it -p 8080:8080 liferay/portal:7.4.3.22-ga22
    
  2. liferay-r9u2.zipをダウンロードして解凍します。

    curl hhttps://learn.liferay.com/dxp/latest/ja/liferay-internals/architecture/liferay-r9u2.zip -O
    
    unzip liferay-r9u2.zip
    
  3. サンプルモジュールをデプロイします。

    cd liferay-r9u2.zip
    
    ./gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)
    
  4. 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
    
  5. Gogo シェルを開きます。

  6. Gogo シェルコマンドフィールドに、r9u2:greetコマンドを入力して、挨拶を生成します。

    r9u2:greet "Captain Kirk"
    
  7. 出力を確認します。

    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

The build.gradle file specifies the module's dependencies.

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

The bundle name, symbolic name, and version are all set similarly to the API.

Finally, there's no Export-Package declaration. A client (which is the project's third module) just wants to use the API: it doesn't care how its implementation works as long as the API returns what it's supposed to return. The client, then, only needs to declare a dependency on the API; the service registry injects the appropriate implementation at run time.

Pretty cool, eh?

All that's left, then, is the class that provides the implementation:

@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

There's nothing new here: you declare the same things you declared for the provider.

The client module depends on the API module and the release.portal.api artifact. Here's the r9u2-osgi-commands module's build.gradle file:

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;

}

上記のメソッドは、Greetergreetメソッドを呼び出します。 com.acme.r9u2.Greeterは、実装モジュールが登録するOSGiサービスタイプです。 レジストリからGreeterサービスを取得するには、Greeterフィールド_greeter@Reference アノテーションを追加する必要があります。

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を拡張できます。

興味が湧いてきましたか。 開発を始める準備はできましたか。 さらに学習するためのリソースのいくつかを以下に紹介します。