定時ランダムアサインslack bot@AWS

はじめに

ライクル開発部のエンジニアの永井です。
ライクルでは、GoogleBusinessProfileを通した集客支援サービスを提供しています。
突然ですが、皆様はこんなランダムアサインslack botを設定していたりしませんか?

今回は、チーム運営する上でランダムアサインを便利に設定できるslack botを開発したモチベとその構成について話したいと思います。

目的

ライクル開発部では、上記のようなランダムアサインbotを簡単に作るために
GASにて実装していました。
しかしGASで実装すると、どのGASで実装したかわからなくなったり
同じようなGASが各チームで実装されたりします。
汎用的なbotを1つ作ることで、一元管理したいと考えました。

裏の目的

上記の目的以外にも、私自身の技術的興味として下記試したいということもありました。 (こっちが本当の目的)

  • slack botフレームワークであるboltを試したい
    • 既に事業部内でboltで実装されているbotのメンテナンスができるように
  • AWS Lambda Function URLsを使ってみたい
  • AWSにおけるEventBridge以外の定期実行方法を考えてみたい

設定UI

実際に設定を行うUIはBlock Kit Builderを使い下記のようにしました。
アイコンダサい。。

設定閲覧UI

また設定の削除はコマンドを実行することで下記のようなメッセージを実行者本人のみに表示し、削除ボタンを押すことで可能にしています。

アーキテクチャ

メッセージの定時送信はStep FunctionsのWaitの機能を使い指定時間までメッセージ送信を遅延させることで実現しています。
Waitしている間は料金は全くかからず、最大1年待つことができるとのこと。
アーキテクチャは下記のようになっています。

また、Step Functionsはこのようになっています。

CDK

参考までにCDKの設定も載せます。(cdk version: 2.23.0)

// dynamodbs
// 定期実行設定保存先
const dynamodbSchedulesTable = new aws_dynamodb.Table(
    this,
    'randomizer_schedules',
    {
    partitionKey: { name: 'id', type: aws_dynamodb.AttributeType.STRING },
    tableName: 'randomizer_schedules',
    },
)
// 前回アサイン者保存先
const dynamodbLastTimesTable = new aws_dynamodb.Table(
    this,
    'randomizer_last_times',
    {
    partitionKey: {
        name: 'schedule_id',
        type: aws_dynamodb.AttributeType.STRING,
    },
    tableName: 'randomizer_last_times',
    },
)

// lambdas
// Slack BoltのLambda設定
const bolt = new NodejsFunction(this, 'randomizer-bolt-lambda', {
    entry: 'src/app.ts',
    handler: 'handler',
    environment: {
    SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN || '',
    SLACK_SIGNING_SECRET: process.env.SLACK_SIGNING_SECRET || '',
    },
    memorySize: 2 ** 8,
})
// [AWS Lambda Function URLsの設定
bolt.addFunctionUrl({
    authType: FunctionUrlAuthType.NONE,
})

// Step Functionの最初のLambda設定
const kicker = new NodejsFunction(this, 'randomizer-kicker-lambda', {
    entry: 'src/command/kicker.ts',
    handler: 'handler',
    memorySize: 2 ** 8,
})

// メッセージ送信Lambda設定
const messenger = new NodejsFunction(this, 'randomizer-messenger-lambda', {
    entry: 'src/command/messenger.ts',
    handler: 'handler',
    environment: {
    INCOMING_WEBHOOK_URL: process.env.INCOMING_WEBHOOK_URL || '',
    },
    memorySize: 2 ** 8,
})

// invokes
const kickerInvoke = new LambdaInvoke(this, 'randomizer-kicker', {
    lambdaFunction: kicker,
})
// 設定数分以降のstepを実行
const mapInvoke = new Map(this, 'mapper', {
    itemsPath: JsonPath.stringAt('$.Payload.mapped'),
    parameters: {
    'Payload.$': '$$.Map.Item.Value',
    },
})
// 次のlambdaの起動時間を設定
const wait = new Wait(this, 'randomizer-wait', {
    time: WaitTime.timestampPath('$.Payload.time'),
})
// 定期メッセージ送信
const messengerInvoke = new LambdaInvoke(this, 'randomizer-messenger', {
    lambdaFunction: messenger,
    payload: TaskInput.fromJsonPathAt('$.Payload'),
})

// step function definition
const definition = kickerInvoke.next(
    mapInvoke.iterator(wait.next(messengerInvoke)),
)

// Step Functionの設定
const stateMachine = new StateMachine(this, 'randomizer-messenger-stepfunction', {
    stateMachineName: 'randomizer-messenger-stepfunction',
    definition,
})

// event rule
new Rule(this, 'randomizer-stepfunction-rule', {
    ruleName: 'randomizer-stepfunction-rule',
    schedule: Schedule.cron({
    minute: '00',
    hour: '15', // 日本時間 00時
    weekDay: 'SUN-THU', // 月曜〜土曜
    }),
    targets: [new SfnStateMachine(stateMachine)]
})

// state machine grants
stateMachine.grantStartExecution(bolt)

// dynamodb grants
dynamodbSchedulesTable.grantReadWriteData(bolt)
dynamodbSchedulesTable.grantReadData(kicker)
dynamodbSchedulesTable.grantReadData(messenger)
dynamodbLastTimesTable.grantReadWriteData(messenger)

終わりに

まだ運用し始めですがこのようにslack botを作成しました。
ただ終わってみるとslack botのスケジュールされたメッセージの設定はScheduling messages を使って対応できるのかなとも思いました。
使ってみたい技術を欲のままに詰め込みましたが、時間があったらもっと便利にSlack boltの機能を使いAWSインフラの引き算をしたいと思います。