はじめに
本記事はBBSakura Networksアドベントカレンダー2022、14日目の記事です。遅刻しました。
こんにちは。BBSakura Networks株式会社の@voice726です。 突然ですが、皆さん社内ナレッジの管理はどうしてますか?
社内ナレッジの管理運用は様々な課題があり、各企業でいろいろな取り組みがなされていると思います。その課題の一つとして挙げられるのが「資料が埋もれてしまう」問題。つまり、せっかくナレッジとして記録してもアクセスされにくくなってしまうという課題があると思います。今回の記事では、この「埋もれてしまう問題」に対して、「お宝発掘くんbot」というアプリを作成することでサルベージできないかと試みたので、その話を紹介します。
作ろうと思ったきっかけ
弊社でも業務にあたり様々なサービスを導入していますが、ナレッジ管理目的としては主にKibelaを利用しています。 各業務に紐づくドキュメント類から、個人が業務で使用した技術のメモ等、様々な記事が投稿されています。
ただ、ナレッジは蓄積されてはいるものの、参照する側としては参照しづらさを感じることもしばしば。特に感じていた課題は以下の2つです。
新しく入ってきた人たちが、過去のナレッジにアクセスしづらい
これは私が入社したばかりのときによく感じていたことですが、なにかの課題に取り組んでいるときに知りたかった情報についての記事の存在に後から気づいて、「こんな記事あったんだ!」と思うことがしばしばありました。 一旦入社してしまえばKibelaの投稿をSlack通知してはいるので、こんな記事が書かれてるんだなーと拾うことはできるのですが、入社前は投稿通知をリアルタイムに見ることができないので(遡るかというとそうでもないし)、過去の記事を目にする機会が極端に少なくなるという課題がありました。
プロジェクトに紐付かない記事(個人での技術メモ)等は浮上しにくい
プロジェクト等に紐付いている記事では各プロジェクトにおける関連ドキュメントとして目次やドキュメント一覧を整理する等してリンクしやすいとは思うのですが、特定プロジェクトに紐付かない、いわゆる「やってみた系」の記事は存在すら認知されないのではと思うこともあります。
そこで思いついたのが、既存の記事をランダムにSlack投稿するbotを作れば良いのではというところでした。この発想は、よくSlack botの入門としておみくじとかそういうネタをよく見かけるのが出発点で、単におみくじ作っても面白くないのでなにか役に立つおみくじは作れないのかと考えていたところ、ちょうど前述のような課題感もあってKibelaの記事をおみくじ的に垂れ流したら面白いんじゃないかなと思ったのがきっかけでした。
ちなみに上記以外の目的としては、KibelaがAPIでGraphQLを採用しており、あまり触ったことがなかったのでその練習をしたいなと思っていたのと、過去の記事を掘り起こすことで社内でのコミュニケーションフックになったらいいなあという妄想もありました。
技術的なはなし
技術選定
ということで、早速実装検討を開始。まず言語は社内でよく使われており、自身も書き慣れていたGo言語を採用。 前述の通り、KibelaはAPIにGraphQLを採用しているので、GoのGraphQLライブラリとして https://github.com/hasura/go-graphql-client を採用しました。
「採用しました」といってもGoでGraphQLをするのは初なので、ググってGitHubのStar数が多そうなものという理由で選定しています。 この辺の選定は雑に済ませてしまったので、もっと便利なライブラリがあるよ!というのがあればぜひ教えていただけると嬉しいです。
このライブラリではクエリを構造体で記述することができます。詳しくは公式READMEを参照していただきたいのですが、簡単なクエリを書くなら以下のように構造体を作ってあげてから
var q struct { Human struct { Name string Height float64 `graphql:"height(unit: METER)"` } `graphql:"human(id: \"1000\")"` }
以下のようにQuery
関数の引数として渡してあげるだけで問い合わせができます。
err := client.Query(context.Background(), &q, nil) if err != nil { // Handle error. }
結果の参照も
q.Human.Name
のようにstructでできるので便利ですね(いずれの例も公式READMEより引用)。
ロジックとしてはシンプルで、記事URL内のIDとなる数字を乱数で生成し、その記事IDで取得してきた記事をSlackのチャンネルに投稿するという形になっています。もう少し詳細に書くと
- 最新の記事のURLを取得
${HOST_NAME}/notes/${\d+}
というフォーマットのURLが取得できるので、${\d+}
を最大値とした乱数を生成- 2.で生成した数値をURLに戻して、記事取得を試みる
- 記事が取得できるまでループさせる(記事削除等でID欠けが発生するので)
- 記事が取得できたらSlackに投稿する
という形になっています。URLを取得しているのは、Kibelaが持っている記事IDは数値ではないので乱数との相性が悪く、URLを利用しています。
さて、アプリを作ったらこれをどこかにデプロイしないといけません。当時弊社ではちょうどGCP導入の機運が高まっており、Cloud runやCloud functionsに社内ツールを移す話も出ていました。
GCPはまったくもって触ったことがなかったので、これも練習にちょうどいいやとGCPのCloud functionsにデプロイすることにしました。
Cloud functionsへのデプロイに関しては公式ドキュメントを参考に、色々試しながらデプロイしました。
また、バッチの定時キックに関しては、DevelopersIOの記事を参考にPub/Sub
で行いました。
ということで、デプロイしてPub/Sub
のスケジュールを調整。
そして完成したのがこちら。
今は1日2回、朝とおやつどきに投稿するようにしています。
ハマったところ
ロジックを書くところまでは各種READMEを読みつつ問題なく進んだのですが、GCPのCloud functionsで色々ハマりました。
- ビルドして単体のバイナリで動かす感じで
main
packageを切ってmain
関数に書いていたらエラーが出た - packageを
main
ではないものにした上で、unexportedの関数名で関数を切ったらunexportedは読み込めないというエラーが出た
これはCloud functionsの仕様なのですが、どうやらクラウド上でmain.go
が動いていて、そこから呼ばれる関数という形で動くみたいなので、exportしておかないとmain.go
が動かしたい関数を読み込めなくて落ちるようです。
後でマニュアルをよく読んでみたら、この辺のことは公式のCloud Functions の関数を作成するに書いてありました。
- Pub/Subをトリガーにして定時キックさせる際は、関数のシグネチャを
func Hoge(ctx context.Context, m PubSubMessage) error
にする必要がある
これも公式のイベント ドリブン関数を作成するに書いてありました。 ドキュメントをよく読みましょうということなんですが、GCPのドキュメントは難しいですね……
作ってみての感想
まず技術的な面でいうと、手軽なCloud functions/GraphQLの練習としては丁度いい規模感でした。 ロジックもGraphQLも複雑ではないし、シンプルに定時実行すればよいので、メインの仕事の合間に勉強として作るには丁度いい規模です。 GCPはとにかく触ってみないとわからないことも多いと思うので、触れる機会が作れたのは良かったなと思っています。
機能面というかお宝発掘くんを実際稼働させてみての感想としては、やはり「こんなドキュメントが社内に埋まってたのか」という発見があるのが楽しいですね。 また、他のメンバーからは昔の記事がサルベージされて「懐かしい」スタンプがついたりと好評(?)なようです。
それ以外での感想としては、何故か社内の出来事と連動して関係のある記事が上がってきて、これ中の人がいるのでは……という気持ちになったり、ランダム性がある投稿が定期的にされるようになったので、なんだか生き物を飼ってそれを観察しているような不思議な気持ちになったりと、思わぬ副次効果もありました。
おわりに
ということで、今回はGo + GraphQL + GCP Cloud functionsでKibelaのAPIを叩いてお宝記事を発掘するbotの紹介でした!
今後としては、今はランダムで拾ってきた記事をそのままフィルタかけずに投稿しているのですが、どうしても記事数が多くなる議事録等は確率を少し下げてみたりの調整機能の追加などを検討しています(会議議事録は投稿しなくていいのではみたいな意見もありましたが、昔の議事録を読むのは懐かしいからいいんじゃない?というコメントもあり、とりあえず確率は下げとこうかなという話に落ち着いています)。また、毎回記事だけを垂れ流しても面白くないので、時々サボってみたりとか、時々ランダムで弊社のShared valuesを投稿してみたりとかなどの遊び心を追加しても楽しいかなと思っています。