コネヒト開発者ブログ

コネヒト開発者ブログ

データ分析コンペで役に立つ特徴量管理方法と学習・推論パイプライン【コネヒトマルシェLT書き起こし】

こんにちは!MLエンジニアの野澤(@takapy0210)です!

気づけば2019年の営業日も残り20日強ですね。年始に立てた個人的な目標が1/5しか達成できていないことに先日気付いたので、残りの期間で1つくらいは達成できると良いですね、という他人行儀な振る舞いをしたくなっている今日この頃です。

さて今回は、11月5日に開催した(コネヒトマルシェ)でLTした内容の全文書き起こしです。参考資料とあわせてご紹介できればと思います。
全文書き起こしは初の試みなので「ふ〜ん。なるほど〜」ぐらいのお気持ちで見ていただければと思います。

発表資料はこちらです。


f:id:connehito:20191113190809p:plain:w500
f:id:connehito:20191113190823p:plain:w500
f:id:connehito:20191113190902p:plain:w500

Kaggleとは

Kaggleと書いて「カグル」と読みます。日本でも最近は定着してきましたが、Kaggleに参加している方を「カグラー(Kaggler)」とも呼びます。 「The Home of Data Science & Machine Learning」(データサイエンスと機械学習の家)と題されている通り、世界中の機械学習・データサイエンスに携わっている約40万人の方が集まるコミュニティです。最大の目玉とも言えるのは「Competetion(コンペ)」です。
https://www.kaggle.com/

SIGNATEとは

日本版Kaggleというのが一番わかりやすく、特徴としては、開催されるコンペティションのデータは日本の企業から提供されています。コンペティションで高順位をとると、後日表彰式および報告会という形で呼ばれることがあり、入賞すると、賞金が出ます。
https://signate.jp/

f:id:connehito:20191113191145p:plain:w500

(会場の9割強の人が挙手)

参加したことある方に聞いてみたいのですが、特徴量の管理ってどうされてますか?

f:id:connehito:20191113191208p:plain:w500

最初に、僕の実体験を添えてありがちなパターンを2つほどご紹介できればと思います。

例えば、[A, B, C, D]という特徴量があった時に、これらから[E, F, G, H, ...]という形で特徴量エンジニアリングする、というシチュエーションはよくあると思います。

f:id:connehito:20191114102914p:plain:w500

で、一通り特徴量を生成し終えた後に、実験したい特徴量を指定して学習データを作り、学習させます。

f:id:connehito:20191114102942p:plain:w500

一通り学習を終えたところで、ふとこんな事を思うタイミングがありました。

f:id:connehito:20191114103004p:plain:w500

どのような計算で生成した特徴量か探してみると、これが結構大変だったりします。

f:id:connehito:20191114103051p:plain:w500

特徴量生成場所が見つかっても、他の特徴量から段階的に生成されていたりする場合、この根源を探すのも大変です。(もちろん、特徴量の名前を一目で分かることにしておくことは前提としてとても大切だと思います。)

f:id:connehito:20191114103117p:plain:w500

続いて、2つ目のパターンをご紹介します。

f:id:connehito:20191114103206p:plain:w500

これは結構ありがちだと思います(笑)

f:id:connehito:20191114103231p:plain:w500

で、意気揚々とDuplicateしてnotebookの中身を見てみるとこんな感じになっているんですね。

f:id:connehito:20191114103247p:plain:w500

。。。

f:id:connehito:20191114103342p:plain:w500

お気付きの方もいると思うのですが、特徴量生成処理など、同じ計算を再度行う必要がでてきます。これは本当に無駄だと思っていて、どうにかしないとな〜と思っていました。

f:id:connehito:20191114103717p:plain:w500

また、Duplicateを繰り返していくと、気づいたらnotebookファイルだらけになっていた、なんてこともあるかと思います。

f:id:connehito:20191114103811p:plain:w500

最初は「めっちゃ良いモデルが作れた!」と歓喜していましたが、煩雑なnotebook、特徴量管理により、コンペのモチベーションも低下してしまう、なんてことにもなりかねません。

f:id:connehito:20191114104304p:plain:w500

今回は、上記で述べたような実体験から感じていた課題感を、玄人の事例を参考に少しづつ解消できてきたので、みなさんにも少しおすそ分けできたらと思っています。
題して
「データ分析コンペにおいて特徴量管理に疲弊している全人類に伝えたい想い〜学習・推論パイプラインを添えて〜」
という壮大なタイトル(笑)でお話できればと思っています。

f:id:connehito:20191114104414p:plain:w500

こちらがアジェンダです。

f:id:connehito:20191114104442p:plain:w500

いろいろ書いていますが、「玄人の知恵をお借りして、特徴量管理と学習・推論パイプライン構築に取り組んだ結果、めっちゃよかったよ」という話をします。 あくまで主観になりますので、「こんな方法で取り組んだらよかった!」などありましたら是非教えてください。

f:id:connehito:20191114104459p:plain:w500

まずは簡単に自己紹介させてください。

野澤哲照と言いまして、コネヒト株式会社で機械学習エンジニアとして働いています。
会社などでは「たかぱい」と呼ばれています。
Kaggleしたり、野球したり、ラーメン食べたりするのが好きです。

f:id:connehito:20191114104712p:plain:w500

次に特徴量管理方法についてお話します。

これから発表する特徴量管理については、下記記事を参考にさせていただきました。
参考記事:Kaggleで使えるFeather形式を利用した特徴量管理法 - 天色グラフィティ

f:id:connehito:20191114104737p:plain:w500

まずは「特徴量を列ごとに管理する」「メモファイルを作成する」という部分のイメージを共有できればと思います。

f:id:connehito:20191114104811p:plain:w500

「特徴量を列ごとに管理する」とは、下記のように1つの特徴量をtrainデータ, testデータそれぞれ1ファイルずつで管理することをイメージしてください。

f:id:connehito:20191114104922p:plain:w500

「メモファイルを作成する」とは、上記の特徴量を生成する際に自動的に「この特徴量はこうやって生成したもの」というメモファイルを生成することです。

f:id:connehito:20191114105030p:plain:w500

これだけ見ると結構大変そうに感じる方もいると思いますが、1つのスクリプトファイルを実行するだけで実現できます。

f:id:connehito:20191114105053p:plain:w500
f:id:connehito:20191114105112p:plain:w500

以下で具体的な方法についてお伝えできればと思います。

例えばhoge.pyという特徴量生成用のスクリプトを下記のように用意しておきます。
これを実行すると、「各特徴量」と「特徴量メモファイル」が生成されます。

f:id:connehito:20191114105222p:plain:w500
f:id:connehito:20191114105242p:plain:w500
f:id:connehito:20191114105300p:plain:w500

特徴量のメモファイルを作成する箇所に関しては、難しいことをやっている訳ではなく、生成した特徴量の記述がファイルになければ追記していく、ということをやっています。

f:id:connehito:20191114105505p:plain:w500

この特徴量メモはCSV形式で保存しておくとGithubから参照しやすかったりします。
ExcelやNumbersといったアプリケーションからでも綺麗に見えるので、今回はCSVファイルを採用しました。

f:id:connehito:20191114105555p:plain:w500

新しい特徴量を生成したい場合は、hoge.pyにその特徴量生成処理を新しく記述します。

f:id:connehito:20191114105642p:plain:w500

hoge.pyを実行すると新しい特徴量が生成されますが、この時すでに生成されている特徴量の計算はskipしてくれるので、余計な計算時間がかかることはありません。(もちろん、再計算することも可能です)

f:id:connehito:20191114111404p:plain:w500

特徴量をdataframeに読み込む場合は、読み込みたい特徴量名のリストを生成しておき、下記のように記述すれば指定した特徴量データのみを読み込むことが可能です。

f:id:connehito:20191114111439p:plain:w500

この特徴量管理方法を使って何が嬉しかったかと言うと

f:id:connehito:20191114111510p:plain:w500

特徴量管理をすることで下記のようなメリットを享受することができ、「時間的なコスト」を大幅に削減できたのが個人的にはとても嬉しかったです。
データ分析コンペでは特徴量生成だけではなく、学習、推論にも一定の時間がかかります。
そのような中で、特徴量を管理することで余計な計算時間が減るだけでなく、学習→推論のPDCAも回しやすくなったと感じています。

f:id:connehito:20191114111536p:plain:w500

次に、学習・推論パイプラインについてお伝えします。

こちらに関しては、昨今話題の下記書籍を参考にさせていただきました。
参考文献:Kaggleで勝つデータ分析の技術:書籍案内|技術評論社

f:id:connehito:20191114111610p:plain:w500

書籍で紹介されているパイプラインを土台に、下記run_nameをprefixとすることで、一貫性のあるファイルやモデル管理を、意識しなくてもできるように工夫しました。

f:id:connehito:20191114111758p:plain:w500
f:id:connehito:20191114111824p:plain:w500

生成されるファイルは下記のようなものになります。モデルや推論結果のファイルは皆さんの想像通りのものなので、それ以外のファイルについて少しご紹介します。

f:id:connehito:20191114112028p:plain:w500

features.txtは今回の学習に使用した特徴量が記載されたファイルです。

f:id:connehito:20191114112059p:plain:w500

また、param.txtは今回の学習に使用したハイパーパラメータが記載されたファイルです。

f:id:connehito:20191114112125p:plain:w500

shap.pngはshapで計算された可視化イメージを保存したものです。これを元に次の学習の勘所を見つけていきます。

f:id:connehito:20191114112306p:plain:w500

logファイルについては、処理過程を保存したもの(general.log)と、モデルのスコアだけを保存したもの(result.log)の2種類あります。

f:id:connehito:20191114112346p:plain:w500

この学習・推論パイプラインを構築して何が嬉しかったかというと

f:id:connehito:20191114112410p:plain:w500

「この特徴量」と「このパラメータ」を使って学習させたモデルに関して、「各タスクに要した時間」と「各foldと最終的なスコア」を意識しなくても管理できるようになったことです。
これにより、モデルの再現性はもちろん、どの特徴量を使うとスコアが上がった or 下がったということも自然と管理できるようになります。
また、shapの計算結果などを出力しておくことで、次の学習時の勘所も掴むことができます。

f:id:connehito:20191114112431p:plain:w500

最後にまとめです。

特徴量管理とパイプラインを構築することで、様々な「いいぞ!」を感じることができました。一定のイニシャルコストはかかりますが、一度構築してしまえば流用できるので、興味のある方は試してみてください!

f:id:connehito:20191114112521p:plain:w500

また、「他にこんな良い方法もあるよ!」といった知見・意見あれば、是非教えていただけると嬉しいです!

ご清聴ありがとうございました!

f:id:connehito:20191114112552p:plain:w500

発表資料全体をご覧になりたい方はこちらをご覧ください。

以上、当日の書き起こしでした。

今後、コネヒトのMLチームとしては推薦システムに取り組んでいく予定です。
取り組みから得た知見など、積極的に発信していきたいと思っていますので、楽しみにしていてください!


よろしければ、今までのキャリア、コネヒトでの業務などをまとめておりますのでこちらもご覧いただけたら嬉しいです。 www.wantedly.com

CakePHPの国際カンファレンス「CakeFest 2019」に協賛&参加しました!(資料まとめ)

こんにちは!サーバーサイドエンジニアの @fortkleです!
今回は、先週末に開催されたCakePHPの国際カンファレンスである「CakeFest 2019」に参加してきたのでレポートしたいと思います。

CakeFest 2019

CakeFestはPHPのフレームワークであるCakePHPの国際イベントで、セミナー2日、カンファレンス2日の計4日間に渡って開催されるイベントです。 開催地は事前に投票によって決められており、今回は初めての日本開催となりました。 私は後半のカンファレンスから参加しました。

cakefest.org

会の雰囲気

f:id:fortkle:20191110162031j:plain
後半2日間のカンファレンス会場はSmartNewsさんのオフィス

公式発表はないのであくまで推測になりますが、全体でいうと100名弱ほど、そのうち半分ほどが海外からの参加者という風に非常に国際色豊かなイベントでした。
このような形式のカンファレンスに参加したのは初めてだったのですが、堅苦しい感じとは真逆でアットホームな雰囲気の温かいカンファレンスでした。

f:id:fortkle:20191109172102j:plainf:id:fortkle:20191109171934j:plain
会場で提供されたドーナツと海外を感じるバナナの箱置き!

今回、微力ながら協賛もさせていただきました。
コネヒトのプロダクトはCakePHPに支えられているものばかりなので、少しでもCakePHPコミュニティの発展を支援できたのであれば幸いです!

当日のセッション

カンファレンス1日目の夜に行われたLTを除くセッションについて、すでに公開されている資料をまとめてみたので参考にしてみてください! ※ 敬称略

特に、José RodríguezさんによるCakePHPのまだあまり知られて機能の発表金澤さんによる滑らかなCakePHP3への移行についての発表などは知らないことも多くまさに"知見"という印象だったのでまた見返したいと思います。もちろん、弊社CTOの伊藤によるCakePHPではじめるOSSの発表もぜひご覧ください!(宣伝)

f:id:fortkle:20191110163556j:plain
弊社CTO伊藤も発表しました(タイトル: Let's start your first OSS with CakePHP )

Day1

※ 一部の資料はCakePHPのSlackチャンネルでのみ共有されていたため当該Slackチャンネルの投稿のリンクを記載します。*1

タイトル/スピーカー 資料公開先
A safer and more helpful CakePHP in 4.0 / Mark Story https://www.slideshare.net/markstory/safer-more-helpful-cakephp
The CakePHP features I wish you were using more / José Rodríguez https://cakesf.slack.com/archives/C172CS4TE/p1573272016104200
Consider a smooth upgrade to CakePHP 3 / Yuki Kanazawa https://speakerdeck.com/ykanazawa/consider-a-smooth-upgrade-to-cakephp-3
12 Factor CakePHP Applications - The Remix / Jose Gonzalez https://speakerdeck.com/josegonzalez/12-factor-php-applications-the-remix-1
Test-driven development to avoid painful of test code / KAZUKI HIGASHIGUCHI https://speakerdeck.com/hgsgtk/test-driven-development-to-avoid-test-painful
CakePHP & Spatial Big Data - Visualising Spatial Data & Metrics over 70 Billion+ rows / Daniel Voyce https://speakerdeck.com/voycey/cakephp-and-spatial-big-data-visualizing-70-billion-rows-of-data
Working with Database Replication / Tadahisa MOTOOKA https://speakerdeck.com/motooka/working-with-database-replications-in-cakephp

Day2

タイトル/スピーカー 資料公開先
Beyond unit testing: How to make your applications more reliable / José Rodríguez https://cakesf.slack.com/archives/C172CS4TE/p1573354079126300
GraphQL, CakePHP & JWT: A Fast & Secure Redemption from REST Hell / Prosper Otemuyiwa 諸事情により発表なし
Baking with Vue.js / David Yell https://docs.google.com/presentation/d/1bgilFVNRtvhp9KyCLVWMMKtlS-Q39rx07AXCOpf67G4/edit#slide=id.p
CakePHP with Habitat - Build once, deploy everywhere / Graham Weldon 諸事情により発表なし
Building interactivity with websockets / Wim Godden https://www.slideshare.net/wimg/building-interactivity-with-websockets-191944043
Life after CakePHP / Andrej Griniuk https://docs.google.com/presentation/d/1Hlrs_T-rZ_7LNy6iQh-S_Ff8Ca6O8HlThYfjg6D8GO4/edit
Let's start your first OSS with CakePHP / Sho Ito https://speakerdeck.com/itosho525/lets-start-your-first-oss-with-cakephp

最後に

今回のCakeFestは、Github上でしかやりとりをしたことがなかったCakePHPのコアコミッターの皆さんと直接コミュニケーションが取れたことがとても貴重な機会でした。
運営の皆様、会場提供してくださったDMM.com様・スマートニュース様、そして当日参加された皆様、本当にありがとうございました。そして、お疲れ様でした!

*1:CakePHPのSlackチャンネルは誰でも自由に入れますし、日本語話者向けのチャンネル#japaneseもあります! 参加方法はこちら https://twitter.com/fortkle/status/1193390451883040768

Connehito Marché vol.6 〜機械学習・データ分析市〜 を開催しました!

f:id:taxa_program:20191108125429p:plain

こんにちは。MLエンジニアの野澤(@takapy0210)です!

11月に入っていよいよ寒くなってきましたね。
寒いといえば、毎朝洗濯物を干すのが辛くなる季節でもあります。
このような季節も影響し、我が家ではドラム式洗濯機のデプロイが検討されています。もしオススメのドラム式洗濯機があれば教えてください!!

さて今回は、先日無事に開催することができました、「Connehito Marché vol.6 〜機械学習・データ分析市〜」の様子や、LTの内容などを簡単にご紹介できればと 思います!
(嬉しいことにLT枠もオーディエンス枠も満席となり、大盛況で終えることができました!)

connehito.connpass.com

今回のテーマ

今回は第6回目ということで「機械学習・データ分析」をテーマとして開催しました。

抽象的なテーマだったため、LT内容含めてどのような方々が参加してくださるのか、非常に楽しみでした!

ちなみにコネヒトマルシェでは毎回テーマを変えており、過去には下記のようなテーマで開催しています。

今回は、下記3つのお願いお伝えした上で、スタートさせていただきました!

f:id:taxa_program:20191108130206p:plain

LT内容

データ分析コンペにおいて特徴量管理に疲弊している全人類に伝えたい想い

by @takapy0210

概要

  • データ分析コンペをnotebookだけで挑むといろんなツラミがある
  • 特徴量管理とパイプライン組むとちょっと良くなった
    (初っ端から時間オーバーしてすみませんでした。。。)

SageMakerで構築する価格推定システム

by @0xb5951さん

www.slideshare.net

概要

  • 機械学習でなんかやってみてよと言われてやった
  • 依頼がいくらで成約するかを推定
  • SageMakerを用いて手早く実装した
  • 今日リリース予定だったが、リリースできず

社内での円滑なデータ分析のために

by @yu__ya4さん

概要

  • データ分析業務のポジティブな社内政治のお話
  • 直接関係のないPJなどにも顔をだしたり、ランチに行ったりコミュニケーション取るのがめっちゃ大事(何やってるかわからない人にならないためにも)
  • 結果として社内外でのプレゼンスが向上しいろんなことが円滑に進んだ

初めて機械学習PJをやってみて得た知見

by @yaginuuunさん

概要

  • 自社サービスにレコメンドエンジンを入れた
  • 簡単でも良いので、まずは結果を見える形にする
  • Kaggleは役に立つ

SIGNATEの練習問題コンペで 57位までスコアを上げた話

by @shnagaiさん

概要

  • 機械学習勉強しはじめて初めて自分でモデルを作った
  • あとで分析結果などを見返したい時のために、メモを取るのが大切
  • ドメイン知識をフル活用して、スコアを向上させた(57位/1748)

BigQueryいいよね!って話をしようと思ったらBigQueryより早いAzure Synapseが出た

by @YASU11552288さん

概要

  • BigQuery良いよね、という話をしようと思っていたら、75倍速いAzure Synapseが出たので、急遽内容を変更
  • Azure Synapseはインスタンス単位の課金でインデックスのチューニングが必要
  • 導入を考えるとすこし運用コストが高そう

日本語学習済みモデルについて

by @TwYamatさん

docs.google.com

概要

  • 今はBERT が微笑む時代
  • 学習済み言語モデルのGood / Badポイント
  • 日本語学習済みモデルを使用すれば、様々なNLPタスクに取り組める一方で、日本語の学習済みモデルが少ないので、適応するには制限がありそう

Meta Kaggleを覗いてみた

by @IshizakiYukoさん

概要

  • Kaggleって本当に流行っているか、Meta Kaggleを覗いて調べてみた
  • 新規ユーザーは右肩上がりの一方、コンペにSubmitしているユーザーで分析してみると、鈍化している
  • 最近は画像コンペが増えている
  • kaggleの沼にはまろう

NGBoost論文読んでみた

by @ml_taroさん

概要

  • kagglerにも人気かつ、つよつよAndrewNg先生が共著者だったので、読んでみた
  • NGBoostは出力の不確実性を確率値として出力する
  • 自然勾配(勾配が大きく変化する場所は慎重に&勾配があまり変化しない場所は大胆に)を用いることで、最適化を効率的にしている

NLP Beginner BERTを試す

by @ktr_wtbさん

概要

  • BERTでkaggleの過去コンペを解いてみた
  • BERTなら特になにもしなくてもそこそこ良いスコアが出るかと思いきや、そんなことはなかった
  • Fine Tuningのやり方を工夫することが大事

SQLベースのMLパイプライン

by @hatuninaさん

概要

  • データセットの作成と特徴量エンジニアリングをSQLで
  • SQLをベースにすることで、使い回しやすい、共有しやすいなどのメリットがある
  • 一方で、DBが混んでいるとデータ作成がボトルネックになりがち

競艇の順位予想をしてみた

by @wakame1367さん

docs.google.com

概要

  • 競艇のデータセットは公開されているが、表データっぽいtxtデータで、データクレンジングに8割の労力を割いた
  • LightGBMでランク学習させた
  • ドメイン知識が少なく、特徴量エンジニアリングがあまりできなかった。
  • 実際に予測してみたところ、勝率は。。。

懇親会

今回はお寿司とお酒を手に乾杯しました。

参加者の方々で質問しあったり、LTの感想を伝えたりしていて、終始楽しそうな雰囲気で幕を閉じることができました!
個人的には、Twitter上で知っている人と顔を合わせてお話できたのがとても嬉しかったです!

f:id:taxa_program:20191108115122j:plainf:id:taxa_program:20191108114935p:plain

最後に

というわけで、当日の様子をお届けしました!

拙い司会・進行でしたが、最後までお付き合い頂きありがとうございました!
私自身もとても楽しく参加させていただきました!

次回開催時期・テーマなどはまだ決まっておりませんが、今回のマルシェの振り返りを社内で実施したときに「今回盛況だったから、次回も機械学習でいこうよ!(いこう)」という話がチラっとあがっていたので、もしかしたらもしかするかもしれません(笑)

参加していただいた皆さま、ありがとうございました!
また次回のマルシェでお会いできたら嬉しいです!

iOSでWebThread関連のクラッシュが急増した件

こんにちは!エンジニアの柳村です。

9月末頃からママリのiOSアプリでWebThread関連のクラッシュが増加し、ときどき爆増するといった事が起こりました。

f:id:yanamura:20191025172637p:plain

クラッシュレポートを調べてみると以下の3つクラッシュが多数発生していました。

Crashed: WebThread
0  JavaScriptCore                 0x1a583e14c WTFCrashWithInfo(int, char const*, char const*, int) + 20
1  JavaScriptCore                 0x1a5d21b3c JSC::Interpreter::prepareForRepeatCall(JSC::FunctionExecutable*, JSC::ExecState*, JSC::ProtoCallFrame*, JSC::JSFunction*, int, JSC::JSScope*, JSC::ArgList const&) + 742
2  JavaScriptCore                 0x1a5f808c0 JSC::boundFunctionConstruct(JSC::ExecState*) + 588
Crashed: WebThread
0  JavaScriptCore                 0x19407aad4 <redacted> + 20
1  JavaScriptCore                 0x1948013dc <redacted> + 746
2  JavaScriptCore                 0x194a80b50 <redacted> + 608
Crashed: WebThread
0  libGPUSupportMercury.dylib     0x1d8145fe4 gpus_ReturnNotPermittedKillClient
1  AGXGLDriver                    0x1dc7f2ed8 (シンボルが不足しています)
2  libGPUSupportMercury.dylib     0x1d8146fac gpusSubmitDataBuffers
3  AGXGLDriver                    0x1dc7f4404 (シンボルが不足しています)
4  IOAccelerator                  0x1be209884 IOAccelContextFinishResourceSysMem + 64

見ての通り、どれもなるほどわからんというエラーですね・・・

しかも、アプリやiOSのアップデートとは関係なく急増するという不思議な状況でした。

原因の分析

ママリのiOSアプリではいくつかWebViewを使っている箇所があり、どこのWebViewが原因かをまず洗い出す必要がありました。

ママリのiOSアプリではFirebase Crashlyticsを利用しており、Firebase Crashlyticsだとクラッシュレポートとログが一緒に見れるのでどの画面でクラッシュしたか判断するのに役に立ちました。

調べた結果は起動直後にクラッシュしているユーザーがいたのでトップページのどこかであるということに絞れました。

しかし、トップページにWebViewは使った心当たりはなかったので、XcodeのDebug View Hierarchy を使ってどこでWebViewが使われているか調査しました。

その結果Google Mobile AdsのDFPBannerView内でUIWebViewが使われていることがわかりました。

f:id:yanamura:20191025175922p:plain

対策

まずはSDKが古かったので最新(7.50.0)にしてみましたが効果はありませんでした。。

そこでGoogle Mobile Adsのフォーラムを見たところUIWebViewをWKWebViewに変えることができるとの情報が得られたのでやってみました。

以下のようにinfo.plistにgad_preferred_webview というKeyとwkwebview というValueを設定するとWKWebViewに変えることができました。

<key>gad_preferred_webview</key>
<string>wkwebview</string>

これをリリースしたところ、多数発生していたクラッシュがWKWebViewにしたバージョンでは発生しなくなりました!

まとめ

急にWebThread関連のエラーが増えて、Google Mobile Adsを使っている場合はinfo.plistを変更してWKWebViewに変えるとよいです。

UIWebViewはiOS13ではdeprecatedになっていますし、iOS13でUIWebViewでwindow.confirm を使うと挙動がおかしかったりするということも弊社で確認しているので、UIWebViewは捨て去ったほうがよいかと思います。

Atomic Designを実践して得た学びと失敗

🙋‍♂️エンジニアの@dachi_023です。約4ヶ月ぶりに記事を書きます、がんばります。

この記事について

コンポーネントやAtomic Designについて書いています。ここではUIデザインのフローに関するAtomic Designの実践ではなく、開発(実装)のフローにはめ込んだ場合にどうすべきなのか、というお話をしています。

コンポーネントとAtomic Design

ReactやVueをはじめとするライブラリのお陰でフロントエンド開発に「コンポーネント」という考え方が浸透した今日この頃ですが、そんなコンポーネントの設計についての話なんかをしているとよく現れるのが今回の主題に挙げている「Atomic Design」です。Atomic Designはデザインシステムを効率よく作成するための手段のひとつですが、その中に登場するコンポーネントを5階層(Atoms, Molecules, Organisms, Templates, Pages)に分類するという手法がコンポーネント指向なライブラリと相性が良く、設計や実装に取り込むことで今までよりもエンジニアとデザイナーの認識も揃えやすそうだよね〜、といった理由などにより次第にメジャーな手法となっていきました。*1

ちなみに、Atomic Designに関する解説などはしないのでよくわからないけど気になるなという方は下記のweb書籍、もしくは「Atomic Design」で検索して出てきた解説記事などを読んでいただくことをオススメします。 atomicdesign.bradfrost.com

直近1年くらいは特に「Atomic Designを参考に設計・実装しました」系の記事やスライドを見る機会も増えてきたのですが、結構ハマりどころとか似てるんじゃないかな?と思ったので溜まった学びを本記事にまとめることで誰かの役に立てれば良いなと思っています。

*1:日本だとAbamaTVさんのAtomic Design powered by React @ AbemaTVからAtomic Designが流行っていったのかな?と思っています

続きを読む

CakePHP3用のMaster/Replica接続管理プラグインをOSS化しました

こんにちは、サーバーサイドやっております 金城 (@o0h_)です。
なんとな〜〜〜くKindleのライブラリを見ていたら、スキエンティアがあって「とても美しくて良い話だなぁ。。。」と思った次第です。

スキエンティア (ビッグコミックススペシャル)

スキエンティア (ビッグコミックススペシャル)

たまに読み返したいな。

さて、掲題のとおりですが、以前にママリのマスプロモーションを実施した際の負荷対策として作成した機構をプラグインとして公開しました。
・・・という書き出しで以前に書いたのが、CakePHP2.x用のMaster/Replica接続管理プラグインです。

tech.connehito.com

それから暫く経ちましたが、この度CakePHP3用の同様のプラグインを公開しました。

packagist.org

この記事では、以下の3点について紹介したいと思います。

  1. プラグインの利用方法についての簡単な説明
  2. 設計について
  3. 「CakePHP3.xのプラグイン」を公開する際に工夫したこと

①利用方法について

CakePHP2用のプラグインと同様に、「複数の接続を管理する」「単一のモデルからそれらを任意に使い分けられる」ことを目的としています。例えば「参照しか走らないリクエストは参照用DBに接続して、更新系はマスターDBに接続する」といったような使い方を想定しています。

簡単に利用方法を紹介します。

  1. Pluginをcomposer installで導入する
  2. 「複数の接続先」をconfig(デフォルトではconfig/app.php)に書き込む
  3. (Controller等で)必要に応じて接続先情報を変更するメソッドを実行する

1. Pluginの配置

Composerによるinstallに対応しています。

composer require connehito/cakephp-master-replica

単体クラスの名前空間解決による読み込みだけで十分なので、Pluginのロード等の処理は必要としません。

2. 接続情報の設定

ConnectionManagerに喰わせる設定を変更します。
標準的な構成では、これは config/app.php にある Datasourcesの内容となります。

通常のConnectionクラス用の設定と同様に記述したあとに、「接続先の差分」を roles に書き込んでください。 共通する接続情報を記述した上で、それぞれの接続先ごとに異なる部分を記述するようになります。

例えば、以下のような3つの接続先を扱いたいという需要があったとします*1
(MySQLを使うものとします)

  1. master
    • Host: db-host
    • DB: app_db
    • Username: root
    • Password: password
  2. replica(1)
    • Host: db-host
    • DB: app_db
    • Username: read-only-user
    • Password: another-password
  3. replica(2)
    • Host: replica-host
    • DB: app_db
    • Username: read-only-user
    • Password: another-password

これに「master接続だけ利用する」場合は、CakePHP標準のConnectionを利用して以下のように記述できるかと思います。

<?php
// config/app.php
use Cake\Database\Connection;
use Cake\Database\Driver\Mysql;

return [
    'Datasources' => [
        'default' => [
                'className' => Connection::class,
                'driver' => Mysql::class,
                'persistent' => false,
                'host' => 'db-host',
                'username' => 'root',
                'password' => 'password',
                'database' => 'app_db',
                'timezone' => 'UTC',
                'flags' => [],
                'cacheMetadata' => true,
                'log' => false,
                'quoteIdentifiers' => false,
        ],
    ],
];

これを、master + replica + replica2という構成に対応できるように書き換えてみます

<?php
// config/app.php
use Cake\Database\Driver\Mysql;
use Connehito\CakephpMasterReplica\Database\Connection\MasterReplicaConnection;

return [
    'Datasources' => [
        'default' => [
                'className' => MasterReplicaConnection::class,
                'driver' => Mysql::class,
                'persistent' => false,
                'host' => 'db-host',
                'database' => 'app_db',
                'timezone' => 'UTC',
                'flags' => [],
                'cacheMetadata' => true,
                'log' => false,
                'quoteIdentifiers' => false,
                'roles' => [
                    'master' => [
                        'username' => 'root',
                        'password' => 'password',
                    ],
                    'replica' => [
                        'username' => 'read-only-user',
                        'password' => 'another-password',
                    ],
                    'replica2' => [
                        'host' => 'replica-host',
                        'username' => 'read-only-user',
                        'password' => 'another-password',
                    ],
                ],
        ],
    ],
];

このように、classNameをMasterReplicaConnectionに書き換えた上で「差分だけroles に書く」ことによって全ての接続を扱えるようになります。

3. 接続の切り替え

デフォルトでは、 master という名前の設定を用いて接続されます。
switchRole() というAPIを用いて簡単に接続先を切り替えることが可能です。

例えばTableインスタンスが手元にある場合は、Tableインスタンスを介してアクセスするのが手軽だと思います。

<?php
$this->UsersTable->getConnection()->switchRole('replica');

それ以外の場合は、ConnectionManagerを利用することになるでしょうか。

<?php
use \Cake\Datasource\ConnectionManager;

ConnectionManager::get('default')->switchRole('replica2');

比較的容易にアクセスができるので、リトライ機構の導入や局所的にアクセス先を変更したいというニーズにも対応が簡単です。

4. 発展的な利用例: CQRS的なもの

多くのWebアプリケーションで、「書き込み・更新を伴うページやエンドポイント」「参照しか用いないもの」というのが比較的はっきりと分かれるのではないでしょうか。
エンドポイント単位をいわゆるCommand/Query的に分類し、それぞれで接続先を設定できると実用できる場面が広がるように思います。

社内のプロダクトでは、Routingの設定と絡めて「どっちにつなげるか」を管理できるようにしました。 例えば「商品の個別ページ」として、 item/show というエンドポイントがあり、ここではDBの更新が走らないものとします。item/edit では情報の更新を行うため、DBの更新を行います。

まず、 routes.phpでエンドポイント個別の設定として独自のオプションであるreadOnly フラグを立てます。

<?php
//routes.php
$routes->scope('item/', ['controller' => 'Items'], function (RouteBuilder $routes) {
    $routes->connect(
        'show',
        [
            'action' => 'show',
            '_method' => 'GET',
        ]
    );
    $routes->connect(
        'edit',
        [
            'action' => 'edit',
            '_method' => 'POST',
            'readOnly' => false,
        ]
    );
});

ルーティング情報と一緒に渡ってきた内容に対して、 AppController::beforeFilter() などの「最初の方に通る共通処理」で設定を反映させます。

<?php
/**
 * routesからパラメータを読み取り、接続先のDBを切り替える
 *
 * @return void
 */
private function setDefaultDbRole()
{
    $readOnly = $this->getRequest()->getParam('readOnly', true);
    $dbRole = readOnly ? 'replica' : 'master';

    /** @var MasterReplicaConnection $connection */
    $connection = ConnectionManager::get('default');
    $connection->switchRole($dbRole);
}

これだけで、「明示的にreadOnlyフラグを折ったエンドポイント以外はレプリカを見る」機能が実現されました。

②設計について

ここからは、実装面の話を紹介させていただきます。

CakePHP3の「ORM」「DB接続」

CakePHP2と比べて3.xは「ORM周りの機能が大幅に強化・変更された」というのは主要なトピックの1つですが、ORM/Database周りに関する内部構造も複雑になっています。

主要な登場人物として「ORM」「Datasource」「Database」の3レイヤーが出てきます。
ざっとまとめると以下のようになります。

f:id:o0h:20191014023707p:plain f:id:o0h:20191014023609p:plain

通常のアプリケーション開発を進めている時に直接触るのはORM層のみで、ほぼ事足りると思います。
この記事では詳細については割愛します*2が、「Connection」と「Driver」に着目してください

Driverクラスは、ClassDocを見ると以下のようなサマリーがついています。

/**
 * Represents a database driver containing all specificities for
 * a database engine including its SQL dialect.
 */

(cakeの中では)これが最もDBに近い層で、PDOインスタンスを保持します。
Connectionクラスはこれらを使役するクラスで、Driverを生成・取得し保持します。
今回作成したかったのは「複数の接続先を管理する」機能なので、「接続を切り替える」場としてConnectionクラスを改変することにしました。

接続の生成と管理・切り替え

PHP上で実際に「DBに接続している」のは「PDOインスタンスを生成(保持)している」箇所になります。
当プラグイン = Connectionクラスでは、「クラスのインスタンス化時に、注入されている全ての設定に応じた PDOインスタンス(との仲介役であるDriverインスタンス)を生成し、保持する」という戦術を取りました。

CakePHP2用プラグインでは、Datasource\Databaseレイヤーへのハックを行いMysqlを前提としていました。そのためにPDOとも密結合になっています。
これに対してCakePHP3用のプラグインでは、接続管理が抽象化されたことで、過度な設計を必要とせずに接続用のDBドライバは付け替え可能です。その点に注目しました。

デメリットとしては、例えば「replica接続しか使わないのにmaster接続のインスタンスも生成されてしまう」といったオーバーヘッドがあります。
実際、当初は「呼び出された時点でPDOインスタンスを作り、不要になったら破棄する」という方法を実現できるか?と模索もしました。
生成自体は遅延読み込み的に生成すれば実現できそうな気はします。問題は「破棄する」タイミングです。トランザクション管理など、どうしても「DriverないしConnectionから向き合わなければいけない関心事が増える」ことで、実装上の複雑さが増しそうな懸念がありました。また、「呼び出されるたびに再接続する」ことで生じるDB接続確立は大きな負担になりそうです。

総合して、「接続するのは最初に済ませてしまう」「インスタンスを内部に保持し続ける」ことで得られる、システムリソース的にもアプリケーションコード的にも魅力を感じました。 また、これらの機能をたった70行ちょっとの単一クラスで実現できたというのは「簡潔な記述ができた」成果だとも言えるのではないでしょうか。

③「CakePHP3.xのプラグイン」を公開する際に工夫したこと

当プラグインは、元々プロダクトコードの一部として実装していたものをライブラリとして切り出したものです。
実際のプロダクトなら「コントローラーもデータベースもすべてが揃っている!」テスト環境があるのですが、スタンドアロンなライブラリでは、そうも行きません。環境構築を含む事前準備は本質的ではないストレスになると考えています。もしテストがすぐに実行できれば、どれだけ開発体験が良くなるか・・・・

この問題は個人的には毎回頭を抱える部分なので、自分なりに「こうしたら楽かな?」と思える工夫をいくつか施してみました。
なお、今もなお試行錯誤している部分なので、ぜひ皆さんのご意見も聞いてみたいです。

docker-composeの梱包

このプラグインは理屈上はDBを使わなくても実装したロジックの内容を検査が可能だと思います。しかしながら、DB周りの機能を提供するものでもあるため、テストの時点で「実際にDBに触ってみる」事ができると安心です。
そこで、docker-composeを用いてPHP+MySQLの開発土台を配布できるようにしました。
(sourceはコチラ)

これによって、例えばPhpStormユーザーであれば手軽にIDE上からのテスト実行を提供できることになります🎉

  • stormの設定
    f:id:o0h:20191014032552p:plain
    f:id:o0h:20191014032654p:plain
  • 実際にテストを実行している光景🗻
    f:id:o0h:20191014032937p:plain

(localの)テスト時にローカルからプラグインを読み込む

Composer配布前提のライブラリなので、最終的にはpackagist経由で喰わせることになります。しかし、開発中はわざわざpushするというのは面倒な話です。
そこで、 docker-composeのvolumesと(テスト実行側アプリの)composer.jsonを組み合わせることで、「テスト実行時にローカルからライブラリを読み込む」ようにしました。

ホストからDockerコンテナにライブラリのsrcを喰わせる

全体構成としては

  • src: ライブラリ本体
  • tests/test_app/composer.json: テスト実行アプリのcomposer.json
  • tests/test_app/docker-compose.yaml: テスト実行アプリのdocker-compose

となります。

docker-compose上では、次のようにして「PJ全体は /app に喰わせる」「それとは別に、 /dist 上にライブラリの本体とパッケージ情報(composer.json)を喰わせる」ようにします。

services:
  test-app:
    volumes:
      - ../../src:/dist/src
      - ../../composer.json:/dist/composer.json
      - ../..:/app
ローカルのパスをレポジトリとして設定する

composer.json上には、ローカルのパッケージを参照させるようにレポジトリ情報を追加します

"repositories": [
      {
          "type": "path",
          "url": "/dist"
      }
  ],

これで、 app/tests/test_app/vendor/connehito/cakephp-master-replica/dist を指すシンボリックになります🎉

CakePHP4を見据えて・・

次期バージョンであるCakePHP4は、すでにβ4まで進んでおり、段々と全貌が見えてきています。
CakePHP3との互換性については意識されているということで、cakephp-master-replicaプラグインにおいても可能であれば低コストに移行したいと考えています。

そこで、4.xから入る規約等への対応を実施しました。
主だったところでは以下の4点です

  1. cakephp-codesnifferを利用してPSR-12対応
  2. 厳密な型チェック(strict_types=1)
  3. 引数・戻り値の型宣言
  4. PHPStanの対応レベル引き上げ(Lv5)

strict_typesの宣言漏れについてはcodesnifferでチェックできるので、そこまでナーバスになる必要もありません(CIでコケるため)。
PHPStanのレベルについては「コーディング規約」とは異なるようにも思いますが、本体の動向に追従しようというものです。
最終的にはstableのリリースを待って移行ガイド他ドキュメントをチェックし対応することになりますが、今の時点での「変更点」としてはこんなものだと思っています。

最後に

ごく僅かなコードで機能を実現できたのは、「フレームワークに乗っかった旨味だ」と感じています!
また、実現したいコードを如何にしてフィットさせるか?という観点でのコードリーディングは、モチベーションも湧きやすく良いものです。今回の機構の開発にあたり、自分なりにCakePHP3のORM,Databaseレイヤーについて理解が深まりました。

PDOインスタンスを複数持たせるというアイディア自体についても、結果的にIlluminateの接続管理でも同様の手法を取っているものです。・・・もっとも、これは実装してから気づいたので、「もっと早く見ればよかった」と項垂れもしました。が、個人的には「悪くないやり方と思っていいのかな〜〜」と、同時に自信も深められたと言えます✨Illuminateの方が高機能な実装をしているようにも思うので、こちらもまだ改善の余地がありそうです。

今回触れた「master replica切り替え機構」の設計や詳細については、もっと詳しい資料が社内にございます🌅
コネヒトではサーバーサイドエンジニアを募集していますので、是非お気軽に遊びに来てくださいね! www.wantedly.com

また、11月に開催されるCakeFestでは弊社CTOも登壇しますので、応援してください!!

*1:実際にこんな構成が使いたいか?は別として、あくまで「こういう事ができるよ」というのを説明するための内容です

*2:手前味噌ですが、以前にこの辺りを調べてみた記事があります。よろしければ御覧ください https://cake.nichiyoubi.land/posts/10-orm-database/

AWS × slackを用いたDDL自動実行フローを構築しました

こんにちは!MLエンジニアの野澤(@takapy0210)です。

10月から軽減税率が始まりましたね。みなさんの身の回りで混乱は起きていませんでしょうか?
そんな中、軽減税率に関するこんな記事を見ました。専門家の人たちでも判断に困る事例があるようなので、難しいですね。

さて、本日はAWS × slackを使って、DDLの自動実行フローを構築した話をできればと思っています。

目次

DDLって何?

リレーショナルデータベースを対象として、テーブルなどの構造を制御することができる言語です。 「CREATE」「DROP」「ALTER」などが書いてあるアレです。

コネヒトでは、SchemafileでDBスキーマを管理することができるRidgepoleというツールを用いています。
このRidgepoleの実行環境はコンテナ化されており、AWS ECS上のサービスとして稼働しています。

従来のフロー

DBは開発環境(以下、dev環境)本番環境(以下、prd環境)に分かれており、dev環境での実行は開発者、prd環境での実行は権限のあるオペレーターに依頼して実行してもらう、というフローになっていました。

下記が詳細な手順です。

  1. local環境で開発(Schemafile)
  2. Github RepositoryにPR
  3. Githubでmaster merge(dev環境にECSデプロイが走る)
  4. 実行環境のインスタンスにSSHログイン
  5. dry-runでDDLの内容が合っているか確認(dev環境)
  6. dry-runの内容に問題がなければdev環境でridgepole(DDL)の実行
  7. 最新のコミットに対してタグをpush(prd環境にECSデプロイが走る)
  8. オペレーターに依頼し、prd環境でdry-runの実行
  9. dry-runの内容に問題がなければprd環境でridgepole(DDL)の実行

ごちゃごちゃ書きましたが、煩雑な雰囲気だけでも伝わって頂ければと思います。

今回は上記の 4, 5, 6, 8 の部分を自動化しましたので、次章以降で詳細をお伝えできればと思います。

新・自動化フロー

自動化のアーキテクチャは下記のようになっています。 dry-run実行フローとDDL実行フローの2パターンに分けて説明します。

dry-run実行

f:id:taxa_program:20191003172131p:plain
dry-run実行時

図を見ていただくと分かりやすいと思うのですが、ECRのimage更新を検知して、Step Functionisが起動し、Fargate Taskでdry-runスクリプトを実行し、結果をslackに通知してくれます。

slackへ通知するメッセージは、下記のようにdry-runの内容DDL実行の可否を問うボタンで構成されています。

f:id:taxa_program:20191003173000p:plain
dev環境のdry-run slack通知メッセージ

上記でdry-runの結果を確認し、問題なければOKボタンを押下するだけで、dev環境へのDDLが実行できます!めっちゃ便利!

ちなみに、prd環境のDDL実行はオペレーターが手動で行う運用としているため、dry-runの結果通知のみにしています。(実行時間帯や権限の関係で)

f:id:taxa_program:20191003184156p:plain
prd環境のdry-run slack通知メッセージ

次に、上記のslackメッセージでOKボタンを押下した後のDDL実行フローについてご紹介します。

DDL実行

f:id:taxa_program:20191003173408p:plain
DDL実行

slackメッセージのボタン押下イベントをAWS API Gatewayで受け取り、そこからLambda経由でStep Functionsを起動しています。
そしてFargate TaskでDDL実行スクリプトを実行し、結果をslackに通知します。

f:id:taxa_program:20191003174457p:plain
DDL実行結果のslack通知

自動化して何が嬉しかったか

DDL実行の都度、実行環境にSSH接続して、dry-runを実行して、結果を確認して、、、という手順が省略されたことが大きいと思います。

また、prd環境で実行する際は

  • オペレーターがdry-runを実行して
  • 開発者に対して、dry-runの結果に誤りがないか確認して
  • 同意がとれたら実行する

という手順を踏まなければならなかったのが、dry-runの結果は自動的にslackに通知されるので、都度開発者にdry-runの整合性を確認する必要がなくなりました。
そして、dry-run結果のメッセージに返信する形で「prd環境で実行お願いします!」と一言伝えるだけでOKになったのも、作業負荷の軽減に繋がっていると思います。

実際に、煩雑だった従来の手順が

  1. local環境で開発(Schemafile)
  2. Github RepositoryにPR
  3. Githubでmaster merge(dev環境にECSデプロイが走る)
  4. 実行環境のインスタンスにSSHログイン
  5. dry-runでDDLの内容が合っているか確認(dev環境)
  6. dry-runの内容に問題がなければdev環境でridgepole(DDL)の実行
  7. 最新のコミットに対してタグをpush(prd環境にECSデプロイが走る)
  8. オペレーターに依頼し、prd環境でdry-runの実行
  9. dry-runの内容に問題がなければprd環境でridgepole(DDL)の実行

下記のように省略されました。

  1. local環境で開発(Schemafile)
  2. Github RepositoryにPR
  3. Githubでmaster merge(dev環境にECSデプロイが走る)
  4. slackでdry-runの実行結果を確認し、メッセージ内のボタン押下でDDL実行
  5. 最新のコミットに対してタグをpush(prd環境にECSデプロイが走る)
  6. slackでdry-runの実行結果を確認し、問題が無ければオペレーターにridgepole(DDL)の実行を依頼

また、従来はEC2バックエンドだったため、常にインスタンスが起動している状態でしたが、今回はFatgate Taskで実行しているため、コストの観点からも恩恵を受けることができそうです。

アーキテクチャ構築のポイント

上記フローを構築する際の注意点をいくつかあげてみます。

Step FunctionsでFatgate Taskを実行するときの注意点

セキュリティグループを正しく指定する必要がある

Step Functionsの定義でセキュリティグループを設定しないと、デフォルトのセキュリティーグループが自動で割り当てられる(?)ようなので、想定するセキュリティーグループを指定する必要があります。

下記に例を載せておきます。

...
"States": {
    "Fargate task": {
      "Comment": "Fargate taskの実行",
      "Type": "Task",
      "Resource": "arn:aws:states:::ecs:runTask.sync",
      "Parameters": {
        "LaunchType": "FARGATE",
        "Cluster": "arn:aws:ecs:ap-northeast-1:12345678:cluster/cluster-name",
        "TaskDefinition": "arn:aws:ecs:ap-northeast-1:12345678:task-definition/task-name:1",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "SecurityGroups": ["sg-id"],
            "Subnets": ["subnet-id"],
            "AssignPublicIp": "ENABLED"
          }
        }
      }
...

EcsTask実行ポリシーに、「タスクを実行するRoleにアクセスする権限」を追加する必要がある

Step Functionsに付与するIAMロールに、EcsTask実行ポリシーを追加する必要があるのですが、自動的に追加されるポリシーには、EcsTaskを実行するRoleにアクセスする権限が付与されません。(これがないとFargate Taskが実行できません)

なので、下記のようにタスクロールタスク実行ロールの2つのロールに対するアクセス権限を、Step FunctionsのIAMロールに追加する必要があります。

...
{
    "Effect": "Allow",
    "Action": [
        "iam:GetRole",
        "iam:PassRole"
    ],
    "Resource": [
        "arn:aws:iam::12345678:role/EcsTaskRoleName",
        "arn:aws:iam::12345678:role/EcsTaskExecutionRoleName"
    ]
}
...

slack apiとAWS API Gatewayの連携の注意点

ここが一番大変でした・・・
ネットにもあまり情報がないので、トライ&エラーを繰り返し、なんとか実装できました。

API Gatewayの設定

まず、AWS API GatewayでREST APIを作成します。
今回はLambdaを呼び出すので、下記のように指定します。

f:id:taxa_program:20191004172039p:plain
POSTメソッドの作成

すると下記のようなAPIが作成されます。

f:id:taxa_program:20191004172335p:plain
API作成例

次に、アクションボタンからAPIをデプロイします。

f:id:taxa_program:20191004172530p:plain:w400
APIのデプロイ

すると、下記のようなURL(エンドポイント)が取得できます。
このURLは、後述のslack APIを作成する時に必要になりますので、メモしておいてください。

f:id:taxa_program:20191004172803p:plain
API Gatewayのエンドポイント

slack APIの作成

slack APIは、slackメッセージ内のDDL実行ボタンを押下したタイミングでAPI Gatewayを呼ぶ時に必要となります。

下記に手順を示します。

1. こちらからslackAppを作成する

f:id:taxa_program:20191007103217p:plain:w400
slack Appの作成

2. Interactive Componentsの設定
AWS API Gatewayを作成したときに取得したエンドポイントを設定します

f:id:taxa_program:20191007103457p:plain:w400
Interactive Componentsの設定

3. OAuth & Permissionsの設定
まず初めに、Tokenを取得します。ここで取得したTokenは、AWS Lambda → slackに下記のようなボタン付きメッセージを投稿する際に使用します。

f:id:taxa_program:20191003173000p:plain
ボタン付きメッセージ

Tokenの取得

f:id:taxa_program:20191007104505p:plain:w500
Tokenの取得

ちなみにLambdaからは下記のようにTokenを設定してリクエストを投げます。

def sample(event, context):
    
    attachments = [{
        'text': 'dry-runの結果はいかがでしょうか? \n問題なければ「OK」ボタンを押下してDDL実行してください。',
        'callback_id': callback_id,
        'attachment_type': 'default',
        'actions': [{
            'name': 'done_yes',
            'text': 'OK',
            'type': 'button',
            "confirm": {
                "title": "Are you sure?",
                "text": "DDLを実行してもよろしいでしょうか?",
                "ok_text": "Yes",
                "dismiss_text": "No"
            }
        },
        {
            'name': 'done_no',
            'text': 'Cancel',
            'type': 'button',
            "style":"danger",
            "confirm": {
                "title": "Are you sure?",
                "text": "DDLの実行をキャンセルしてもよろしいでしょうか?",
                "ok_text": "Yes",
                "dismiss_text": "No"
            }
            
        }]
    }]

    payload = {
        'token':※ここにTokenを設定※,
        'channel': SLACK_CHANNEL,
        'username': username,
        'icon_emoji': icon_emoji,
        'attachments': json.dumps(attachments)
    }

    res = requests.post(post_url, data=payload)
    return res

最後に、然るべきPermissionを設定し、対象のチャンネルに今回作成したAppをintegrationとして追加すれば終了です!

まとめ

今回はDDL自動実行フローの構築ということで、DevOps的な取り組みについてご紹介しました。

このフローに関しては、構築に少し手間がかかりますが、一度構築してしまえばそれ以降半永久的に恩恵を受けることができます。
もし同じような悩み・課題を感じている方がいれば、一度試してみてはいかがでしょうか。

We are Hiring !

コネヒトでは、成長中のサービスを一緒に支えるために働く仲間を探しています。 少しでも興味をもたれた方は、是非気軽にオフィスに遊びにきていただけるとうれしいです!

www.wantedly.com