kubell Creator's Note

ビジネスチャット「Chatwork」のエンジニアのブログです。

ビジネスチャット「Chatwork」のエンジニアのブログです。

読者になる

Amazon EventBridge Pipesを使ってChatworkにメッセージを送ってみた

こんにちは!BPaaSプロダクト部の片岡です。

この記事はkubell Advent Calendar 2024(シリーズ 2)の16日目の記事です。

本投稿はAmazon EventBridge Pipesを使ってChatworkにメッセージを送れることを確認した内容となっています。

Chatwork APIでメッセージを投稿する場合、APIの利用回数制限がありリトライ処理が必要です。

AWS環境化においてはLambdaで実装することが多いのではないかと思います。

EventBridge PipesのAPI Destinationsはコードを書かずともAPI呼び出しおよびリトライ処理まで行ってくれますのでかなり楽ができるのではないかと思い、以下の構成を試してみました。

SQSキューをソースとしたEventBridge PipesでターゲットをAPI Destinationsとしていますす。

EventBridge Pipes

これでSQSにメッセージを送信すればChatwork APIでメッセージが送信されるはず。メッセージは下記のJSON形式とします。

{
   "roomId" : 123456789,
   "message" : "こんにちは"
}

次はメッセージのroomIdmessageをAPI Destinationsにパラメータとして渡します。

Chatwork APIのメッセージ投稿はURLでroom_idを指定することになっています。

developer.chatwork.com

この場合はAPI Destinationsのパスパラメータを使用します。

パスパラメータを使用する場合、API送信先のエンドポイントのパラメータとする部分に * を指定します。

Chatwork APIのメッセージ投稿のエンドポイントURLは https://api.chatwork.com/v2/rooms/{room_id}/messages なので API送信先のエンドポイントには、 https://api.chatwork.com/v2/rooms/*/messages を設定します。

続けてパスパラメータを上図のように $.body.roomId と設定します。($.body はSQSメッセージの構造です)

messageについては入力トランスフォーマーで $.body.messagebody=messageというに出力に変換します。

以上で、SQSに投稿先のルームIDと投稿するメッセージを送信するだけでメッセージを投稿できます。

ノーコードで実現できました!

なお、API Destinationsはリトライ機構を持っていて設定により適切にリトライさせることが可能です。

docs.aws.amazon.com

リトライに失敗した場合は設定によりデッドレターキューに送ることもでき、後で再処理することも可能です。

docs.aws.amazon.com

CDK Construct

CDK Constructのコードを載せておきますのでご利用ください。Chatwork APIキーはSecretsに保存されている前提です。

RateLimitについては指定していませんので、必要に応じてChatworkNotificationPropsに追加してください。

import * as cdk from 'aws-cdk-lib';
import * as events from 'aws-cdk-lib/aws-events';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as pipes from 'aws-cdk-lib/aws-pipes';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import { Construct } from 'constructs';

type ChatworkNotificationProps = {
  secretName: string;
  secretKey: string;
};

export class ChatworkNotification extends Construct {
  public readonly queue: sqs.Queue;

  constructor(scope: Construct, id: string, props: ChatworkNotificationProps) {
    super(scope, id);

    const { secretName, secretKey } = props;

    this.queue = new sqs.Queue(this, 'ChatworkApiQueue', {
      visibilityTimeout: cdk.Duration.seconds(30),
      receiveMessageWaitTime: cdk.Duration.seconds(20),
    });

    const connection = new events.Connection(this, 'ChatworkApiConnection', {
      authorization: events.Authorization.apiKey(
        'X-ChatworkToken',
        Secret.fromSecretNameV2(
          this,
          'ChatworkApiSecret',
          secretName,
        ).secretValueFromJson(secretKey),
      ),
      description: 'Connection to Chatwork API',
    });

    const apiDestination = new events.ApiDestination(
      this,
      'ChatworkApiDestination',
      {
        connection,
        endpoint: 'https://api.chatwork.com/v2/rooms/*/messages',
      },
    );

    const eventBridgePipesRole = new iam.Role(this, 'EventBridgePipesRole', {
      assumedBy: new iam.ServicePrincipal('pipes.amazonaws.com'),
      inlinePolicies: {
        ApiDestinationPolicy: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              actions: ['events:InvokeApiDestination'],
              resources: [apiDestination.apiDestinationArn],
            }),
          ],
        }),
      },
    });

    this.queue.grantConsumeMessages(eventBridgePipesRole);

    // eslint-disable-next-line no-new
    new pipes.CfnPipe(this, 'SqsToApiDestination', {
      name: 'SqsToApiDestination',
      roleArn: eventBridgePipesRole.roleArn,
      source: this.queue.queueArn,
      sourceParameters: {
        sqsQueueParameters: {
          batchSize: 1,
        },
      },
      targetParameters: {
        httpParameters: {
          pathParameterValues: ['$.body.roomId'],       
        },
        inputTemplate: 'body=<$.body.message>',
      },
      target: apiDestination.apiDestinationArn,
      logConfiguration: {
        cloudwatchLogsLogDestination: {
          logGroupArn: new logs.LogGroup(this, 'SqsToApiDestinationLogGroup', {
            retention: logs.RetentionDays.ONE_MONTH,
          }).logGroupArn,
        },
        level: 'ERROR',
      },
    });
  }
}

AWS Amplify Gen 2

BPaaSプロダクト開発ではAWS Amplify Gen 2を採用しています。我々がAWS Amplify Gen 2を選択した理由については下記の記事をご覧ください。

creators-note.chatwork.com

Amplify Gen 2はCustom resourcesやCloud sandboxという便利な仕組みを持っています。

docs.amplify.aws

docs.amplify.aws

先述のCDK ConstructをCustom resourcesとして定義してCloud sandboxを作成すれば簡単にデプロイできるのでオススメです。

Amplify BackendからChatworkへメッセージを投稿することもできるはずですが、未検証です。

まとめ

EventBridge Pipesを使ってノーコードでChatworkの任意のルームにメッセージを投稿することができました。 APIの利用回数制限があるため大量の通知を即時に投稿できるわけではないのでその点だけご留意ください。

記事は以上です。読んでいただきありがとうございました!