cds-pluginとは何か

## はじめに
この記事は [SAP Advent Calendar 2023](https://adventar.org/calendars/8870)の 12月11日分の記事として執筆しています。

cds-pluginとは、CAPのアプリケーションに機能を追加することができる仕組みで、[2023年3月にリリースされました。](https://cap.cloud.sap/docs/releases/march23#new-cds-plugin-technique)機能をパッケージ化しておき、他の複数のアプリケーションから利用することができます。


ここで「npmのライブラリとどう違うの？」という疑問が浮かぶかもしれません。プラグインは確かにnpmのライブラリとして提供されます。違いは、ライブラリは開発者が自分のソースの中で利用するのに対し、cds-pluginは使用先のアプリケーションの[cdsファサード](https://cap.cloud.sap/docs/node.js/cds-facade)（API）に直接アクセスし、機能を実行する点です。このため使用先のアプリケーションで（ほとんど）コードを書くことなしに機能を追加することができます。
※機能を有効化するためにアノテーションをつけるなど、プラグインによって使い方は異なります

## cds-pluginの仕組み
### 仕組み
cds-pluginを提供するパッケージは、package.jsonと同じ階層にcds-plugin.jsというファイルを置き、この中で処理を書きます。あとは、利用先のアプリケーションがプラグインをインストールするだけ、というとてもシンプルな仕組みです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/76588c52-2939-083a-bf45-8cf9991849c3.png)


### プラグインの作成
プラグインの仕組みを理解するために、以下のブログを参考に「特定のアノテーションをつけた項目に絵文字を追加する」プラグインを作成してみます。ソースコードは元のブログのものをそのまま使用している点をご承知おきください。

https://blogs.sap.com/2023/04/30/reusable-components-for-cap-with-cds-plugin/

＜ステップ＞
1. CAPプロジェクトを作成
1. プラグインを作成
1. CAPプロジェクトにプラグインを組み込み
1. プラグインの実装
1. CAPプロジェクトでアノテーションを追加

#### 1. CAPプロジェクトを作成
以下のコマンドでCAPプロジェクトを作成し、サンプルのデータモデルを追加します。

```
cds init plugin-consumer 
cd plugin-consumer
cds add sample-tiny
```

#### 2. プラグインを作成
plugin-consumerプロジェクトの中に新しく`emojiPlugin`というフォルダを作成し、package.jsonファイルを追加します。

```
mkdir emojiPlugin && cd emojiPlugin && npm init -y && cd ..
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/4000d0a7-3f15-7d27-6640-7c7b3f8a5cec.png)

cds-plugin.jsファイルを追加し、以下のコードを書きます。

```
console.log('my awesome plugin')
```
プロジェクトの構成は以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/e1029f92-68a5-a540-25fa-046b0e24a6f1.png)

#### 3. CAPプロジェクトにプラグインを組み込み
CAPプロジェクトのpackage.jsonに以下の設定を追加します。
※注意点：workspacesのほうはフォルダ名なので"P"が大文字、dependenciesのほうはプラグインのpackage.jsonのnameを指定するので"p"が小文字になります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d47024c6-fbb5-ad21-c0b7-57467ed83a29.png)

`npm install`でdependencyをインストールし、`cds watch`でサービスを起動します。すると、コンソールにプラグインで出力したメッセージが表示されます。これによりプラグインが使用されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/10e08a36-74b4-279a-cb54-b4a8eed60cb7.png)

#### 4. プラグインの実装
エンティティの項目に`@randamEmoji`というアノテーションがついている場合に、その項目の末尾にランダムな絵文字をつける実装をします。emojiPlugin/cds-plugin.jsを以下のように実装します。この例のように、cds-pluginでは使用先のアプリケーションのライフサイクルイベントやCRUDイベントなどに乗じて処理を挿入することが可能です。

```javascript
const cds = require('@sap/cds')
// 絵文字の定義
const emojis = ["😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "🥲", "🥹", "😊", "😇", "🙂", "🙃", "😉", "😌", "😍"]
//our business logic...
function getRandomEmoji() {
  return emojis[Math.floor(Math.random() * emojis.length)]
}
// すべてのサービスがブートストラップされ、
// expressに追加されたタイミングで発生するイベントでロジックを追加する
cds.once('served', () => {
  // すべてのサービス、エンティティをトラバースして@randomEmojiアノテーションがついている項目を探す
  for (let srv of cds.services) {
    for (let entity of srv.entities) {
      const emojiElements = []
      for (const key in entity.elements) {
        const element = entity.elements[key]
        // 項目に@randomEmojiアノテーションがついているかをチェック
        if (element['@randomEmoji']) emojiElements.push(element.name)
      }
      // アノテーション付きの項目がある場合、READオペレーションの後に呼ばれるイベントハンドラを登録
      if (emojiElements.length) {
        srv.after('READ', entity, data => {
          if (!data) return
          // 単一レコードを取得した場合は配列にならないので、配列にする
          let myData = Array.isArray(data) ? data : [data]
          // アノテーションがついている項目を探して絵文字を追加
          for (let entry of myData) {
            for (const element of emojiElements) {
              if (entry[element]) {
                entry[element] += getRandomEmoji()
              }
            }
          }
        })
      }
    }
  }
})
```

#### 5. CAPプロジェクトでアノテーションを追加
srv/cat-service.cdsファイルでアノテーションを追加します。

```
annotate CatalogService.Books with {
  title @randomEmoji;
};
```
ブラウザでエンティティを読み込むと、以下のように絵文字が付与されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/6d538b4e-00a0-ad68-0d24-4cbed0e51e07.png)

この例では同一プロジェクトの中にプラグインのフォルダを作成しましたが、実際には独立したプロジェクトとして作成し、npmパッケージとして提供することが可能です。

## 提供されているプラグイン
12月7日現在、[CAPire](https://cap.cloud.sap/docs/plugins/)には以下のプラグインが掲載されています。

|プラグイン  |パッケージ  |説明  | 
|---|---|---|
|GraphQL Adapter  |[@cap-js/graphql](https://www.npmjs.com/package/@cap-js/graphql)  |CAPのモデルに対してGraphQLのスキーマを生成し、GraphQLのクエリ言語でサービスを呼び出すためのエンドポイントを提供する。  |
|OData v2 Proxy  |[@cap-js-community/odata-v2-adapter](https://www.npmjs.com/package/@cap-js-community/odata-v2-adapter)  |OData V2のサービスを公開するためのプロトコルアダプター。以前は@sap/cds-odata-v2-adapter-proxyを使用していたが、プラグイン化することによってserver.jsを自分で実装しなくてよくなった。|
|UI5 Dev Server  |[cds-plugin-ui5](https://www.npmjs.com/package/cds-plugin-ui5)  |UI5アプリをCDSサーバー上で動かすためのプラグイン。これを使うとCAPのプロジェクトの中で開発するUI5アプリでTypeScriptが使用可能になる。  |
|Change Tracking  |[@cap-js/change-tracking](https://npmjs.com/package/@cap-js/change-tracking)  |アノテーションをつけた項目について、変更履歴の保存と照会を可能にする。  |
|Audit Logging  |[@cap-js/audit-logging](https://www.npmjs.com/package/@cap-js/audit-logging)  |アノテーションをつけた（個人情報に関わる）項目で発生するイベントについて、Audit Log Serviceにログを残す。  |
|Notifications  |[@cap-js/notifications](https://www.npmjs.com/package/@cap-js/notifications)  |Alert Notificationsサービスへ通知を送信する（※）。  |

上記以外のプラグインは以下のページで確認できます。
https://bestofcapjs.org/#


**※@cap-js/notificationsについて**
CAPireのドキュメントでは上記の説明ですが、[npm](https://www.npmjs.com/package/@cap-js/notifications)の説明ではSAP Build Work Zoneへ通知を送るという説明になっています。両方できるということなのでしょうか🤔
@cap-js/notificationsについて検証した記事は[こちら](https://qiita.com/tami/items/7c786f9d79a5fd157b24)

## おわりに
プラグインは、複数のプロジェクトで共通的な処理を実装したいという場面でも使えそうです。たとえば、以下のようなプラグインが考えれます。

- 共通的なフォーマットでログを出力するためのプラグイン
- エラーハンドリングのためのプラグイン