CAP (Java)でPostgreSQLを使う方法

## はじめに
この記事では、CAP (Java) のアプリケーションでBTPの[PostgreSQL](https://discovery-center.cloud.sap/serviceCatalog/postgresql-hyperscaler-option?region=all)をデータベースとして使用する方法について紹介します。

[過去の記事](https://qiita.com/tami/items/a7a6b019cf8e82efcf8e)でコミュニティ開発のモジュールである`cds-pg`および`cds-dbm`を使用した方法を紹介しましたが、cds 7.xから[@cap-js/postgres](https://www.npmjs.com/package/@cap-js/postgres)が登場してPostgreSQLの使用が公式にサポートされるようになりました。これにともない、`cds-pg`と`cds-dbm`はdeprecatedとなっています。

**関連ブログ**
- [Run and Deploy SAP CAP (Node.js or Java) with PostgreSQL on SAP BTP Cloud Foundry](https://community.sap.com/t5/technology-blogs-by-sap/run-and-deploy-sap-cap-node-js-or-java-with-postgresql-on-sap-btp-cloud/ba-p/13558467)
- [Run and Deploy SAP CAP (Java) with PostgreSQL using Official Database Adapter @cap-js/postgres](https://community.sap.com/t5/technology-blogs-by-sap/run-and-deploy-sap-cap-java-with-postgresql-using-official-database-adapter/ba-p/13564753)

## 実施すること
- ローカル実行にはインメモリデータベース（H2）を使用する
- Cloud FoundryではPostreSQLを使用する
- ハイブリッド実行（BTP上のPostreSQLに接続）

**バージョン**
@sap/cds: 8.6.1
@sap/cds-dk: 8.4.2

**開発環境**
SAP Business Application Studio

## ステップ
1. プロジェクトの作成
1. サンプルサービスの設定
1. PostgreSQLを追加
1. デプロイのための設定
1. ビルド、デプロイ
1. 動作確認

### 1. プロジェクトの作成
CAP (Java) のプロジェクトを作成します。

```
cds init cap-java-postgresql --add java
```
### 2. サンプルサービスの設定
サンプルサービスを追加します。

```
cds add tiny-sample
```
以下のコマンドでサービスを実行します。
```
mvn spring-boot:run
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/aaba23ec-aa95-6980-ca92-6d7f92598d4e.png)

### 3. PostgreSQLを追加
以下のコマンドでPostgreSQLのための設定を追加します。
```
cds add postgres
```
この結果、以下の設定が追加されます。

```xml:srv/pom.xml
		<dependency>
			<groupId>com.sap.cds</groupId>
			<artifactId>cds-feature-postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>
```
```json:package.json
  "dependencies": {
    "@cap-js/postgres": "^1"
  }
```
```yaml:srv/src/main/resources/application.yaml
---
spring:
  datasource:
    password: postgres
    embedded-database-connection: none
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/postgres
    username: postgres
  config:
    activate:
      on-profile: default
```

#### application.yamlの設定を元に戻す 
`application.yaml`がローカル実行時もローカルのPostgreSQLに接続する設定に変更されたので、このまま`mvn spring-boot:run`を実行するとDBに接続できずにエラーになります。ローカル実行時にはこれまで通りH2データベースに接続するため、デフォルトのプロファイルを元に戻します。

`application.yaml`の全体は以下のようになります。


```yaml
---
spring:
  config.activate.on-profile: default
  sql.init.schema-locations: classpath:schema-h2.sql
cds:
  data-source.auto-config.enabled: false
```

### 4. デプロイのための設定
mta.yamlを追加します。
```
cds add mta
```
mta.yamlファイルを開き、サービスの`properties`に以下の設定を追加します。`<POSTGRESQL_SERVICE_NAME>`には、PostgreSQLのサービス名（mta.yamlのresourcesセクション参照）を設定します。
```
CFENV_SERVICE_<POSTGRESQL_SERVICE_NAME>_ENABLED: false
```
:::note
**設定の意味**
デフォルトでは`java_buildpack`がPostgreSQLデータソースを初期化します。CAPで動かすためにはPostreSQLのデータソースはCAP Javaランタイムが作成する必要があるため、ビルドパックによるデータソースの初期化を無効化しています。（参考：[Provisioning a DB Instance](https://cap.cloud.sap/docs/guides/databases-postgres#provisioning-a-db-instance)）
:::

以下は設定例です。
```yaml:mta.yaml
modules:
  - name: cap-java-postgresql2-srv
    type: java
    path: srv
    parameters:
      buildpack: sap_java_buildpack_jakarta
      readiness-health-check-type: http
      readiness-health-check-http-endpoint: /actuator/health/readiness
    properties:
      SPRING_PROFILES_ACTIVE: cloud,sandbox
      JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']"
      JBP_CONFIG_SAP_MACHINE_JRE: '{ version: 21.+ }'
      CFENV_SERVICE_CAP-JAVA-POSTGRESQL-POSTGRES_ENABLED: false #追加
```
#### ビルドの設定
postgresqlのデプロイヤーは`gen/pg`フォルダ配下にあるビルド結果を使用しますが、生成されたmta.yamlにはビルドのためのコマンドがありません。そこで、mta.yamlの先頭に以下の設定を追加します。`cds build --production`コマンドにより`gen/pg`フォルダが生成されます。

```yaml
build-parameters:
  before-all:
    - builder: custom
      commands:
        - npx cds build --production  
```
先頭部分は全体で以下のようになります。
```yaml
_schema-version: 3.3.0
ID: cap-java-postgresql2
version: 1.0.0-SNAPSHOT
description: "A simple CAP project."
parameters:
  enable-parallel-deployments: true
build-parameters:
  before-all:
    - builder: custom
      commands:
        - npx cds build --production  
```
#### application.yamlの設定
デプロイしたサービスからPostgreSQLに接続できるよう、application.yamlに以下の設定を追加します。（参考：[Run and Deploy SAP CAP (Java) with PostgreSQL using Official Database Adapter @cap-js/postgres](https://community.sap.com/t5/technology-blogs-by-sap/run-and-deploy-sap-cap-java-with-postgresql-using-official-database-adapter/ba-p/13564753)）
```yaml
---
spring:
  config.activate.on-profile: cloud
  datasource:
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://${vcap.services.<POSTGRESQL_SERVICE_NAME>.credentials.hostname}:${vcap.services.<POSTGRESQL_SERVICE_NAME>.credentials.port}/${vcap.services.<POSTGRESQL_SERVICE_NAME>.credentials.dbname}
      username: ${vcap.services.<POSTGRESQL_SERVICE_NAME>.credentials.username}
      password: ${vcap.services.<POSTGRESQL_SERVICE_NAME>.credentials.password}
```
```yaml:例
---
spring:
  config.activate.on-profile: cloud
  datasource:
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://${vcap.services.cap-java-postgresql-postgres.credentials.hostname}:${vcap.services.cap-java-postgresql-postgres.credentials.port}/${vcap.services.cap-java-postgresql-postgres.credentials.dbname}
      username: ${vcap.services.cap-java-postgresql-postgres.credentials.username}
      password: ${vcap.services.cap-java-postgresql-postgres.credentials.password}  
```


※[ドキュメント](https://cap.cloud.sap/docs/guides/databases-postgres)には記載がなかったのですが、この設定がないとBooksエンティティを照会しようとしたときに以下のエラーが返されました。

```json
{
    "error": {
        "code": "50003009",
        "message": "Failed to start a new transaction"
    }
}
```

#### postgresql-dbサービスプランを変更（トライアルアカウントの場合）
`resources`セクションに追加されたpostgresql-dbサービスには、デフォルトでは`development`プランが設定されています。トライアルアカウントを使用している場合はサービスプランを`trial`に変更します。

```yaml
resources:
  - name: cap-java-postgresql2-postgres
    type: org.cloudfoundry.managed-service
    parameters:
      service: postgresql-db
      service-plan: development
```
:::note
トライアルアカウントの場合、作成できるサービスインスタンスは1つまでです。
:::

### 5. ビルド、デプロイ
以下のコマンドでビルド、デプロイします。
```
mbt build -t gen --mtar mta.tar
cf deploy gen/mta.tar
```

### 6. 動作確認
デプロイされたサービスにアクセスし、データが取得できることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b0d2a4c6-2f05-b3d4-e769-12ac6f552244.png)

## ハイブリッド実行
BTPのPostgreSQLに接続するには、BASからアプリケーションにSSHで接続し、ローカル（BAS）のポートとPostgreSQLのポートをトンネリングする必要があります。以下のステップで行います。

### 1. PostgreSQLのサービスキーを作成（まだない場合）
```
cf create-service-key cap-java-postgresql-postgres cap-java-postgresql-postgres-key
```

### 2. サービスキーを確認
```
cf service-key cap-java-postgresql-postgres cap-java-postgresql-postgres-key
```
`dbname`, `hostname`, `port`, `username`, `password`などが表示されます。

### 3. アプリケーションでSSHを有効化
```
cf enable-ssh cap-java-postgresql-srv
cf restart cap-java-postgresql-srv #再起動
```
### 4. BASのポート63306にPostgreSQLのポートをマッピング
```
cf ssh -L 63306:<hostname>:<port> cap-java-postgresql-srv
```
`hostname`および`port`はサービスキーで確認したものを設定します。

### 5. application.yamlにハイブリッド用の設定を追加
```yaml
---
spring:
  config.activate.on-profile: hybrid
  datasource:
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://localhost:63306/${DB_NAME}
      username: ${DB_USERNAME}
      password: ${DB_PASSWORD}
```

### 6. .envファイルに環境変数を設定
プロジェクトのルートに`.env`ファイルを作成します。
※`.env`ファイルは`.gitignore`に追加すること

```:.env
DB_NAME=<dbname>
DB_USERNAME=<username>
DB_PASSWORD=<password>
```

### 7. ハイブリッドモードで実行
```
cds bind --exec -- mvn spring-boot:run \
  -Dspring-boot.run.profiles=default,hybrid
```

この結果、PostgreSQLのDBに接続しデータを取得することができます。