satoryuの日記

忘れっぽいから覚えてるうちに書いておかないと。

nikotama.rb #1 に行ってきた。 #nikotamarb

nikotamarb.connpass.com

渋谷.rbなんかに行こうとしても田園都市線上りの負荷(通称: 上昇負荷)に耐えられないので行けない自分にとってはとてもありがたい場所で開催される地域Rubyコミュニティnikotama.rb 第一回目に参加してきました。

タイムテーブルにあるように、参加者が持ち寄ったお題であれこれ話し合うOST風なスタイルで、わいわいできて楽しかった。

OSTのテーマは、その場でみんなでテーマを共有し、投票した後の上位4つ。 今回のテーマは、

  1. Rubyの勉強のやり方
  2. VS Code Remote Development のRuby環境の設定
  3. Ruby コミュニティへの関わり方
  4. コードの品質を保つためのあれこれ

でした。

自分が参加したのは、2と3だったのでそれらについて書きます。

Visual Studio Code Remote Development のRuby環境の設定

先日発表されたVisual Studio Code Remote Developmentを試してみるというテーマ。

code.visualstudio.com

集まった人たちは誰も経験が無かったので、 言い出しっぺ 起案者の@mtsmfmさんをドライバーとしたモブプロ形式で チュートリアルを進めていきました。

Ruby環境を設定するところまで出来なかったのですが、

  • VS Code Insiderを設定し、
  • チュートリアルにあるnode.jsのサンプルコードを取得して、
  • node.js環境を提供するコンテナにVS Code Insiderから接続して、Node.jsのサーバーアプリを起動し、
  • ホストで起動したブラウザからアクセスする。

といったことができて、どんな感じなのかを体感できました。 また、拡張がホストとリモートとでそれぞれインストールできるようなのですが、それらがどのタイミングでインストールされるのか、どれがリモートで使える拡張なのかがわからなかったりと疑問を発見することができました。

Ruby コミュニティへの関わり方

はじめに参加者で知っているコミュニティについて共有。 上の写真右側のように、あげてみるとけっこうありますね。

  • ドットrb
  • 地域RubyKaigi
  • RailsKaigi
  • RailsGirls
  • よちよち.rb

それぞれ、やっていることや参加者の対象が異なっていて、さまざま。 参加の仕方についても、

  • 参加者
  • 発表者
  • 運営

があって、それぞれにメリットがあるという話をしました。 今日の参加者の中にもコミュニティを運営している方が数名おり、参加者が固定してしまわないようにどのように新規参加の人を引き込むかについて議論しました。

Nikotama.rb は、発起人の@kyanagimotoとしては、今日みたいにみんなが話したいテーマを気軽に持ち寄れる場を作りたいようですね。

次回は6月末頃

月1の間隔で開催していくそうで、次回は6月最終週あたりの開催を予定しているそうです。 田園都市線沿い、二子玉川付近の方々は、connpass上のコミュニティにぜひ参加するといいと思います。

nikotamarb.connpass.com

Twitterでもアナウンスをするらしいので、こちらもフォローしておくとよさそう。

twitter.com

エンジニアの新人研修Night に行ってきた。 #ttnight

connpass.com

新人研修についての知見を共有したりする場ってなかなか聞いたことが無いので、参加してみた。 昨年、ひょんなきっかけから新卒研修で講師というかメンターみたいな感じで関わったこともあり、他社がどのようにやっているのか気になっていた。 プログラミング言語などの知識については個人でも学べる機会はかなり増えてきていて、実際の業務はどのように進めていくのかを伝えていくことが研修での重要な点になっていると思う。情報系出身の学生であれば、それこそ業務のことをキャッチアップすれば即戦力としてすぐに成果を出してもらうことが期待できる。 ただ、その業務に関することは、会社独特なものがあるので各社で内製するしかないんだろうなー、という気がしている。

という前置きはいいとして、私用で長居できなかったため、結局、自社の昨年の研修についての話を聞いてきた。

新人研修をやってみたら最&高だった件 @TAKAKING22

昨年の新卒研修は、業務に必要な事前知識を教える研修ではなく、実際の業務で起きていることを体験してもらう研修として作られていた。 事業の人が持ってきてくれた実際の業務上の課題を、新卒達が自分たちで考えて進んでいく。 技術的に出来ないことやわからないことがあれば、自分たちで調べたり、出来ることの中でプロダクトを仕立てていく。 今何をすべきで何をすべきでないかといった判断も彼ら自身が決めて進んでいく。 1週間という限られた期間でプロダクトを作りデモをして、フィードバックをもらう。また次の週にはフィードバックに応えるための開発をし、またデモをする。といった繰り返しをしていた。

この時のメンターがやれることって、答えをしっているわけではないので、一緒になって考え、その考えを伝えることだったと今は思っている。 問題があやふやであれば整理して、その観点を伝えたり、使い方のわからないライブラリを隣でググってみたり。

個人的に気をつけていたのは「新卒だから○○」といった勝手な制限を設けないこと。結果的に、最後のデモで彼らは、自分だったら到底作れないような素晴らしいプロダクトを作りあげた。あの日は本当に感動した。

ということを色々思い出してしまった発表だった。

ビジネス採用がエンジニア研修を受けた話 @mori_PYON_miwa

で、そんな研修を受けた @mori_PYON_miwa の配属後の話がこちら。 想定はしていたものの研修時とのギャップの大きさに悩み、研修で学んだことを活かして自分の働き方を変えていった。 研修でやった技術的なことではなく、働き方を変えていく、しかも1人で始められるところからやっていったところがすごい。

余談だけど、ここには書いてないが、彼は同期と組んでプロダクトづくりに取り組んでいる。 その話を教えてくれた時に、「やっぱりエンジニアとして配属された人たちはすごい。追いつけない。」と言っていた。本人としては残念な気持ちがあるのかもしれないが、同じチームのメンバーに対して尊敬の念を持てていることでもある。少なからずともチーム開発をやった経験があるからこそ、持てる感情なんじゃないだろうか。

ログインシェルをfishに乗り換えた。

https://fishshell.com/assets/img/Terminal_Logo_CRT_Small.png

長らくzshを使っていたのだけれど、少し前から気になってたfishに乗り換えてみた。

zshで凝った設定はあまりしていなかったので、.zshrcからの移植のほとんどは

  • PATHの追加
  • aliasの追加

で、唯一、rbenvがrbenv init -evalしてもfishでは正しく動かなかったので、oh-my-fishrbenvプラグインを入れた。

ドキュメントのいたるところに

Unlike other shells,

と出てきて、これまでのbashzshのようにはいかないが、デフォルトのコマンドが読みやすく、自動読み込みされるfishファイルの配置もわかりやすいので、書きやすそう。

最近、 ~/.configに設定ファイルを置くコマンドやツールが増えてきたので、それらも他のドットファイルみたいにgitで管理したい。うまい方法はないかな?

Node.js で書いたAzure Functions アプリのテストを書く。

自分で使うためだけのTwitterクライアントをほそぼそと開発していて、それのテストがそろそろ欲しいと思い、Jest を使って書いてみた。 手始めに、Azure Functions アプリのテストを書いてみたので、それについてまとめてみる。

実際のコードは、こちら。

github.com

Jest

jestjs.io

JestはFacebookが開発するJavaScriptのテストツール。Node.jsで書いたライブラリやフロントのアプリのテストも同じように書ける。 知人に勧められて使ってみることにしたのが発端ではあるが、今開発しているものはAPIはAzure Functions(Node.js)で、フロントがVue.jsで開発しているので、ちょうど良いと思った。

あとで気づいたのですが、公式ドキュメントもJestでテストの説明が書いてあります。

docs.microsoft.com

Azure Functions アプリのテスト

Azure Functions Core Toolsを使えば、Azure上で動いているのと同じようにローカルの開発環境でAzure Functionsアプリを動かすことができる。 しかし、それでも、再現が面倒臭いものがあったり、手作業でテストする事自体が面倒。 例えば、バインディングしているBlobストレージから得られる情報に応じての振る舞いをテストしようとしたり、バインディングでサポートされていない外部のAPIへのアクセスなどをモックしたりするのは面倒くさい。 また、今回開発しているアプリではWebApps のEasyAuthという認証認可の仕組みを使っている。 それを再現するためには、認証情報を表した特殊なHTTPヘッダを追加しないといけない、といった面倒くささがある。

方針

Functionsアプリそれぞれは、context と入力バインディングを引数とした関数として記述される。 例えば、HTTPトリガーの関数の場合、

module.exports = async function(context, req) {
    const name = req.query.name;
    context.bindings.res = { status: 200, body: `Hello, {name} !` }
    done()
}

のように、引数としてcontext とHTTPリクエストを入力バインディングreqとして受け取る。HTTPレスポンスなどFunctionsアプリの出力は、出力バインディングを経由して出力する(上の例の場合、context.bindings.res)。 また終了したこと表すため、doneメソッドを呼んでいる。異常終了させたい場合は、この引数にエラーメッセーや例外オブジェクトを渡す。

ということで、大まかなテストの方針として、以下のように考える。

  • 前提条件として、入力バインディングに値、オブジェクトを設定し、
  • それを引数としてテスト対象のFunctionsアプリに与え、実行し、
  • 出力バインディングが期待した状態になっているかチェックする。

それぞれについて書いていく。

前提条件を作る。

context は、

  • バインディングの情報を持つbindings プロパティ
  • ログ出力のためのlogオブジェクト
  • 終了通知のためのdoneメソッド

を持つオブジェクトなので、それぞれを再現させるようにモックなどを使う。 入力バインディングは、Functionsアプリの引数としても渡すことができるので、状況に応じて使い分ける。 bindingsは出力を受け付けるためにも使うので、入力バインディングが引数だとしても用意する。 logdoneは、関数内部で呼ぶことさえできればいいので、モック関数を充てる。

上のHTTPトリガーの関数の場合、引数として入力バインディングを受け取り、出力はbindingsを経由して出力するので、下記のように前提を作る。

test('should response 200', async () => {
  const bindings = {}
  const req = {
     query: { name: 'satoryu' }
  }
  const log = jest.fn() 
  const done = jest.fn()

  // TODO: Call Function

  // TODO: Check output bindings
})

関数を実行する。

function core toolsなどテンプレートから作ったFunctionsアプリは、通常、関数をexportしたJavaScriptファイルなので、それをテストコード内でrequireし、その関数に上で用意したbindingsなどを引数に渡すことで実行できる。

const httpTriggerFunction = require('../HttpTrigger')

test('should response 200', async () => {
  const bindings = {}
  const req = {
     query: { name: 'satoryu' }
  }
  const log = jest.fn() 
  const done = jest.fn()

  // Call Function
  await httpTriggerFunction({ bindings, log, done }, req)

  // TODO: Check Output Bindings
})

出力をチェックする。

あとは、関数によってbindingsに追加された出力バインディングへの値をチェックするだけ。 例えば、bindings.resstatusには、200が入っているはずなので、それを確認する。

const httpTriggerFunction = require('../HttpTrigger')

test('should response 200', async () => {
  const bindings = {}
  const req = {
     query: { name: 'satoryu' }
  }
  const log = jest.fn() 
  const done = jest.fn()

  // Call Function
  await httpTriggerFunction({ bindings, log, done }, req)

  //Check Output Bindings
  expect(bindings.res.status).toBe(200)
})

また、ログに期待したものが書き込まれているかや、正しくエラーを検知して異常終了できているか確認したければ、logdoneが呼ばれたかどうか、その時の引数についてチェックすると良いだろう。

expect(log).toHaveBeenCalledWith('Error found!')
expect(done).toHaveBeenCalledWith(expectedErrorObject)

おわり

Azure Functions は、Blobストレージなど連携するサービスをバインディングを定義することで、SDKなど準備する必要なく利用できるところが便利で、そのバインディングのおかげで関数の実装だけでなくテストも簡易に書ける。

あとは、function.jsonで定義されているバインディングの名前と合致してるかとか確認できれば、デプロイ前のテストとしては良さそう。

PHPでGoogle Suggest APIを利用するためのパッケージを作って、リリースした。

packagist.org

タイトルの通りなのですが、先日、google_suggest というPHPライブラリをリリースしました。

Googleみたいなサジェスト機能を作りたい!

といった時に是非ご活用ください。

以下は、開発のときの余談です。

続きを読む

easyAuthを使ってAzure FunctionsでTwitter OAuth認証付きのAPIを作る。

Azure Functionsには認証認可を扱うeasyAuthと呼ばれる機能があり、ここでその使い方について紹介する。

Azure Functions は、Azure Web Appがベースとなっていて、だいたいWeb App で出来ることはFunctionsでもできる。 Web Appには、TwitterFacebookなどの外部での認証と連携させる機能を持っており、それを利用し、アプリケーションの認証機能部分を作ることができる。 また、Token Storeを利用することで、認証したユーザーのAccess Tokenも保存し、特別なライブラリを使用することなくそのAccess Tokenを利用することができる。

それらを試すために、Twitter OAuthを利用した認証付きのツィートを投稿するAPIをAzure Functionsアプリを作ってみた。

github.com *1

ちなみに、Vue.jsで作ったフロントを組み合わせたものが、こちら。

takoyaki.netlify.com

もくじ

前提

予めAzure Functionsのアプリが1つあること。 以降、文中ではsamplefuntionとして参照する。

Twitter にアプリケーションを登録

Twitter Developers を開き、アプリケーションを登録する。 登録するCallback URLは、 https://samplefunction.azurewebsites.net/.auth/login/twitter/callback を入れる。 これは、あとで設定するeasyAuthが追加するCallback用のエンドポイントのURLである。

他の必須項目を入力して登録後、発行されたConsumer API KeyとAPI Secret Keyを控えておく。

Azure Functions の認証/認可の設定

Azure Portal から、Azure Functions のブレードを開き、Authentication/Authorization から以下のように設定する。

f:id:satoryu:20190404193101p:plain

easyAuthによる認証

ログイン

https://samplefunction.azurewebsites.net/.auth/login/twitter をブラウザで開くと、TwitterOAuth認証画面にリダイレクトされる。 ここで先程登録したアプリケーションにTwitterアカウントで認証することができる。

ログアウト

https://samplefunction.azurewebsites.net/.auth/logout にアクセスすることで、ログアウトができる。

Access Tokenの利用

認証後に特別なライブラリを利用することなくAccess TokenをFunctionアプリは取得することができる。

具体的には、Twitterで認証した後に、ドメインsamplefunction.azurewebsites.netに対してAppServiceAuthSessionというCookieが発行される。このCookieを付けて、Azure Functionsアプリにリクエストを送ることで認証済みとして扱われる。 そして、アプリケーションへのリクエストに下記のヘッダが追加される。

  • X-MS-TOKEN-TWITTER-ACCESS-TOKEN
  • X-MS-TOKEN-TWITTER-ACCESS-TOKEN-SECRET

これはそれぞれAccess TokenとAccess Token Secretなので、Consumer KeyとConsumer Secretと組み合わせることで、Twitter RESTful APIを認証済みのユーザーとして利用することができる。

実際のコード

実際にこの仕組を利用して、Twitterに投稿するためのFunctionのコードを載せる。

takoyaki-api/index.js at master · satoryu/takoyaki-api · GitHub

const Twitter = require('twitter')

module.exports = async function (context, req) {
    const status = req.body.status
    const twitter = new Twitter({
        consumer_key: process.env.TWITTER_CONSUMER_KEY,
        consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
        access_token_key: req.headers['x-ms-token-twitter-access-token'],
        access_token_secret: req.headers['x-ms-token-twitter-access-token-secret']
    })

    try {
        const tweet = await twitter.post('statuses/update', { status })
        context.bindings.res = { body: tweet }

        context.done()
    } catch(err) {
        context.log.error(err)
        context.bindings.res = {
            status: 500,
            body: err
        }
        context.done(err)
    }
};

ここでは、twitterというパッケージを利用して、Twitter APIへアクセスしている。 それを利用する際に、環境変数からConsumer Key、リクエストに追加された2つのヘッダからAccess Tokenを用いている。 環境変数TWITTER_CONSUMER_KEYTWITTER_CONSUMER_SECRET は予めFunctionアプリのApp Settingsから追加しておく必要がある。

おわりに

easyAuthによる認証とToken Storeを使うことで、OAuthの認証の実装や認証後に得られるAccess Tokenの管理をする必要がなく、DBを準備することもなく、アプリケーションの開発に集中することができる。

easyAuthをローカルの開発環境で起動(もしくは再現)させる方法をまだ見つけられていないので、テストする際には実際にデプロイしなければならないところが課題として残っている。 もしご存知の方がおりましたら教えてください。

参考

開発者の@cgillum のeasyAuthのWikiに詳しく書いてある。

github.com

また、Web App の公式ドキュメントにも認証と認証後のAccess Tokenの扱い方について書いてある。

docs.microsoft.com

*1:なぜタコ焼きにしたのかはよく覚えてない。「つぶやき」をもじっただけ。