CAPとEvent Meshでメッセージを送受信する

#はじめに
この記事は [chillSAP 夏の自由研究2021](https://note.com/chillsap/n/n08ef5ec73b4c) の記事として執筆しています。

[前回](https://qiita.com/tami/items/727fe127158eea39fd3e)の記事で、Event Meshの概要とAPIの使い方について紹介しました。今回はCAPを使ってメッセージを送受信してみたいと思います。
CAPを使うとEvent Meshとの統合が簡単にできます。今回は以下のようなシナリオを実装します。

- CAPからEvent Meshのトピックにメッセージを送信する
- 送信したメッセージをCAPのイベントハンドラで受け取る

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5387b177-40d6-4031-38a9-48ac85eb0770.png)

自分で送信して自分で受信とは変な感じですが、簡単にするために1つのサービスにしています。

#前提
Event Meshはトライアル版(dev)を使い、サービスインスタンスの作成まで完了させておきます。
サービスインスタンスの作成方法については、[前回](https://qiita.com/tami/items/727fe127158eea39fd3e)の記事をご参照ください。

##ステップ
1. CAPのプロジェクトを作成
1. Event Meshを使うための設定
1. 送信用のイベントハンドラを作成
1. 受信用のイベントハンドラを作成
1. デプロイ

###1. CAPのプロジェクトを作成
以下のコマンドでCAPのプロジェクトを作成します。プロジェクト名は`cap-em`としました。

```
cds init cap-em
```

必要なモジュールをインストールします。

```
npm install
```

###2. Event Meshを使うための設定
以下のモジュールをインストールします。

```
npm i @sap/xb-msg-amqp-v100
```
package.jsonに以下の設定を追加します。

```json 
  "cds": {
    "requires": {
      "messaging": {
        "kind": "enterprise-messaging-shared"
      }
    }
  }
```
ローカル実行用の設定として、プロジェクトのルートに`default-env.json`ファイルを作成します。まず、以下のような構造を作ります。

```json
{
	"VCAP_SERVICES": {
		"enterprise-messaging": [
			{
				"label": "enterprise-messaging",
				"credentials": {}
			}
		]
	}
}
```
次に、`credentials`の中にEvent Meshのサービスキーの中身をコピーして貼り付けます。最終的に以下の形になります。

```json
{
	"VCAP_SERVICES": {
		"enterprise-messaging": [
			{
				"label": "enterprise-messaging",
				"credentials": {
					"xsappname": "clone-xbem-service-broker-3b8f5738face419d89ace9a895e2e28e-clone!b54386|xbem-service-broker-!b2436",
					"management": [
						{
							"oa2": {
								"clientid": "xxxx",
								"clientsecret": "xxxx",
								"tokenendpoint": "https://b736177ctrial.authentication.eu10.hana.ondemand.com/oauth/token",
								"granttype": "client_credentials"
							},
							"uri": "https://enterprise-messaging-hub-backend.cfapps.eu10.hana.ondemand.com"
						}
					],
					"serviceinstanceid": "3b8f5738-face-419d-89ac-e9a895e2e28e",
					"messaging": [
						{
							"oa2": {
								"clientid": "xxxx",
								"clientsecret": "xxxx",
								"tokenendpoint": "https://b736177ctrial.authentication.eu10.hana.ondemand.com/oauth/token",
								"granttype": "client_credentials"
							},
							"protocol": [
								"amqp10ws"
							],
							"broker": {
								"type": "sapmgw"
							},
							"uri": "wss://enterprise-messaging-messaging-gateway.cfapps.eu10.hana.ondemand.com/protocols/amqp10ws"
						},
						{
							"oa2": {
								"clientid": "xxxx",
								"clientsecret": "xxxx",
								"tokenendpoint": "https://b736177ctrial.authentication.eu10.hana.ondemand.com/oauth/token",
								"granttype": "client_credentials"
							},
							"protocol": [
								"mqtt311ws"
							],
							"broker": {
								"type": "sapmgw"
							},
							"uri": "wss://enterprise-messaging-messaging-gateway.cfapps.eu10.hana.ondemand.com/protocols/mqtt311ws"
						},
						{
							"oa2": {
								"clientid": "xxxx",
								"clientsecret": "xxxx",
								"tokenendpoint": "https://b736177ctrial.authentication.eu10.hana.ondemand.com/oauth/token",
								"granttype": "client_credentials"
							},
							"protocol": [
								"httprest"
							],
							"broker": {
								"type": "saprestmgw"
							},
							"uri": "https://enterprise-messaging-pubsub.cfapps.eu10.hana.ondemand.com"
						}
					]
				}
			}
		]
	}
}
```


###3. 送信用のイベントハンドラを作成
srvフォルダ配下に`catalog-service.cds`ファイルを作成します。

```:catalog-service.cds
service CatalogService {
    type Message {
        id: Integer;
        message: String;
    }
    action send(message: Message)
}
```
srvフォルダ配下に`catalog-service.js`ファイルを作成し、`send`アクションが呼ばれたときのイベントハンドラを実装します。ここでは"topic1"というトピックに対してメッセージを送信しています。

```javascript:catalog-service.js
module.exports = async function () {
    const messaging = await cds.connect.to("messaging")
    this.on('send', async (req) => {
        console.log(JSON.stringify(req.data))
        messaging.emit("topic1", req.data.message)
    })
}
```
####テスト実行
`cds watch`コマンドでサービスを起動します。
コマンドラインに以下のように表示されれば、Event Meshに接続できています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d9c09c4c-bbc8-7312-dd97-dca86a372a9b.png)
VS Codeの拡張である[REST Client](https://qiita.com/toshi0607/items/c4440d3fbfa72eac840c)を使ってテストをしてみます。以下のようにテスト用のファイルを作成します。

```:test.http
POST http://localhost:4004/catalog/send
Content-Type: application/json

{
	"message": {
		"id": 100,
		"message": "hello"
	}
}

```

"Send Request"の文字をクリックしてリクエストを送信します。右側が結果です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/cfe30f5a-7af6-24de-3b37-ca4196d10a3e.png)
コンソールには以下のように、送信したメッセージの内容が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/af23dc9f-1471-b4bb-c60a-29333cb15f4d.png)

###4. 受信用のイベントハンドラを作成
メッセージを受信したときに動くイベントハンドラを作成します。`catalog-service.js`に以下のコードを追加します。"topic1"に受信したメッセージを表示するだけのシンプルな処理です。

```javascript
module.exports = async function () {
    const messaging = await cds.connect.to("messaging")
    this.on('send', async (req) => {
        console.log(JSON.stringify(req.data))
        messaging.emit("topic1", req.data.message)
    })

    //add
    messaging.on('topic1', async(msg)=> {
        const messagePayload = JSON.stringify(msg.data)
        console.log('===> Received message : ' + messagePayload)       
    })
}
```
`cds watch`を実行すると、コンソールに以下のように表示されます。これは、"CAP/0000"というキューと、"topic1"に対するキューサブスクリプションが登録されたことを表しています。
そして、さきほど`send`アクションで送信したメッセージを受信しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5bea68f4-2ef9-9cd0-f979-6d8fcf1ed91f.png)

メッセージの内容を少し変えて送信してみます。

```json
{
    "message": {
        "id": 101,
        "message": "hello"
    }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/7e51951f-8f07-62c6-e617-444035cc41d3.png)

####Event MeshのAPIを使ってメッセージを送信
[前回](https://qiita.com/tami/items/727fe127158eea39fd3e#241-%E3%83%88%E3%83%94%E3%83%83%E3%82%AF%E3%81%AB%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E9%80%81%E4%BF%A1)の記事でやったように、APIを使ってメッセージを送ってみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5164f317-3893-564b-8a7a-8732e24f48e9.png)
受信したメッセージが`undefined`になってしまいました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/78a381c2-2ef4-5b77-c0f2-0e4794792f3f.png)
そこで、イベントハンドラを以下のように変えてみます。

```javascript
    messaging.on('topic1', async(msg)=> {
        const messagePayload = JSON.stringify(msg.data)
        console.log('===> Received message (body) : ' + messagePayload)

        const messageHeader = JSON.stringify(msg.headers)
        console.log('===> Received message (header) : ' + messageHeader)
               
    })
```

ヘッダにメッセージを受信していました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f8339cb1-4b5c-cee5-f777-d0c87d2c685f.png)

`send`アクションでCAPからメッセージを送った場合は以下のようになります。なぜこのような違いがあるのかはわかりませんが、ヘッダ、ボディ両方にメッセージが来るケースを想定しておいたほうがよさそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/55c2a1e9-4393-a45b-c578-95e46322a125.png)

####CAPで使用するキューについて
受信用ハンドラを定義した場合、キューはデプロイ時に自動で登録されます。よって、**キューをマニュアルで登録する必要はありません。**"CAP/0000"というキューはローカル実行用のキューで、デプロイすると本番用のキューが別途登録されます。

※自動登録されたキューではなく独自のキューを使いたい場合は、以下のブログが参考になります。
[CAP – Specifying the queues to be consumed from SAP Event Mesh](https://blogs.sap.com/2021/06/21/cap-specifying-the-queues-to-be-consumed-from-sap-event-mesh/)

###5. デプロイ
以下のコマンドでmta.yamlファイルを生成します。

```
cds add mta
```

`resources`セクションにEvent Meshのサービスインスタンス（登録済のもの）を指定し、サービスの`requires`セクションにも同じものを追加します。

```yaml
_schema-version: '3.1'
ID: cap-em
version: 1.0.0
description: "A simple CAP project."
parameters:
  enable-parallel-deployments: true
   
build-parameters:
  before-all:
   - builder: custom
     commands:
      - npm install --production
      - npx -p @sap/cds-dk cds build --production

modules:
 # --------------------- SERVER MODULE ------------------------
 - name: cap-em-srv
 # ------------------------------------------------------------
   type: nodejs
   path: gen/srv
   parameters:
     buildpack: nodejs_buildpack
   provides:
    - name: srv-api      # required by consumers of CAP services (e.g. approuter)
      properties:
        srv-url: ${default-url}
   requires:
    - name: em-test #add

resources:
 - name: em-test #add
   type: org.cloudfoundry.existing-service
```

以下のコマンドでビルド、デプロイします。

```
mbt build
cf deploy mta_archives/cap-em_1.0.0.mtar
```
デプロイ後、`cf logs cap-em-srv --recent`でサービスのログを表示すると、登録されたキュー名を見ることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/517e8d3b-6ebe-81f1-6d23-e9d3e5c20585.png)
キュー名は`cap-em-srv/8974`となり、`8974`の部分は何かといえば、登録されたアプリケーションIDの先頭4桁のようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f82c5f9b-607d-3eac-d461-836cc42dbf8c.png)



##参考

- [SAP Cloud Application Programming Model and Enterprise Messaging [2]: Send message](https://blogs.sap.com/2020/03/03/sap-cloud-application-programming-model-and-enterprise-messaging-2-send-message/)
- [SAP Cloud Application Programming Model and Enterprise Messaging [3]: Receive message](https://blogs.sap.com/2020/03/03/sap-cloud-application-programming-model-and-enterprise-messaging-3-receive-message/)
- [SAP Cloud Application Programming Model and Enterprise Messaging [4]: Local Development](https://blogs.sap.com/2020/03/09/sap-cloud-application-programming-model-and-enterprise-messaging-4-local-development/)