😩

Slackワークフロー開発をして見えてきた沼

2024/07/13に公開

Slackのワークフロー開発をする機会があり、色々と試行錯誤し苦労した(沼った)ので、他の人が同じ沼にハマらないよう一助になれば

前提

  • 作ったワークフローの主な機能概要
    • slackチャンネル作成依頼
      • チャンネルを作りたい人がワークフロー起動し、チャンネル作成依頼をする
    • チャンネル作成&招待
      • 管理者が内容チェックし承諾すると、ワークフローがチャンネル作成しそこに依頼者を招待する
  • Slack Enterprise Gridを利用
    • 複数のワークスペースに対して利用できるワークフローを作成

沼一覧

似た概念沼

ワークフローとAppは言葉でみると明らかに異なる概念ですが、実際触っていくと機能的に酷似しています。

ワークフローを作る際、難しいことをやろうとするとAppも新規に利用する必要が出てくるのですが、利用者から見ると、ワークフローもAppも同じように実行できるし、App自体にワークフローの機能も備えており、結局、やりたいことに対してAppだけでやるべきか、ワークフローと組み合わせて両方使ったほうがいいのか、に迷います。

方針としては、以下でいいと思います。

  • ワークフローで出来る範囲ではワークフローでする。
  • ワークフローで出来ないところをAppで賄う

ワークフローは、ワークフロービルダーというツールが用意されているので、コードを書かずともワークフロー自体の開発ができるようになっています。そのため、できるだけワークフローに寄せておくことで、メンテナンスが容易になります。

また、今回の開発で関わった用語(概念)に関しての関係性を絵にまとめました。
※Slack Platformのすべての用語が登場しているわけではないです。


ワークフローとAppが似ている、という表現をしましたが、おそらく、両者はそもそも全く異なるものではなく、内部ではどちらもSlack APIを利用している機能コンポーネントが動いており、表面上の見え方の違いだけかなと想像しています。(なので機能的に似ている)

似た画面沼

関係性の絵の通り、Appは複数のワークスペースに紐づけられます。
そのため、Appの設定には2種類の画面が用意されています。

  1. ワークスペースとは関係のないApp単独の設定画面

  2. ワークスペースごとの設定画面
    基本アプリの設定は大体1枚目の画面に集約されているので、開発中は1枚目に行きたいことが多いのですが、なぜか2枚目に遷移することが手順上多いです。
    これらの画面に見慣れないうちは、そのたびに変えたい設定が見つからずさまよいます。

ちなみに1枚目の行き方をメモしておきます。
Slack APIトップページ -> 右上のyour apps(下の添付参照) -> 該当のアプリをクリック

これだけ見ると簡単ですが、アプリの設定をしていく中でslack公式の手順に書いてる通りに遷移してもこのページにたどり着かず、いつのまにか2枚目の方に遷移したりしてよく混乱します。

API沼

ユーザ名からユーザIDを特定しづらい

チャンネル作成申請ユーザを新規作成したチャンネルに招待する、という機能があります。

チャンネル招待するために、カスタム関数:チャンネル作成は、Workflow:チャンネル作成からユーザ情報を受け取る必要があるのですが、このとき

  • workflowが投げれるユーザ情報が、ユーザ名(@hogehoge というslack上でリプライつけるときの文字列)しか投げれないこと
  • チャンネル招待API conversations.inviteが、ユーザIDを指定しないと行けない
  • ユーザ名からユーザIDを参照するAPIが無い

とういことで、ユーザ一覧を取得するAPI users.list でユーザ一覧を取得しつつ、ユーザ名がマッチするまでループさせてユーザIDを取得する、という泥臭い方法で対応しました。

ワークフローからユーザIDを投げれるか、APIにユーザ名から検索できる機能があればうれしいところ。。。

ユーザ名らしきものが複数ある

users.listのレスポンスのユーザ一覧(membersプロパティ)は以下のようなjson形式です。

[
  {
    id: "USLACKBOT",
    team_id: "Txxxx",
    name: "slackbot",
    deleted: false,
    color: "757575",
    real_name: "Slackbot",
    tz: "America/Los_Angeles",
    tz_label: "Pacific Daylight Time",
    tz_offset: -25200,
    profile: {
      title: "",
      phone: "",
      skype: "",
      real_name: "Slackbot",
      real_name_normalized: "Slackbot",
      display_name: "Slackbot",
      display_name_normalized: "Slackbot",
      fields: {},
      status_text: "",
      status_emoji: "",
      status_emoji_display_info: [],
      status_expiration: 0,
      avatar_hash: "sv41d8cd98f0",
      always_active: true,
      first_name: "slackbot",
      last_name: "",
      image_24: "https://a.slack-edge.com/80588/img/slackbot_24.png",
      image_32: "https://a.slack-edge.com/80588/img/slackbot_32.png",
      image_48: "https://a.slack-edge.com/80588/img/slackbot_48.png",
      image_72: "https://a.slack-edge.com/80588/img/slackbot_72.png",
      image_192: "https://a.slack-edge.com/80588/marketing/img/avatars/slackbot/avatar-slackbot.png",
      image_512: "https://a.slack-edge.com/80588/img/slackbot_512.png",
      status_text_canonical: "",
      team: "Txxxx"
    },
    is_admin: false,
    is_owner: false,
    is_primary_owner: false,
    is_restricted: false,
    is_ultra_restricted: false,
    is_bot: false,
    is_app_user: false,
    updated: 0,
    is_email_confirmed: false,
    who_can_share_contact_card: "EVERYONE"
  },
  {
    id: "Uxxxx",
    team_id: "Txxxx",
    name: "xxxx",
    deleted: false,
    color: "e7392d",
    real_name: "xxxx",
    tz: "Asia/Tokyo",
    tz_label: "Japan Standard Time",
    tz_offset: 32400,
    profile: {
      title: "",
      phone: "",
      skype: "",
      real_name: "xxxx",
      real_name_normalized: "xxxx",
      display_name: "",
      display_name_normalized: "",
(以下省略)
]

この中で、ワークフローから飛んでくるユーザ名と一致しそうな要素がこれだけあります。

  • name
  • real_name
  • profile.real_name
  • profile.real_name_normalized
  • profile.display_name
  • profile.display_name_normalized

どれを見ればいいのか分からなかったため、結局すべてを参照し、いずれかにマッチする、という条件にしました。

機能名とAPI上の命名が違う

API仕様を見てるとteamとかconversationという見慣れない言葉が出てきます。APIのパラメータにも team_idとか、conversation_id というものが登場してきて、一体これは何?どこをみればteam_idが分かるの?と混乱します。

結論から言うと、普段のslackで出てくる右側の言葉で置き換えると分かります。

team→Workspace
conversation→Channel

これも、どこかの資料をみた、というよりapiの挙動からようやく分かった、という感じで沼りました。

ドキュメントと挙動が違う

users.listteam_id はOptional argumentsにあるので、任意のはずですが、指定せず実行するとエラーで怒られます。

Error retrieving users: missing_argument
{
  ok: false,
  error: "missing_argument",
  arg: "team_id",
  ・・・

結局ドキュメントより、挙動を信じるスタイルでやるしかなさそう。
なぜこんなことになってるのかの予想ですが、Enterpise Gridが後から出てきて、team_idがOptionalからRequiredに挙動は変わったが、ドキュメントの更新が追いついてないのではないか、と推測してます。

トークン沼

設定を色々していると、複数のところで、トークンというものが出てきます。
調べてみると6つもあることを知り、絶望しかけます。Token types
それぞれが、異なる役割があるようなのですが、結局やりたいことを実現するために何が必要なのか、を理解するのは非常に苦労しました。

最終的に、今回必要だったのは、User TokenBot Token の2つでした。

本来、必要なscope設定はUser Tokenのみで良かったのですが、Orgへのインストールする手順上Bot Tokenが必須、というエラーがでるため、不要ではありますが、適当なscopeを付与しました。これもなぜそうなってしまうのか、は疑問ではあります。

App利用フロー沼

Appを各ワークスペースで利用できるようにするまでの手順が一筋縄では行きません。例えば、ワークスペースAとBで利用できるようにしようとしたときの手順を、ざっくり説明すると、

  • アプリ新規作成
  • ワークスペースAにインストール
  • Org にインストール
    • Orgオーナーに要承認
  • ワークスペースBにインストール

こう書くと簡単に見えますが、これがそもそも調べて出てくるwebの手順通りに進めてもこうなりません。ポイントは、

  • 最初は、とあるワークスペースにインストールすること
  • 次に他のワークスペースにインストールするために、Orgにインストールすること
    • このとき、Orgオーナーという特別な権限を持つ人に承認して貰う必要あり
  • それが承認されると、自由に他のワークスペースにインストールできるようになる

つまり、例えば組織内の開発者一人だと、権限不足で手順を実行できないことがあるので、slackの管理者と協力が必要になります。今回は、一時的にその権限をもつアカウントを共有してもらって基本私一人で設定を進めることができましたが、そうでもしないと、かなり時間のかかる作業になります。(試行錯誤中は、なんどもワークスペースへのインストールをやり直すので)

非同期沼

これは、実装したコードが悪かった、というのが本質にはなるのですが、
Deno SDKを使ってtypescriptで実装していたのですが、awaitを書き漏らしたことにより

  • local development 上では、正常動作する
  • 本番では正常動作しない。ただし、エラーも起きない

という沼にハマりました。できれば、local developmentでも検知できるよう、挙動を合わせてほしいですね。。。

local development用アプリ多重管理沼

先に結論

困らないためには、
.gitignore内の .slack/app.dev.json は消しておくこと

背景

Slack CLIのslack create は、projectのテンプレートを作ってくれるので、非常に便利な機能です。その中の.gitignoreファイルには .slack/app.dev.jsonが含まれています。

これをそのままにしてコード管理進めると、以下のような問題が起こります。

  • コードをgit repogitory管理する
  • 開発者Aが開発する
    • このとき、local development用のアプリhogeApp(local)と、本番デプロイ用のアプリhogeAppができる
      • slack runとslack deployでできるアプリは別アプリになるため
  • 他の開発者Bがgit cloneして、開発を進める
  • 開発者Bがslack runしたときに、なぜか初期インストールとして動作する
  • そのままインストールを進めると、hogeApp(local)とは別のAppIDの同名アプリhogeApp(local)ができる

ここでの問題は、開発者Bは、開発者Aが作っていたhogeApp(local)をそのまま利用するだけでいいのに、別のhogeApp(local)を管理することになり、かつ、元のhogeApp(local)を管理できなくなっていることです。

2つのAppができる原因

こうなる原因は、.slack/app.dev.jsonをgitで管理していないために起こります。
app.dev.jsonは以下のような構成になっており、

{
  "<Enterprize grid ID>": {
    "app_id": "<App ID>",
    "enterprise_id": "<Enterprize grid ID>",
    "IsDev": true,
    "team_domain": "<Enterprize grid domain>",
    "team_id": "<Enterprize grid ID>",
    "user_id": "<開発者のUser ID>"
  }
}

app_idで、AppのIDを識別しています。
また、開発の流れとして

  • slack createでprojectテンプレートを作成する
    • この時点では、app.dev.jsonは存在しない
  • 初回のslack runでApp をSlack Platformにインストールする
    • インストール完了するとapp.dev.jsonが生成される
    • このときに、AppにIDが自動で割り当てられる
  • 次回以降のslack runでは、app.dev.jsonのapp_idを参照して、対象のAppを(新規インストールではなく)更新する

となります。つまり、app.dev.jsonがないと、slack runしたときに、初回インストールの挙動になってしまうわけです。
そのため、開発者Bが開発する際に、開発者Aと同じ流れで(同じlocal development上のアプリを)開発を続けるためには、app.dev.jsonも必要になるわけです。

よって、最初の結論の通り、app.dev.jsonもgit管理することで、同じ(local)なアプリを他の開発者も利用できるようになります。

補足

  • app.dev.json内のuser_idは、開発者が変わってもそのままで大丈夫
    • ここのuser_idはslack run したときに参照しているような挙動ではなかったので、今のところは気にしなくて良さそうです。この要素が無くても動作しました。(いずれ仕様変更で利用するようになる可能性はある)
  • app.jsonもあるけどこれは気にしなくて良い
    • 同じ原理で、.slack/app.json というslack deployしたときに出来るファイルもありますが、これはデフォルトの .gitignore にないため、開発を引き継いだときに同じ問題は起きません。どうして、app.dev.json は、.gitigoreに加えたんでしょうね?

みんな沼ってないのかな?

こんなに沼があるの、みんな苦労してないのかな?と疑問に思ったのですが、やはりいらっしゃいました。
https://qiita.com/mpyw/items/5a8a90713a56196a55ae
これを見る限り、まだ沼は深そうですね。。。。

いいところ

沼ばかりではない、いいところもあったので、ご紹介。

Slack CLI

Slack CLI というツールによりカスタム関数の開発体験が非常にスムーズです。
最初に、 slack login してしまえば、後は以下の流れで開発を進めれます。

  • slack create:プロジェクトファイルの雛形作成
  • slack run : local development[1] へのデプロイ。修正のリアルタイム反映。
  • slack deploy :本番デプロイ
  • slack log :アプリケーションログ。-t でリアルタイム監視も可能

まずは、slack createで出来た雛形のカスタム関数をそのままslack runして動作確認してみると流れが分かります。

注意としては、slack run によるlocal developmentへのデプロイしたカスタム関数とslack deploy したカスタム関数は別のカスタム関数になります。なので、ワークフローから選択する際はどちらを指定するか意識しましょう。(同じカスタム関数が上書きされるわけでは有りません。)

ホスティングサーバ不要

カスタム関数を動かす場所は、別途ホスティングサーバを用意する方法が紹介されていましたが、今は無くてもよいです。

上述の通り、slack runやslack deployによってSlack Platform上で動作させることができるので、こういったサービスを利用することなく実現できます。

最後に雑感

slack自体が歴史あるサービスで、salesforceによる買収などでビジネス用途への展開など、初期コンセプトから比べると大きく変わってきたプロダクトだと思います。変化のたびに様々な機能/概念を後付していってるがゆえの歪み、技術的負債、が溜まっているのだろうなというのは、想像に難くないですね。好きなプロダクトなので、中の人頑張れ!と、応援しております。

脚注
  1. local development は、イメージとしては 開発環境のようなもので、slack deploy で本番で動かすまえの、開発環境として利用できます。 ↩︎

Discussion