技術者向けテクニカルtips
 / 

Liferay 7における、サービス実装パターンの基礎

Liferay 7における、サービス実装パターンの基礎をご紹介。

はじめに


※本記事はLiferay Community Blogに投稿されている”A Simplified Pattern for Liferay 7 Services”を翻訳したものです。

本記事で紹介するのは、簡易化したOSGiサービスAPIと実装パターンです。プログラマがインタフェースクラスと実装クラスを同期させるだけで済む、従来のJavaインタフェースの実装パターンに従います。 Liferay 7のサービスビルダーは使用しません。

本リンクのアーカイブはMyBatisに基づいて完全に実装されたORMサービスです。 Bladeで作成したワークスペースのmodulesフォルダへ解凍してください。このサンプルを完全に運用可能なものにするための、バックエンドデータベースを作成するスクリプトが、すぐに追加されます。

Factory使用パターン


'api.Factory'クラスは、コンシューマーが必要とするサービスを受けるためのアクセスポイントです。コンシューマクラスは、次のようにFactoryを使用します。

実装パターン

このORMの例では、2つのトップレベルパッケージ:'api'と 'impl'があります。 ’api’ とその子パッケージは、このサービスAPIのコンシューマーによってエクスポートされ、使用されます。 'impl'とその子パッケージは実装専用であり、このOSGiモジュールのプライベートパッケージのままでなければなりません。

import com.acme.orm.api.bean.Order
import static com.acme.orm.api.Facotry.getOrderLocalService;

class OnlineStore {
    public Order checkOrder(String orderId) {
        Order order = getOrderLocalService().getOrderDetailsById(orderId);
        // Do something else
        return order;
    }
}

このモジュールのOSGiの実行時ライフサイクル管理を維持するため(起動、停止、インストール、アンインストール)、Factoryから取得したサービスオブジェクトの参照を、保持しないことが重要です。

// DO NOT DO THIS
OrderLocalService myService = getOrderLocalService();
// DO NOT DO THIS
OrderLocalService myService = Factory.getOrderLocalService();

Liferayサービスビルダーは、ユーザーがXyzServiceUtilクラスの静的メソッドを利用して、サービスの参照を保持しつづけないようにしました。更に、XyzServiceBaseImpl、XyzServiceWrapperに加え、1つのサービスに対する2つのプロジェクト(2つのjarファイル)といった無関係で混乱を招くその他の成果物も作成されます。

初心者向けの対応で複雑さを増す代わりに、なぜ経験もあり、自分がやっていることを理解しているプログラマーたちに、OSGiサービスオブジェクトを参照し続けないように、といわないのでしょうか?そうすれば、わかりやすい実装にするために、従うべきルールは以下の2つです。

  • APIインターフェイスクラスを実装クラスと同期させる。
  • Factoryから取得したサービスオブジェクトの参照を保持しない。

ServiceTrackerを理解する

OSGiモジュール(またはバンドル)が停止、起動、アンインストール、または再インストールされて実行時に置き換えられる場合、そのモジュールによって提供されるサービスも同様に置換されることが望ましいでしょう。
ServiceTrackerは、モジュールライフサイクルの変更を追跡するOSGiクラスです。モジュールのライフサイクルの変更は、コンシューマコードがServiceTrackerから常にサービスにアクセスする限り、サービスコンシューマコードに対して透過的です。

OSGiは、Javaプログラムと同じ、実行時動作を示すJVMインスタンスで実行されるコンポーネント・フレームワークです。コンシューマ・コードがサービス・オブジェクトへの参照を保存すると、そのサービス・オブジェクトは、OSGiがそのモジュールを新しいインスタンスに置き換えた場合でも存続します。そのサービスオブジェクトは、コンシューマコードだけが知っている孤立した古いインスタンスになります。これが、サービスへの参照を保持すべきでない理由です。

この実装パターンでは、Factoryクラスは対応するServiceTrackerからサービスオブジェクトを取得します。 ServiceTrackerのgetService()メソッドは、モジュールのライフサイクルの変更をコンシューマコードから保護します。

@ProviderType
public class Factory
{
    private static ServiceTracker
        _OrderLocalService = ServiceTrackerFactory.open(OrderLocalService.class);
    public static OrderLocalService getOrderLocalService() {
        return _OrderLocalService.getService();
    }
}

ローカルvsリモートサービス

Liferay 7.0のローカルサービスとリモートサービスの違いは次のとおりです。
1. APIの基本インターフェース
2.リモートインタフェースの特定のアノテーション

ORMの例では、OrderLocalServiceはローカル・サービス・インタフェースです。

ローカルサービスAPI宣言

@ProviderType
@Transactional(isolation = Isolation.PORTAL, rollbackFor =  {
    PortalException.class, SystemException.class})
public interface OrderLocalService extends BaseLocalService {

}

OrderServiceはRESTful Webサービスとして公開されるリモートサービスインターフェイスです:

リモートサービスAPI宣言

@AccessControlled
@JSONWebService
@OSGiBeanProperties(property =  {
    "json.web.service.context.name=acme",
    "json.web.service.context.path=Order" }, service = OrderService.class)
@ProviderType
@Transactional(isolation = Isolation.PORTAL, rollbackFor =  {
    PortalException.class, SystemException.class})
public interface OrderService extends BaseService {

    public Order getOrderDetailsById(String orderId);
}

このRESTful Webサービスは、次のカタログの、Context Name "acme"の下で見つけることができます(ドロップダウンボックスをクリックして、 "acme"または他のコンテキスト名を見つけてください)
http://localhost:8080/api/jsonws?contextName=acme

ローカルとリモートの両方のサービス実装クラスは、対応するAPIインターフェイスを実装するだけです。 ORMの例では、OrderLocalServiceImplは、実際のデータベースへのマッピング作業を行うローカル実装です。以下に示すように、リモート実装はローカルFactoryサービスを呼び出します。

リモートサービスの実装


@ProviderType
public class OrderServiceImpl implements OrderService {

    public Order getOrderDetailsById(String orderId) {
        return Factory.getOrderLocalService().getOrderDetailsById(orderId);
    }
}

開発の詳細

本パターンでサービスを作成し、実装するためにキーとなるファイルがあります。

Eclipseの.projectと.classpath

サンプルアーカイブ内の2つのファイルは、EclipseがGradleプロジェクトとして認識するよう、サービスプロジェクトを開始するために使用する必要があります。プロジェクトをEclipseにインポートする前に、.projectファイルのプロジェクト名をタグで変更することができます。

<name> orm.api </ name>

また、Javaファイルとリソースファイルを保持するために、次の2つのフォルダ構造を作成する必要があります。

src / main / java
src / main / resources

Eclipseにインポートしたら、プロジェクトを右クリックして、 "Gradle" >"Gradle Projectをリフレッシュする" を選択してください。 Liferay Bladeが作成した、親Gradleプロジェクトでも同じことができます。

build.gradle

2つの 'org.osgi:org.osgi*'依存関係はOSGi機能に必要です。

dependencies {
    compile group: "com.liferay", name: "com.liferay.osgi.util", version: "3.0.3"
    compile group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
    compile 'org.osgi:org.osgi.core:5.0.0'
    compile 'org.osgi:org.osgi.annotation:6.0.0'
    compile group: 'javax.servlet', name: 'servlet-api', version: '2.5'
    compile group: "org.mybatis", name: "mybatis", version: "3.4.1"
    compile files('./resources/lib/sqljdbc4.jar')
    compileOnly group: "com.liferay", name: "com.liferay.journal.api", version: "1.0.0"
}

bnd.bnd
‘api’下すべてのパッケージは、”Export-Package:”設定で、エクスポートする必要があります。 "Liferay-Spring-Context:"設定は、Liferayに、以下のmodule-spring.xmlファイルにSpring bean定義をロードするよう指示します。 "Lifer-Require-SchemaVersion:", "Liferay-Service:", "Require-Capability:"の設定も必要です。


Bundle-Version: 1.0.0
Bundle-ClassPath: .,lib/sqljdbc4.jar
Export-Package: \
    com.acme.orm.api,\
    com.acme.orm.api.bean,\
    com.acme.orm.api.exception
Import-Package: \
    !com.microsoft.sqlserver.*,\
    !microsoft.sql.*,\
    !com.sun.jdi.*,\
    !net.sf.cglib.proxy.*,\
    !org.apache.logging.*,\
    *
Include-Resource: @mybatis-3.4.1.jar
Liferay-Require-SchemaVersion: 1.0.0
Liferay-Service: true
Liferay-Spring-Context: META-INF/spring
Require-Capability: liferay.extender;filter:="(&(liferay.extender=spring.extender)(version>=2.0)(!(version>=3.0)))"

src/main/resources/META-INF/spring/module-spring.xml

各Bean定義について: "class ="値は実装クラス名であり、 "id ="値はインタフェースクラス名です。


<?xml version="1.0"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" default-destroy-method="destroy" default-init-method="afterPropertiesSet" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="com.acme.orm.impl.CommunicationMediumLocalServiceImpl" id="com.acme.orm.api.CommunicationMediumLocalService" />
    <bean class="com.acme.orm.impl.MessageCenterLocalServiceImpl" id="com.acme.orm.api.MessageCenterLocalService" />
    <bean class="com.acme.orm.impl.NSMUserLocalServiceImpl" id="com.acme.orm.api.NSMUserLocalService" />
    <bean class="com.acme.orm.impl.OrderLocalServiceImpl" id="com.acme.orm.api.OrderLocalService" />
    <bean class="com.acme.orm.impl.OrderServiceImpl" id="com.acme.orm.api.OrderService" />
    <bean class="com.acme.orm.impl.RoutingAreaLocalServiceImpl" id="com.acme.orm.api.RoutingAreaLocalService" />
    <bean class="com.acme.orm.impl.WebContentArticleLocalServiceImpl" id="com.acme.orm.api.WebContentArticleLocalService" />
</beans>

   

 

本記事は以上です。
いかがでしたでしょうか?

記事に関するご意見、ご感想などございましたら、お気軽に[email protected]までご連絡ください。


■これまで発信してきた日本語による技術コンテンツを、Qiitaでお読みいただけます。よろしければそちらの記事も合わせてご役立てください。
https://qiita.com/yasuflatland-lf

■Doorkeeperのライフレイコミュニティメンバー大歓迎!
コーポレートブログの技術コンテンツ更新情報など定期的におとどけしています。
https://liferay.doorkeeper.jp/