CAPでマルチテナントなサービスを作成する

## マルチテナントなサービスとは
SAP BTPにおけるマルチテナントなサービスとは、サービスを提供するサブアカウント（プロバイダー）以外のサブアカウント（コンシュマー）から利用可能なサービスのことを指します。サービスはプロバイダーのサブアカウントで実行され、コンシュマーのサブアカウントがSaaS的に利用するという形態になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5540afdc-3d29-3d7a-24f6-b5529f9fd46b.png)
[Help Portal | Developing Multitenant Applications in the Cloud Foundry Environment](https://help.sap.com/docs/btp/sap-business-technology-platform/developing-multitenant-applications-in-cloud-foundry-environment)より引用

## CAPでマルチテナントなサービスを作成する
2023年6月のリリースで、CAPireのドキュメントが大幅に更新されました。ドキュメントに沿って行けば、デプロイまでこぎつけられます。

https://cap.cloud.sap/docs/guides/multitenancy/

**補足：サイドカーについて**
最近のCAPのマルチテナントの特徴は、メインのサービスに加えて**サイドカー**と呼ばれるサービスが作られることです。このためにプロジェクトに"mtx"というフォルダが追加されます。サイドーカーはテナントのサブスクライブ、アンサブスクライブといったリソースに負荷がかかるオペレーションをメインのサービスから切り離して実行するためのサービスです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d29a7be0-f005-d6cd-c13b-862abf06dc3e.png)

この記事では、ドキュメントに書かれていない以下の太字のステップについて説明します。デプロイ先はCloud Foundry、データベースはSAP HANA Cloudとします。

1. プロジェクトを生成
1. Multitenancyを有効化
1. Dependencyをインストール
1. ローカルで実行
1. **デプロイ** 
1. **コンシュマーのテナントでサブスクライブ**
1. **ルートの追加**
1. **リダイレクト用URLの追加**


## 手順
この記事を書くために作成したプロジェクトは以下のGitHubリポジトリにあります。

https://github.com/miyasuta/multitenant-bookshop/tree/main

### 前提
@sap/cdsのバージョンは7.0.2を使用しています。その他のモジュールのバージョンは以下の通りです。
```
@sap/cds: 7.0.2
@sap/cds-compiler: 4.0.2
@sap/cds-dk: 7.0.2
@sap/cds-dk (global): 7.0.1
@sap/cds-fiori: 1.0.0
@sap/cds-foss: 4.0.2
@sap/cds-hana: 2.0.0
@sap/cds-mtxs: 1.9.1
@sap/eslint-plugin-cds: 2.6.3
Node.js: v16.19.0
```

### 5. デプロイ
デプロイする前に、データベースに何を使うのかを指定する必要があります。ここではSAP HANA Cloudを使うため以下のコマンドでデータベースを指定します。

```
cds add hana --for production
```

その結果、以下の設定が追加されます。従来は`hdb`というモジュールによりHANA Cloudをサポートしていましたが、[2023年6月に`@sap/cds-hana`がリリース](https://cap.cloud.sap/docs/releases/jun23#new-packages)され、今後はこちらを使用するようになっています。

```json:package.json
  "dependencies": {
    ...
    "@sap/cds-hana": "^2"
  },
  ...
  "cds": {
    "profile": "with-mtx-sidecar",
    "requires": {
      "multitenancy": true,
      "[production]": {
        "auth": "xsuaa",
        "db": "hana"
      }
    }
  }
```
上記の設定後、[ドキュメント](https://cap.cloud.sap/docs/guides/multitenancy/#_5-deploy-to-cloud)の通り以下のコマンドでビルド、デプロイします。

App routerを追加
```
cds add approuter --for production
```

mta.yamlを追加
```
cds add mta
```

npmのdependencyをフリーズ
```
npm update --package-lock-only
npm update --package-lock-only --prefix mtx/sidecar
```
ビルド、デプロイ
```
mbt build -t gen --mtar mta.tar
cf deploy gen/mta.tar -f
```

### 6. コンシュマーのテナントでサブスクライブ
コンシュマー用のサブアカウントを新たに作成します。このサブアカウントでは、Cloud Foundryを有効化する必要はありません。ここでは"tenant"というサブアカウントを作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/31bf6870-8a45-5484-47e3-684c43c0cffb.png)

Service Marketplaceで対象のサービスを探し、**Create**をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/8fb550ee-3538-db96-43e1-729b5ffec755.png)
**Create**をクリックしてサブスクライブします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5bfe9390-924a-332a-dc71-8cb8ee4eec6b.png)
サブスクライブが完了したら、**Go to Application**をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b7bad3ea-87c9-5f42-1fca-ba9f4db90527.png)
この時点では**404 Not Found**というエラーになってしまいます。これは、指定されたURLに対応するルートがまだ存在しないためです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/072a1cd2-35a6-265e-28de-a17fe430727a.png)

### 7. ルートの追加
プロバイダーのサブアカウントのCloud FoundryスペースでRoutesメニューを開き、**New Route**をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/6ddb7e01-3389-1ce2-a770-cd9dccab501d.png)
"Not Found"になったURLからホスト名、ドメインを以下のとおりに取得してルートに設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ba62256e-3b0c-3037-e02e-ae89907a5161.png)
次に、作成したルートにアプリケーションをマッピングします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/cc4fd076-dc38-88ad-8b75-55ae785957e1.png)
ApplicationにApp router（-srvや-mtxとつかないもの）を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/336841f3-99e2-e6be-8566-fbbd21b4b913.png)

※ここではマニュアルで行いましたが、サブスクライブ時にCloud FoundryのAPIを使用して自動でルートを登録することも可能です。方法は以下の動画をご参照ください。
また、カスタムドメインを使用している場合は[ホストとカスタムドメインのひもづけを事前に行う](https://blogs.sap.com/2019/08/02/step-by-step-guide-to-custom-domains-with-multitenant-multi-target-applications/)ため、個別のルートの登録は不要です。このためCAPの中では自動的にルートを登録する仕組みになっていないのかもしれませんね。

https://www.youtube.com/watch?v=Xjn09Ae7_Z8&list=PLkzo92owKnVx3Sh0nemX8GoSNzJGfsWJM&index=14

再度アプリケーションのURLを開くと、App routerによりログインを求められます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b8529460-e770-c192-7a1d-5f5475b06aaa.png)
ログインすると、以下の画面になります。リダイレクト先のURLのドメインが許可されていないためにエラーになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d8fe9e60-5a23-8d6f-aa8d-59643a1371b2.png)

### 8. リダイレクト用URLの追加
リダイレクト先のURLを許可する設定をxs-security.jsonに追加します。

```json
  "oauth2-configuration": {
    "redirect-uris": [
      "https://*.cfapps.us10-001.hana.ondemand.com/**"
    ]
  }
```

全体では以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/fd2400c9-c84e-4a6f-6805-4b928be18f64.png)

再度ビルド、デプロイを行います。サービスのURLを開くと今度は"Not Found"となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b739325a-74a4-e2c0-cd45-731d49da469c.png)
`app/index.html`の代わりに`/odata/v4/<サービス名>/$metadata`を指定するとメタデータが取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/fffd5905-3714-6ee9-796f-1ac063671ede.png)
Not Foundとなるのは、App routerの設定（xs-app.json）で指定されているwelcomFileが存在しないためです。対応としてここではwelcomFileの設定を削除してしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/a6c5a56b-bd1c-bb2a-e0e3-ece6ec983966.png)
これにより、サービスのURLを開いたときによく知っているCAPのサービス画面が表示されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/58abfe94-c8a0-ab3a-f734-295fce2060b3.png)

## おわりに
この記事では私がつまづいた順序をそのまま再現していますが、本来ステップ7と8はコンシュマーがサブスクライブする前に実施しておくべきです（ルートの登録は自動化、またはカスタムドメインを使うことが望ましい）。そうしないと「サブスクライブしたのに使えない！」ということになってしまいます。

今回は基本的なマルチテナントアプリケーションの作成、デプロイ方法を紹介しましたが、「ルートの自動登録はどうするのか」「Destinationサービスを使いたいとき（各コンシュマーで異なる宛先の設定とする）ときはどうするのか」など、まだ気になる点があります。検証したらまた記事にしたいと思います。