Liferay 7 / DXP におけるサービスビルダーポートレットの作成方法

## はじめに
LiferayではService Builderという仕組みにより、モデル定義を作成して、Service Builderを実行することによってテーブルを直接定義せずに、データベースといった永続層アクセスクラス、ユーザーが利用するためのサービスレイヤークラスを自動生成してくれる仕組みがあります。

Liferay 6.2ではService Builderによって、サービスと、そのデータを表示するためのポートレットが同時に作成されましたが、Liferay 7 / DXPからはOSGi基盤への移動と、それに伴う徹底的なモジュール化により、サービスはサービス、ポートレットはポートレットで別々に作成するようになりました。

また開発環境もLiferay 6.2ではSDKを別途準備する必要がありましたが、Bladeツールの導入により、コマンドにより開発環境を作成することができるようになりました。

そうした相違点を踏まえて、以下ではLiferay 7 / DXPにおけるサービスビルダーポートレットの作成方法と、ベストプラクティスをまとめてみました。

## ビルドツール(Gradle/blade)のインストール

### Gradleのインストール
ビルドツールですが、Liferay 6.2の時はAnt / Mavenを利用していましたが、Liferay 7 / DXPからはGradle / Maven が利用できるようになりました。LiferayとしてはGradleの使用が標準となっているので、本記事でもそれに従うことにします。

インストール方法などに関しては[Gradle公式ドキュメント](http://gradle.monochromeroad.com/docs/userguide/installation.html)などを参考にインストールしてください。常に最新版を利用するので問題ありません。

### bladeのインストール
Liferay 7 / DXPからは、開発支援ツールとしてBladeというツールが導入されました。これにより開発環境の構築、デプロイまでを１つのコマンドでカバーできるようになりました。

公式ドキュメント[INSTALLING BLADE CLI](https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/installing-blade-cli)を参照してインストールしてください。

## Liferayワークスペースの作成
Liferay 6.2ではSDKを利用して開発環境を作成する必要がありましたが、Liferay 7 / DXPでは以下のコマンドで開発環境(Liferayワークスペース)を作成できます。

```bash
blade init workspace(任意のLiferayワークスペース名)
```

もしくはすでに存在するディレクトリ内で初期化する場合は

```bash
blade init --force
```

でLiferayワークスペースとして初期化されます。

## Serviceの作成

### サービスのスケルトンを作成
Liferayワークスペースが作成されると、直下にmodulesというフォルダが作成されています。modulesの下に移動し、例えば

```bash
blade create -t service-builder -p com.liferay.todo -c Todo Todo
```

のように実行すると、

```
Todo-api/  Todo-service/  build.gradle
```

といったサービスのフォルダと、gradleのビルドファイルが生成されます。

### モデルの作成
次にモデルを作成します。Todo-serviceフォルダ直下に移動すると、service.xmlというファイルがあるので、それを編集します。各種設定の詳細は[公式ドキュメント](https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/service-builder)を参照していただければと思いますが、主な設定は以下になります。

#### ネームスペース
以下の該当サービスのネームスペースを任意に設定します。

```xml:service.xml
<namespace>FOO</namespace>
```

#### エンティティ設定
以下のアトリビュートでエンティティの設定をします。  エンティティは1つのservice.xml内に複数定義することが可能です。1つのエンティティが1つのテーブルに対応します。

local-serviceをtrueにすると、同じOSGi基盤上に展開されたバンドルから参照できるサービスを自動作成します。

remote-serviceをtrueに設定すると、Liferay外部からサービスをコールできるインターフェース(JSON / Web API)を自動生成します。

uuidはユニークID文字列をレコードに生成したい場合に指定します。

```xml:service.xml
<entity local-service="true" name="Foo" remote-service="true" uuid="true">
```

#### プライマリキー
その名の通り、このエンティティのプライマリキーとなります。習慣的にエンティティ名 + Idという名称で指定することが多いです。

```xml:service.xml
<column name="fooId" primary="true" type="long" />
```

#### Group ID
Liferayでは権限の制御などで、対象となるデータレコードを区分するために、Group IDというのをいたるところで利用しています。この項目は必ずエンティティに含めるようにしておいてください。後で説明するFinderでもこのキーを利用します。

```xml:service.xml
<column name="groupId" type="long" />
```

#### Auditフィールド
Liferayでは監査(Audit)の仕組みが標準で備わっていますが、その仕組みが監査ログを記録するために、これらのフィールドが必要になってきます。そのためこれらの項目は必ずエンティティに含めるようにしておいてください。

```xml:service.xml
        <column name="companyId" type="long" />
        <column name="userId" type="long" />
        <column name="userName" type="String" />
        <column name="createDate" type="Date" />
        <column name="modifiedDate" type="Date" />
```

#### ソート順
エンティティのデータを取得する際、データがリストで取得できる場合の並び順を指定します。

```xml:service.xml
        <order by="asc">
            <order-column name="field1" />
        </order>
```

#### Finderメソッド
Liferayでは単純なデータ取得のためのインターフェースを自動生成する仕組みがあり、Finderと呼ばれています。これらを設定することで、データを取得するためのFinderメソッドが生成されるようになります。任意の内容に置き換えて、Finderを作成してください。Finderも複数定義することができます。return-typeにはCollection、エンティティ名が指定できます。設定内容の詳細はDTD( http://www.liferay.com/dtd/liferay-service-builder_7_0_0.dtd )を参照してください。

```xml:service.xml
        <finder name="Field2" return-type="Collection">
            <finder-column name="field2" />
        </finder>
```

#### References
該当エンティティと関連するテーブルを指定する。デフォルトで指定されています。

```xml:service.xml
        <reference entity="AssetEntry" package-path="com.liferay.portlet.asset" />
        <reference entity="AssetTag" package-path="com.liferay.portlet.asset" />
```

はコメントやスター、タグなどのLiferayが提供している共通基盤を利用する際などに指定します。

### Serviceの生成
Todo-serviceディレクトリに移動し、

```bash
gradle buildService
```

を実行、サービスのスケルトンを生成する。するとservice.xmlの設定に従って、Finderメソッドなどが自動生成されると共に、データベースにテーブルが作成され、それを操作するためのCRUD用のメソッドも生成されます。

### Serviceにメソッドの追加
Service Builderは、実装クラス(*Impl.javaとついているファイル）から対応するインターフェースを自動生成するという動作を行います。

またLliferay側が生成したスケルトンは、開発工程の中で、モデルやサービスが変更される度にgradle buildServiceで何度も再生成します。そのためLiferayが許可しているファイル以外を編集すると上書きされてしまうので、注意が必要です。

編集可能なファイルは、

1. ***LocalServiceImpl.java**
1. ***ServiceImpl.java**
1. ***ネームスペース名Impl.java (ネームスペース名ModelImpl.javaではない)**

となる。

例えばFooLocalServiceImpl.javaに以下を足して、

```java:FooLocalServiceImpl.java
    public String getHelloWorld() {
        return "Hello World";
    }
```

gradle buildServiceを実行すると、getHelloWorldに対応するインターフェースが生成され、このサービスが利用できる状態になります。

## Portletの作成
次に、実際にLiferay上に配置して、データを表示するためのポートレットを作成します。

### bladeでポートレットスケルトン作成
Liferay 7 / DXPでは慣習的に、

|機能|サフィックス|
|:--|:--|
|サービス（実装)|*-service|
|サービス（API、インターフェース)|*-api|
|ポートレット(View部分)|*-web|

と命名します。Todoフォルダ直下に移動し、以下のコマンドでポートレットを生成します。

```bash
blade create -t mvc-portlet -p com.liferay.todo -c TodoPortlet Todo-web
```

これでTodo以下のディレクトリの構成は以下のようになります。

```bash
Todo-api/  Todo-service/  Todo-web/  build.gradle
```

生成されたTodo-webディレクトリ直下に移動して、build.gradleに以下のようにTodo-apiを追加

```java:Todo-api/build.gradle
dependencies {
    compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
    compileOnly group: "com.liferay.portal", name: "com.liferay.util.taglib", version: "2.0.0"
    compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0"
    compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1"
    compileOnly group: "jstl", name: "jstl", version: "1.2"
    compileOnly group: "org.osgi", name: "osgi.cmpn", version: "6.0.0"
    compileOnly project(":modules:Todo:Todo-api") //これを追加する。
}
```

また、TodoPortlet.javaからFooLocalServiceがコールできるように、import文と合わせて以下を追加します。

```java:FooLocalServiceImpl.java
    @Reference
    protected void setFooLocalService(
            FooLocalService fooLocalService) {

        _fooLocalService = fooLocalService;
    }   
    
    private FooLocalService _fooLocalService;
```

これで、Todo-web内でFooLocalServiceが利用できるようになりました。

### デプロイ
開発環境の場合、ローカルでLiferayサーバーを立ち上げると、bladeは自動的に起動しているサーバーに対してデプロイしてくれます。
1. Liferayを起動(localhost:8080で起動していると仮定)
2. Todoディレクトリ直下に移動、blade deployを実行。Todo-service, Todo-api, Todo-webがそれぞれデプロイされる。
3. Liferayにログイン、画面右のApplicationペインからデプロイしたTodo-webが選択＋画面上に配置できる。

## サービスビルダーポートレット自動生成ツール
サービスビルダーポートレット(*-service、*-api、*-web)を自動生成するツール、[Damascus](https://github.com/yasuflatland-lf/damascus)もあります。これにより、CRUDや検索、ワークフローなどの業務アプリに必要な機能がすでに実装されたテンプレートを生成することができます。

[Liferay DXP / 7.0 ポートレット自動生成ツール、Damascus](http://qiita.com/yasuflatland-lf/items/3421dbceaf01d1f6b37b)

## まとめ
以上が駆け足でしたが、Liferay 7 / DXPでサービスビルダーポートレットを利用する際の基本的なセットアップの例になります。