こまどブログ

技術のこととか仕事のこととか。

GitHubに草を生やして100日の節目にアウトプットをふりかえる〜ブログ・登壇・GitHub〜

はじめに

目次

GitHubに草を生やして100日目の節目に

昨日、GitHubのコントリビューショングラフへの着色*1(いわゆる「草を生やす」)が5月3日から連続100日目を迎えました*2

上記のツイートへの反響が大きくて驚いています。#100daysofcodeというのもありますし、100という数字にインパクトがあったのでしょうか。いずれにせよ、ひとつの節目になるこのタイミングでブログを書き残しておくことは、自分のために有意義であるだけでなく、少なからず誰かのためにもなるのだろうと感じさせられました。

アウトプット全体をふりかえる

草を生やし続けたことについての「ふりかえり」をしようと考えましたが、僕の場合はGitHubへのコミットは独立して存在するものではなく、当ブログを中心としたアウトプットの一環としての位置付けです。そこでこの記事では、ブログ、登壇、GitHubという3つのアウトプットを総体的にふりかえってみようと思います。

この100日間+αでのアウトプットは以下の通りです。これらについて、順にふりかえっていきます。

  • ブログ
    • 頻度:週1回更新
    • 記事数:21本
      • 技術記事:14本
      • 登壇報告:2本
      • イベントレポート:2本
      • その他:3本
  • 登壇
    • 回数:2回
    • 内容:
      • 2018/6/26「行動を無駄にしないために必要なこと」
      • 2018/7/27「httpモジュールから始めるNode.js入門」
  • GitHub
    • 草を生やした日数:100日
    • コミット数*3:227コミット

ブログ(記事21本)をふりかえる

意図:技術の勉強を駆動する&長い文章を書くために

「エンジニアにとってアウトプットをすることが重要である」という趣旨の言説は、しばしば説かれ、少なからざるエンジニアにとって受け容れられているものと思います。あえて権威に訴えようとすれば、Matzの若手エンジニア向けの講演や、『SOFT SKILLS』を挙げることができます。

アウトプットの媒体として、やはり人気があるのはブログでしょう。ブログは簡単に始められますし、内容もタイミングも分量も自由、また顔も声も本名も出ない媒体ということもあって心理的なハードルが相対的に低いのも魅力です。

このような魅力に加えて、僕の場合は長い文章を書くことへの苦手意識があり、それを払拭するためにもブログというアウトプットを選択しました。このあたりについては、ブログの最初の投稿でも書いています。

ky-yk-d.hatenablog.com

週に1回、技術系の記事を書くことをノルマとしていました。イベントレポートなどは、あくまでオプションという位置付けに留めていました。その理由は、僕が「プログラミング大好き!」というタイプの人間ではなく、放っておくと開発手法や組織論といった方向に偏ることがわかっていたからです*4

実績:投稿した記事を一覧にしてふりかえる

投稿記事一覧

No 投稿日 タイトル 分類
1 4/22(日) ブログを始めます。目標:週1更新 その他
2 4/22(日) Tech系ポッドキャストについて(1):t_wadaさんとajitofm その他
3 4/26(木) 4/26「エンジニアリング組織論への招待 ☓ カイゼン・ジャーニー」 に参加しました イベントレポート
4 4/30(月・祝) Tech系ポッドキャストについて(2):しがないラジオについて その他
5 5/4(金・祝) フロントエンドの勉強としてGitHub PagesでダメWebサイトを公開してみました 技術
6 5/14(月) フロントエンド初心者が学ぶ「リンクが展開されるあれ」とVue.js 技術
7 5/17(木) JS初心者がAWS Lambdaで実装するLINE Bot〜「オウム返し」の一歩先〜 技術
8 5/21(月) WebページからDynamoDBにアクセスしてみる〜はじめてのAjax通信とDOM操作〜 技術
9 5/27(日) Vue.js+axiosでDynamo DBにAjax通信する 技術
10 6/3(日) 目指せ!脱Vue.js初心者〜Udemyの"The Ultimate Vue JS 2 Developers Course"を始めた〜 技術
11 6/9(土) フロントエンド弱者が腹を括ってWebpackに触ってみた 技術
12 6/16(土) Vue.jsで作る初めてのSPA〜Udemyの"The Ultimate Vue JS 2 Developers Course"のProject 2を受講した 技術
13 6/20(水) F.O.X Meetup #3 ~スタートアップのチームビルド~ に参加した イベントレポート
14 6/23(土) 環境構築弱者でも簡単に始められるテスト駆動開発〜mocha + power-assert でJavaScriptのテストを書く〜 技術
15 6/27(水) 6/26 「カイゼン・ジャーニー・ライトニングトークス」で初LT登壇した 登壇報告
16 7/1(日) Vue Routerで「ネストされたルート」を試した 技術
17 7/7(土) Vue Routerのナビゲーションガードによるアクセス制限を試した&コードを読み解いた 技術
18 7/16(月) Node.jsのhttpsモジュールを用いた通信処理をPromiseで書き直して解読してみた 技術
19 7/22(日) Node.jsからConnpassのAPIを叩こうとしてつまずいたこと 技術
20 7/28(土) 7/27 WEBエンジニア勉強会 #08 でNode.jsについてLTした 登壇報告
21 8/5(日) Connpass APIをLambdaから扱う〜Lambdaの非同期呼び出しによる分散処理〜 技術

更新頻度については、週1回のペースを守って合計21本の記事を書くことができました。「週1回」というのを「月〜日の間に1回」として、土日のどちらかに書くというのを原則にしていました。月曜日に投稿していることが何回かありますが、これらは全て「日曜日に書こうとして日付が回ってしまった」パターンです(翌日の月曜日、その後の1週間が辛かったことは言うまでもありません)*5

内容についても、「技術系記事を」というのを概ね達成できました(記事の内容についての縛りを目標に追加したのは5月に入ってから)。7月最後の週に投稿した記事は登壇報告ではありますが、スライドの内容にソースコードが含まれているので技術系の投稿ということにしました。こうして並べてみると、Web系の技術、それにAWSについて中心に勉強していたことがわかります。Vue.jsの勉強は最近止まってしまっているので再開したいですね。

感想:ノルマは守れている。しかし、ただそれだけだ。

当初立てたノルマを守ることで、習慣になりつつあることはよいです。当初はまったく更新できる気がしなかったのですが、今では「あー書くかー」くらいの気分で書くことはできています。もっとも、辛いことにはまだまだ辛く、毎週苦しみながら書いているというのが正直なところです*6

技術面の勉強を駆動するという意味でも、平日のうちに勉強しておかないと土日でネタを探すのは辛い*7ので、勉強のネタを探すようになりました。また、日頃のプログラミングの中でエラーに直面したときなどは、「しめしめ、これでブログが書けるぞ」と逆にポジティブに捉えられるようになりました。また、よく言われる「外部記憶装置」としての意義も大きく、一度やったはずのことを忘れてしまった時に自分のブログを見るという機会がすでに何度かありました。


課題は、「技術系の記事を週1」というノルマを達成することで満足してしまっていることです。何度かノルマを引き上げようか検討したこともあったのですが、結局このノルマに安住したままになっています。あくまでノルマなので、当人の心がけ次第ではあるのですが、実情として達成に資する技術系の記事の執筆だけにフォーカスしてしまっているのは確かです。

目標としたいのは、技術系の記事を定期的に投稿しながらも、それ以外の記事もバシバシ投稿するような状態です。記事の質については、上げようとするとペースの方が犠牲になる、あるいは嫌になってしまってブログ自体やめてしまう危険性があるので、当面は過度に意識することはしませんが、もう少しあげられたらとは常々思っています。

登壇(5分間LT×2回)をふりかえる

意図:コンフォートゾーンに留まらない

ブログは自分のペースで書けますし、読みたい人だけ読んでくれればというスタンスで、質をそこまで意識しないでいられます。当初は、ブログを書くだけでも精一杯だと思ったので、これでもよかったのですが、ブログを続けていくなかで、(ノルマを変更しなかったこともあり)自分に満足してしまいそうになっていました。

一方で、アウトプットの別の形として、登壇というのはかなり初期から意識していました。5月半ばに参加した「WEBエンジニア勉強会 #07」の最後に、OSCA(@engineer_osca)さんが勉強会の趣旨を説明されているなかで、登壇歓迎と仰ったのを聴いて以下のようなTweetを残しています。

三点リーダ×2が示しているように、このときは登壇に対してはあまり前向きではなかったと思います。しかしその後、「コンフォートゾーン」という言葉を目にするたびに、自分がアウトプットについてコンフォートゾーンに陥り、そこに留まってしまうことへの危惧を覚えました。そこで、ブログの次は登壇だろうということで登壇することを考えました。

実績:ブログの記事でふりかえる

6月末と7月末に1回ずつ、合計2回のLT(5分間)を経験しました。それぞれ、ブログ記事を書いています。

ky-yk-d.hatenablog.com

ky-yk-d.hatenablog.com

感想:登壇に必要な安心感、学生症候群

エンジニアにはブログを書く人は結構いる印象ですが、登壇する人はその中でもごく一部なのではないかと思います。登壇というのは聴衆の前に身を晒すことですから、(本名を明かす必要こそありませんが)ブログのような気楽さはありません。この心理的ハードルを乗り越える経験ができたのは収穫です。

「勇気を振り絞ってやってやった!」というような書き方をしましたが、登壇については安心して自らを晒すことのできる場の存在があって初めて実現したものだと思います。これまで登壇させていただいたのは、どちらも自分にとってそういう場でした。すなわち、過去のイベントに参加したことがあったり、中心メンバーの方*8と懇親会の場やTwitterなどで交流する機会があったりしたコミュニティです。いきなり見知らぬ人ばかりのイベントで登壇するのは難しくても、このような場で訓練を積んでからであれば心理的にも随分と楽になるのではと思います。

登壇をすることによって気付くことができたこともあります。1週間で1本のブログと異なり、登壇は1ヶ月くらい前から予定が決まるので、準備に使うことのできる時間が長くなります。したがって、ブログでは意識せずに済んだ自分の悪癖を嫌でも思い出させられることになります。それは、学生症候群です。夏休みの宿題に8月下旬まで着手しなかったり、試験前日に慌てて勉強を始めてしまうあれです。学生症候群については、7/27の登壇の報告記事でも書きましたが、これはブログだけやっていたら気付かなかったものだと思います。


感想ということからは少しずれますが、プレゼンという面で、とても印象に残っているのが、ajitofmの和田卓人(@t_wada)さんゲスト回です。テスト駆動開発の伝道師であり、プレゼンテーションの名手として知られる和田さんが、どのようにプレゼンの準備をしているのかを語っています。準備の重要性というのはよく知ってはいますが、登壇をすることでより実感しますし、準備をすることが簡単ではない(単に時間をかければいいというものではない)こともよくわかりました。

ajito.fm

GitHubでの「草生やし」(100日間)をふりかえる

意図:技術から逃げないこと。Write Code Every Day

ブログで技術系の記事を書くのと同じで、技術から逃げないために続けていました。開始当初の下記の記事でもその旨が表明されていました。

技術で解決されるべき問題をエモい話で解決しようとしてしまったり、業務で与えられた技術的課題を自分の技術力の不足によって解決できなかったりするという怖れがある以上、技術の勉強をすることは不可欠だなと考えています。

フロントエンドの勉強としてGitHub PagesでダメWebサイトを公開してみました - こまどブログ

また、Write Code Every Dayというキーワードにも強く背中を押されました。このキーワード自体は、以前から知ってはいましたが、GitHubに草を生やしはじめてから「やってみよう」と思いました。

おじさんになるのも怖かったみたいです。

実績:コントリビューショングラフとTweetでふりかえる

f:id:ky_yk_d:20180811104438p:plain

github.com


書いたコードの内容は以下の通りです。


開始直前〜継続期間中のTweetも載せておきます。

  • 【2018/4/28】 Git/GitHubの使い方を学び始める

  • 【2018/5/3】GitHub Pagesに草を生やす(翌日にブログ投稿)

  • 【2018/5/21】自戒する

  • 【2018/6/22】8週間を達成(継続のために毎日の時間を短縮している)

  • 【2018/8/10】100日間を達成

感想:「草」を目的にすることの功罪

「草が生える」というのは、モチベーションとしてはかなり大きく作用したと思います。途中で何度もコントリビューショングラフのスクショをとって嬉々としてTwitterに貼っていました。他人に見てもらいたいという気持ちが強かったと思います。動機としてはあまり褒められたものではありませんし、そのための弊害も後述するようにありましたが、「草を生やしている自分に酔う」というのはそれなりに使い道のあるものです。

GitHubでの「草を生やす活動」については、良かったことと悪かったことがはっきりあります。まず、良かったこととしては、以下の2点が挙げられます。

  • ブログで技術系の記事を書く準備になった
  • 朝の時間を有効活用できるようになった

「草を生やし続ける」ということは、毎日コードを書くということです。毎日コードを書くことで、ブログに書く材料を継続的に供給することができました。土日だけでネタを探すのは辛いというのは先述の通りですが、平日にも毎日コードを書いていると何かしらネタにはなるわけです。言い方を変えれば、ブログ執筆における学生症候群を防いでいたとも言えるかもしれません。

また、夜は飲み会などがあって時間を確保できない、場合によっては日付が変わってから帰宅することもあるということで、コードはほぼ朝、会社に行く前に書いていました。始める前は、朝は寝坊したり、ネットサーフィンしたりと無駄に使っていたことも多かったのですが、コードを書くようになって有意義に使うことができるようになりました。


一方、悪かったことは以下の2点です。

  • ネタが断片的になった
  • 「草の生えないこと」をしなくなってしまった

毎日書くというのはハードルが高く、続けるために1日あたりの時間は少なくせざるを得ませんでした。じっくり設計について考えたり、方針を考えたりする機会を設けることもしませんでしたから、必然的に書くコードは場当たり的になります。100日間続けた割に、技術を身につけたという感覚に乏しいのは、このあたりの事情もあるでしょう。

また、「草を生やす」を目標にしてしまったことの弊害もありました。最も大きいのは、「コードを書くことではあるのに、草は生えないこと」をしなくなってしまったことです。ブログでも書いたように、UdemyのVue.jsのコースを受講していました。このコースは、講師のGitHubリポジトリからプロジェクトの土台のソースコードをクローンし、そこを編集することで進めて行くものです。しかし、GitHubの仕様上、他人のリポジトリからクローンしたものへのコミットはコントリビューション扱いにならず、草が生えません(当然といえば当然)。

6月半ばごろまでは、Udemyのコースの勉強も進められていました。しかし、少し忙しくなってきてから、これは一番最初に生活から抜け落ちました。理由は当然、それが「草の生えない活動」だからです。講師の方の説明もわかりやすく、実践的な内容でもあったUdemyのコースは、自分としてもモチベーション高く取り組めていたはずのものでした。そうであるにもかかわらず、「草」を優先したのは、もちろん「草を途絶えさせたらコードを書くのをやめてしまう」という危惧があってのことでしたが、このような選択をすることになったノルマの設定の仕方が悪かったことは疑いようがありません。

おわりに

以上、ブログ・登壇・GitHubの3つに分けてふりかえってみました。最後に、全体的な感想と、今後のアクションについてまとめておこうと思います。

全体的な感想

ノルマとして設定したものについては、守ることができている印象を持ちます。もちろん、今回「ノルマ設定した」と書いたこと以外に、Twitterやら何やらで「こうしていく」と宣言したことで、実際にできていないこともあります。しかし、毎日コードを書き、週に1回ブログを書くという明白で基本的なノルマを守ることができたのは、自信になりました。これからもノルマをうまく設定して取り組んでいこうと思います。

一方で、質については修正が必要です。これまでは頻度を維持するため、質を問うことはしてきませんでしたが、習慣化ができつつあるので、既に習慣になっている部分を中心に質も向上させたいです。そのためにも、場当たり的な記事・コードではなく、ある程度の計画をもってアウトプットを組み立てていきたいです。

今後の方針

下記をアクションプランとします。

  • ブログは週に2本で、1本は技術系の記事(2本目は、イベントレポート、書評、雑記など、何でも可)を書く。
  • ブログはすぐに公開せず、必ず推敲する(上から下まで読み通して修正し、修正点がなくなるまでこれを繰り返す)。
  • 2ヶ月に1回は登壇する(ジャンルは問わない)。
  • 1週間に4日はプライベートで1ポモドーロ以上コードを書く(草には拘らず、Trelloで管理)。

ふりかえってみて感じたこと

最後に、今回の記事を書こうとしてみて思ったことを。アウトプットのふりかえりというのが今回の趣旨ですが、ふりかえろうとして思ったのは次のことです。

ブログとGitHubでのアウトプットについては、習慣化というのを強く意識したものでしたが、ふりかえりを同時に習慣に組み込めていなかったのは失敗でした。今回の記事が書きづらかったこともそうですが、ここまでに書いてきた反省点の中には、もっと早く自覚し、修正するアクションを起こすことのできたものも含まれています。

ふりかえりを習慣にするというのは、何度も何度も決心し、その決意を周囲の人に伝えることまでしていたにもかかわらず、結局できずにいることです。もちろん、日々行動しながら「今のはよくなかったな」とか「うまくいっていないな」とかは感じていますが、それは一過性のもので、すぐに忘れてしまいますし、アクションに繋がっていきません。

今回書き出してみて、一度感じていたはずなのに忘れていたことをいくつも思い出しました。おそらく、思い出せていないものもあるはずです。やはり、ブログの記事という形をとるかはともかくとして、「定期的に」「アクションプランを伴って」ふりかえりを行うことが必要だと思いました。

というわけで、アクションプランを追加します。

  • ブログは週に2本で、1本は技術系の記事(2本目は、イベントレポート、書評、雑記など、何でも可)を書く。
  • ブログはすぐに公開せず、必ず推敲する(上から下まで読み通して修正し、修正点がなくなるまでこれを繰り返す)。
  • 2ヶ月に1回は登壇する(ジャンルは問わない)。
  • 1週間に4日はプライベートで1ポモドーロ以上コードを書く(草には拘らず、Trelloで管理)。
  • 1ヶ月に1回ふりかえりの記事を書く(その週の2本のうちの1本としてよい)。
  • 月次のふりかえりでは方針の見直しを必ずする(これ自体も見直す)。

この記事を書いていたら、土曜日が終わってしまいました(扱う対象も記事自体も大きすぎる)。コードを書いていないので、GitHubの草も途切れました。変なものに執着しても仕方がないので、来週から上記の方針でまた気分を入れ替えて頑張って行こうと思います。乞うご期待。

カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで

カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで

わかばちゃんと学ぶ Git使い方入門〈GitHub、Bitbucket、SourceTree〉

わかばちゃんと学ぶ Git使い方入門〈GitHub、Bitbucket、SourceTree〉

SOFT SKILLS ソフトウェア開発者の人生マニュアル

SOFT SKILLS ソフトウェア開発者の人生マニュアル


Special Thanks to @kakakakakku

僕がアウトプットについてふりかえるとき、欠くことのできない人がいます。それは、カカカカック(@kakakakakku)さんです。ここまでの記事では、彼への言及は全て脚注に落としておきましたが、それは本文に出すと「アウトプットのふりかえり」という記事の趣旨がブレるからです。とはいえ、最後に言及しておかなければなりません。

ブログを書こうと思ったきっかけをくれたのも、マネジメント方面に傾く僕に敢えて技術系の記事を書くように勧めてくれたのも、ブログの書き方を教えてくれたのも、困ったときにネタを提案してくれたのも、登壇に向けて背中を押してくれたのも、カカカカックさんです。僕がアウトプットを曲がりなりにも続けられているのは、カカカカックさんのおかげです。感謝します。思い切ってお願いして良かったです*9

*1:本来は他人のリポジトリへのプルリクエスト等で「貢献」した実績がこのグラフに表れるべきなのでしょうが、僕の場合はすべて自分のリポジトリへのコミットだけです。OSSコントリビュートもできるようになれるとエンジニアとしては一段階上がる感じがしますが……

*2:アクセスしてみると、6/1については草が生えて見えるときとそうでない時があります。この日は、他のユーザからリポジトリをフォークして、そこに対して修正を加えていた日で、厳密には草を生やしたことになるのか怪しいのですが、実態としてコードを書いてはいたのでアリとしています。

*3:自分のリポジトリに対してなのでこの数字に意味はありませんが。

*4:この傾向は自覚はしていましたが、それを踏まえて「技術系を週1」というノルマを提案してくれたのはカカカカックさんでした。

*5:日付が変わってしまって月曜日から寝不足で会社に行くのはサラリーマン的には褒められた行為ではありませんから、合理的な判断としてブログを翌日に先送りする選択をすることもできたと思いますが、ブログを書かないと眠れないのです。

*6:三大欲求の一角を「ブログ欲」が突き崩してしまった方も世の中にはいるようですが……。

*7:カカカカックさんからも指摘を受けました。

*8:カイゼン・ジャーニー』の著者の市谷(@papanda)さんと新井(@araratakeshi)さん、WEBエンジニア勉強会のOSCAさん。

*9:5月から2ヶ月間、「ブログメンティ」として上記のようなサポートを受けました。その内容については、他のメンティのみなさんが卒業エントリとして書いているものをご参照ください。

Connpass APIをLambdaから扱う〜Lambdaの非同期呼び出しによる分散処理〜

Connpass APIを用いたボットの作成

先日、ConnpassのAPIをNode.jsから実行する方法について記事を書きました。

ky-yk-d.hatenablog.com

上の記事の末尾にも記載した通り、現在はConnpassのイベント新着情報をSlackに通知するボットを作成しています。

github.com

実装の方針としては、

  • 定期実行が必要
  • 1回1回はAPI呼び出し+通知だけ

ということで、AWS LambdaをCloudWatch Eventsを用いてスケジュール起動することを考えました。CloudWatch Eventsを使うのは初めてですが、LambdaでAPIコールを行うのは今までも何度もしてきたので、そこまで苦労はないと考えていました。

docs.aws.amazon.com

直面した問題

しかし、いざ開発を始めてみると、Lambda側で少々工夫が必要であることに気づきました。それは、以下のような問題に直面したからです。

  • 1個あたりの検索結果が増えるとConnpassのAPIでエラーになる
  • 検索結果の総量が増えるとLambdaのタイムアウトにひっかかる

これらについて順に説明します。

問題①:1個あたりの検索結果が増えるとConnpassのAPIでエラーになる

ConnpassのAPIでは、様々な検索条件をクエリ文字列に付加することができます。その中の一つとして、文字列で検索ワードのAND条件とOR条件を指定することができるため、ウォッチ対象のテーマをOR条件に付加していけば、1本のリクエストで複数のテーマのイベント情報を取得することができます。検索ワードごとに別々のリクエストを発行する必要はありません。

たた、あまりに多くの検索ワードをOR条件として付加してしまうと、膨大な数のイベント情報が取得されてしま うからか、適切にレスポンスが取得できずにエラーとなってしまいます。

問題②:検索結果の総量が増えるとLambdaのタイムアウトにひっかかる

ConnpassのAPIの問題以外に、Lambda側の問題もあります。検索結果の件数が総量として増えれば、それだけ処理にかかかる時間は増加します。そうなると、Lambdaのタイムアウト時間に抵触する恐れが出てきます。実際、タイムアウト時間を10秒に設定した場合には、エラーが発生してしまっていました。

考えた解決策

起動部のLambdaから本体のLambdaを複数回&非同期で呼び出す

以上のことから、次のような解決策を考えました。

  • 1回のリクエストで対象とする検索ワードを限定する(ConnpassのAPIでのエラー対策)
  • 1回のLambdaの実行ではタイムアウトしない程度の処理のみを行う(Lambdaのタイムアウト対策)
  • 上のLambda(本体)を呼び出すLambda(起動部)を作成し、そこで検索ワードを処理させる
  • 起動部から本体を非同期で呼び出す(同期呼び出しすると起動部がタイムアウトする)

図にすると以下のような形。

f:id:ky_yk_d:20180805193703p:plain

実装の一部

const AWS = require('aws-sdk');
const lambda = new AWS.Lambda();
// 中略
ArrayOfArrayOfWords.forEach((element,index)=>{  // 「『検索ワードの配列』の配列」を順に処理
      queries.keyword_or = element;
      const params = {
        FunctionName: targetLambdaArn,
        InvocationType: 'Event', // 非同期呼び出し。即座にステータスコード202で返ってくる
        Payload: JSON.stringify(queries)
      };
      lambda.invoke(params).promise().then(
        (res)=>{
          console.log(index,res);
        },
        (error)=>{
          console.log('Error:', index, error);
        });
    });

LambdaのInvocationTypeについて

ポイントは、Lambdaの呼び出しタイプとして'Event'を指定していることです。起動部と本体を切り分けたところで、本体を起動部が同期呼び出ししてしまうと、結局起動部がタイムアウトになってしまいます。

そこで、Lamdabの呼び出しタイプを指定します。パラメータでInvocationType: 'Event'と指定すると、Lambdaは非同期的呼び出しとなります。つまり、呼び出された側(本体)の処理が終わるのを待たずに、呼び出した側(起動部)は次の処理に進みます。

dev.classmethod.jp

このようにすることで、呼び出し側(起動部)は必要な回数だけ本体を呼び出し、本来の目的であるConnpassのAPIとの通信〜通知を本体に委ねて自分自身は処理を終えて終了します。したがって、API通信に時間がかかってもタイムアウトとなる心配はありません。

ベストプラクティスは何なのか?

以上のような構成を採ることで、ConnpassのAPIの制限とLambdaのタイムアウトを回避することができました。しかし、この方法がベストプラクティスなのかどうかは疑問です。というのも、Lambdaを複数回実行するようにしたため課金的に不利になっているからです。そしてあまりエレガントに見えない。

サーバーレスで並列・分散処理というのはよく目にし、Lambda以外にもSNSやらSQSやら何やらと使えそうなものがたくさんありません。しかし、どうにもよくわかっていないというのが正直なところです。調べている過程で、StepFunctionsを使っている例にも接しましたが、いまいちピンときていません。

qiita.com

今回の例に限らず、AWSのサービスには少しずつ親しんできていますが、本格的な業務の構成を実際に触ったことがないので、どういう構成が良いもの(少なくとも、業務利用に耐えるもの)であるのかがわからないでいます。CloudDesignPatternというのもありますが、うーん、という感じ。有識者の方からのコメントをお待ちしています。。

7/27 WEBエンジニア勉強会 #08 でNode.jsについてLTした

7/27に渋谷で開催されたWEBエンジニア勉強会 #08で、LT(5分)登壇をしてきました。6/26の「カイゼン・ジャーニー・ライトニングトークス」に続いて2回目のLT登壇でした。

web-engineer-meetup.connpass.com

ky-yk-d.hatenablog.com

WEBエンジニア勉強会について

WEBエンジニア勉強会は、OSCA(@engineer_osca)さんが主催している勉強会で、「初心者でも参加できる勉強会」というコンセプトで運営されています。今回で8回目となる勉強会ですが、僕は前回(#07)に聴く側で参加したのが初参加でした。

twitter.com

techblog.oscasierra.net

OSCAさんとは、前回のときの懇親会の他に、別のイベントでもお話をする機会があり、登壇ウェルカムと言っていただいていました。また、アウトプットメンターのカカカカック(@kakakakakku)さんからも、「WEBエンジニア勉強会参加したんですね、次回は発表しませんか?」と義務付けられ提案されていたので、今回の登壇につながりました。

LTについて

スライド資料

テーマ

テーマは悩んだものの、AWS Lambdaを通じて少しずつ触るようになっているNode.jsについて話すことにしました。具体的な題材としてhttpモジュールを選ぶことで、「WEBエンジニア」勉強会という場にもふさわしいものにできるとも考えました。

内容としては、Node.jsのhttpモジュールの基本的なコードを読み解いていき、Node.jsの特徴を垣間見ることができれば、と考えました。httpsモジュールのサンプルコードをPromiseで書き換えるという前々回の記事の内容を掘り下げて、Node.jsの仕組みを理解し、それを共有することを目標としていました。

ky-yk-d.hatenablog.com

準備をする際に参考にした資料

準備としては、基本的にはNode.jsのAPIリファレンスを読んでいました。このリファレンスはあくまでAPIリファレンスなので、内部の実装がどうなっているかはわかりにくかったため、補う意味でGitHub上のソースコードも読むようにしていました。

github.com

また、ネット上の記事も多く参照しました。調査が難航したこともあり、非常にたくさんの記事を読んだので、全てを挙げることはできませんが、一部を下記に示しておきます。

書籍としては、下記のものを参照しました。古い本ですが、Node.jsの仕組みやコアモジュールについて記述が充実していたので非常に勉強になりました。

サーバサイドJavaScript Node.js入門 (アスキー書籍)

サーバサイドJavaScript Node.js入門 (アスキー書籍)

課題と反省点

内容について(技術ネタとして)

調査をしている中で、libuvというどうやらNode.jsにとって極めて重要らしい存在にまで辿り着きました。イベントループがNode.jsの基礎にあるのですが、これを担っているのがlibuvというC言語のライブラリというわけです。となると、これを理解することはNode.jsについてより深く理解することにつながるはずだったのですが、一筋縄ではいかず、満足に調査することができませんでした。

github.com

また、httpモジュールの理解という側面ではTCP、ソケットについても多少なりとも理解できればよかったのですが、これもまともに取り組むことができませんでした。ネットワークについては基礎的な部分もきちんと理解しておきたいと思いながらも、後回しにしてしまっています。さっさと『マスタリングTCP/IP』を読まねば……

マスタリングTCP/IP 入門編 第5版

マスタリングTCP/IP 入門編 第5版

形式について(アウトプットとして)

学生症候群の一言に尽きます。

今回、登壇する日程は1ヶ月前から決まっていました。準備に費やす時間はたくさんあったにも拘わらず、(技術面でのつまずきもあったものの)集中することができずに時間が経ってしまい、当日まで直し続けることになりました。

特に、発表としての準備に時間を割くことができなかったのは反省です。構成ももう少し練ることができたと思いますし、5分間という時間の制約への対応も不十分で、スーパー早口&時間オーバーとなってしまいました。

総括

正直に言って、今回のLTは、僕にとってはかなり苦い経験になりました。技術知識の伝達としても、発表としてもクオリティが低かったことを認めざるを得ません。聴き手のみなさんには申し訳ない気持ちでいっぱいです。

とはいえ、これで懲りてやめてしまっては、それこそ申し訳が立たないので、また機会を見つけて発表をしたいです。次回は、内容の固定を早期に仕上げることを意識しようと思います。

最後に

僕自身もそうですが、知識・経験に自信を持てず、「自分が人前で発表するなんて・・・」と思っている人は少なくないのではないでしょうか。しかし、「そういった人でも気軽に登壇にチャレンジできる会にしたい」というOSCAさんの思いで運営されているのがこの勉強会だと思いますので、興味が少しでもある方は登壇してみてはいかがでしょうか。

そして、最後の最後になりますが、登壇という機会を与えてくださったOSCAさん、運営に携わっていた方々、それにLTを聴いてくださったみなさん、ありがとうございました!

サーバサイドJavaScript Node.js入門 (アスキー書籍)

サーバサイドJavaScript Node.js入門 (アスキー書籍)

SOFT SKILLS ソフトウェア開発者の人生マニュアル

SOFT SKILLS ソフトウェア開発者の人生マニュアル

Node.jsからConnpassのAPIを叩こうとしてつまずいたこと

はじめに

この記事をご覧になっている方の多くは、技術系の勉強会に参加された経験があるのではないかと思います。僕はよく参加しています。

他の業界の友人と話をして、最も驚かれるのが「業後に勉強会に出ている」ということです。毎日のようにどこかで勉強会が開かれ、様々な企業の人が職業に関わる情報をやりとりしているのはこの業界の特色でしょう。

さて、そんな勉強会の情報は、いくつかのイベント情報共有サービスを通じて周知されているものがほとんどだと思います。ConnpassやDoorkeeper、ATNDなどが有名どころでしょうか。それらのサイトを訪問すれば、日々新しい勉強会の開催情報が公開されているのを見ることができます。

しかし、サイトを訪問したり、公式のメール配信に登録する以外にも、勉強会の情報を得る方法があります。それはAPIを利用することです。APIを利用することで、特定の条件を満たすイベントの情報を自動で取得でき、様々なツール(SlackやTwitter)に送信することもできます。

そこで今回は、

  • ConnpassのAPIを利用する手順
  • 利用するに当たってつまずいたこと

についてご紹介します。

Connpass APIは登録なしですぐ使える

今回利用するのは、ConnpassのAPIです。無料で公開されています。

WebサービスAPIの中には、登録が必要なものもありますが、Connpassの場合は利用登録は必要ありません。指定されたURL( https://connpass.com/api/v1/event/)に対してHTTPリクエスト(GET)を送信するだけで、イベント情報を取得することができます。

Connpass APIでイベントの情報を取得するサンプルコード

実際に叩いてみましょう。以下のような実装で取得することができます。まだ途中ですが、Lambdaで定期実行するようにしたいので、Lambda向きの書き方になっています。

const https = require('https');
const queryString = require('querystring');

const getResponse = async (opts, queries)=>{
  opts.path +='?' + queryString.stringify(queries);  // クエリ文字列を生成してパスの末尾に付加
  console.log('path:', opts.path);
  return new Promise((resolve, reject)=>{  // httpsモジュール、Promiseについては前回記事(下記)を参照
    https.get(opts, (response)=>{
      console.log('statusCode:', response.statusCode);
      console.log('statusMessage:', response.statusMessage);
      response.setEncoding('utf8'); // Buffer型ではなくUTF-8の文字列で取得する
      let chunkCount = 0;
      response.on('data', (chunk)=>{
        chunkCount ++;
        body += chunk;
      });
      response.on('end', ()=>{
        console.log('チャンクの個数',chunkCount);
        let bodyObj = JSON.parse(body); // レスポンスはJSON形式で返ってくるのでパースする
        console.log('検索結果件数:', bodyObj.results_available);
        console.log('うち取得件数:', bodyObj.results_returned);
        console.log('1つめのイベント:', bodyObj.events[0].title);
        resolve(bodyObj);
      });
    }).on('error', (err)=>{
      console.log('error:', err);
      reject(err);
    });
  });
};

exports.handler = async ()=>{
  let opts = {
      hostname: 'connpass.com',
      path: '/api/v1/event/',
      headers: {
        'User-Agent': 'Node/8.10'  // これがないと拒否される
      }
  };
  const queries = {
    'keyword_or': [
      'アジャイル',
      'javascript'
    ]
  };
  let result = await getResponse(opts, queries);
  return result;
};

参考(httpsモジュールによるリクエストの送信)

ky-yk-d.hatenablog.com

つまずきポイント①403エラーとUser-Agentヘッダー

今回、ConnpassのAPIを利用しようとして、最初につまずいたのは、403 Forbiddenになってしまうという事象でした。Web APIを利用した経験がほぼゼロであるため、解決方法が分からずしばらく苦しみましたが、以下の記事に行き着きました。

上の記事は、GitHubAPIへのリクエストが403 Forbiddenとなっていたのを、User-Agentヘッダーを付与することで解決したというものです。User-Agentについては、以下の記事が端的でわかりやすかったです。リクエストを送信したのが何者なのかをサーバ側に教えてあげるためのものというわけですね。

www.atmarkit.co.jp

上記のQiita記事を読んだときに、「たぶんこれだ」と直感し、https.request()の第一引数のoptionsに、User-Agentヘッダーを付加して送信するように指定してあげました。すると思った通り、200 OKが返ってくるようになったではありませんか。Web APIを叩くときは要注意ですね。

なお、Node.jsからの実行なので、試しにNode/8.10と記載してみましたが、書き方をご存知の方がいらっしゃればご教示いただけると幸いです。

修正前

let opts = {
    hostname: 'connpass.com',
    path: '/api/v1/event/'
};

修正後

let opts = {
    hostname: 'connpass.com',
    path: '/api/v1/event/',
    //  ---以下を付加---
    headers: {
        'User-Agent': 'Node/8.10'
    }
    // ---追加部分終わり---
};

つまずきポイント②日本語のクエリ文字列のエンコード

つまずいた第二のポイントは、日本語のクエリ文字列のエンコードです。

ConnpassのAPIは、検索条件をクエリ文字列としてリクエストに付加して利用するのですが、そのなかの検索ワードは文字列で送信することになります。英数字であれば、何も考えずに文字列として付加して送信すればよいのですが、日本語文字列を何も考えずに送信すると失敗します200 OKにはなりますが、検索文字列として機能しないので有意味なレスポンス本文が返ってきません。

1. 何も考えずに日本語文字列を送る例

最初、このようなやり方をしていて首を傾げていました。日本語文字列がそのままパスに含められて送信されています。①と違って、HTTPリクエストとしてエラーになるわけではありませんが、レスポンス本文が取得できていないため、本文から検索結果の情報を取得するところでエラーとなります。

HTTPによる通信に慣れている人であれば絶対につまずかない箇所なのだと思いますが、生成したクエリ文字列を含むパスをChromeのURL入力欄に放り込むとちゃんと検索結果が(エンコードされていない状態で)得られるので、自分が間違ったことをしていることに気づきませんでした。

ソースコード(一部)

opts.path +='?' + 'keyword=アジャイル';

実行結果

2018-07-22T13:30:53.242Z 74a3205b-8db3-11e8-80e4-430677743f08    path: /api/v1/event/?keyword=アジャイル
2018-07-22T13:30:53.547Z    74a3205b-8db3-11e8-80e4-430677743f08    statusCode: 200
2018-07-22T13:30:53.547Z    74a3205b-8db3-11e8-80e4-430677743f08    statusMessage: OK
2018-07-22T13:30:53.550Z    74a3205b-8db3-11e8-80e4-430677743f08    チャンクの個数 1
2018-07-22T13:30:53.586Z    74a3205b-8db3-11e8-80e4-430677743f08    検索結果件数: 0
2018-07-22T13:30:53.586Z    74a3205b-8db3-11e8-80e4-430677743f08    うち取得件数: 0
2018-07-22T13:30:53.587Z    74a3205b-8db3-11e8-80e4-430677743f08    TypeError: Cannot read property 'title' of undefined
    at IncomingMessage.response.on (/var/task/index.js:24:52)
    at emitNone (events.js:111:20)
    at IncomingMessage.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickDomainCallback (internal/process/next_tick.js:218:9)

2. 自分でエンコードして送る例

日本語文字列を含める場合は、エンコードが必要になるということでした。そのための関数はちゃんと用意されていて、下記のようなやり方で適切な形に変換することができました。実行結果を見ると、きちんと日本語文字列がエンコーディングされて送信されていることがわかります。結果もちゃんと返ってきています。

ソースコード(一部)

opts.path +='?' + 'keyword='+ encodeURIComponent('アジャイル');

実行結果

2018-07-22T13:31:57.688Z 9b0f3f00-8db3-11e8-ab20-c3947964f9d8    path: /api/v1/event/?keyword=%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB
2018-07-22T13:31:59.441Z    9b0f3f00-8db3-11e8-ab20-c3947964f9d8    statusCode: 200
2018-07-22T13:31:59.446Z    9b0f3f00-8db3-11e8-ab20-c3947964f9d8    statusMessage: OK
2018-07-22T13:31:59.468Z    9b0f3f00-8db3-11e8-ab20-c3947964f9d8    チャンクの個数 8
2018-07-22T13:31:59.487Z    9b0f3f00-8db3-11e8-ab20-c3947964f9d8    検索結果件数: 775
2018-07-22T13:31:59.487Z    9b0f3f00-8db3-11e8-ab20-c3947964f9d8    うち取得件数: 10
2018-07-22T13:31:59.487Z    9b0f3f00-8db3-11e8-ab20-c3947964f9d8    1つめのイベント: 第2回enPiT-Proスマートエスイーセミナー: アジャイル品質保証と組織変革

3. querystringモジュールを利用する例

理屈の上では、2のようなやり方で足りますが、クエリ文字列の数が増えてくるとめんどくさそうです。エンコーディングについても、あまり意識したくはありません。これらの悩みを解決してくれるのが、Node.jsのモジュールであるquerystringモジュールです。

今回は、querystring.stringify()メソッドを利用しています。このメソッドでは、

  • オブジェクトからクエリ文字列への変換
  • パーセントエンコーディング(デフォルト。第四引数として関数を渡すことで変更することも可能)

を行ってくれます。また、下記の例では一つだけですが、keyword': ['aaa', 'bbb']と配列で指定してやると自動的に展開していい感じに処理してくれます。優れものですね。

ソースコード(一部)

const queryString = require('querystring');
let queries = {
    keyword: 'アジャイル'
};
opts.path +='?' + queryString.stringify(queries);

実行結果

2018-07-22T13:35:03.055Z 097210d9-8db4-11e8-a137-69efa6eedcfd    path: /api/v1/event/?keyword=%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB
2018-07-22T13:35:03.468Z    097210d9-8db4-11e8-a137-69efa6eedcfd    statusCode: 200
2018-07-22T13:35:03.486Z    097210d9-8db4-11e8-a137-69efa6eedcfd    statusMessage: OK
2018-07-22T13:35:03.508Z    097210d9-8db4-11e8-a137-69efa6eedcfd    チャンクの個数 9
2018-07-22T13:35:03.527Z    097210d9-8db4-11e8-a137-69efa6eedcfd    検索結果件数: 775
2018-07-22T13:35:03.527Z    097210d9-8db4-11e8-a137-69efa6eedcfd    うち取得件数: 10
2018-07-22T13:35:03.527Z    097210d9-8db4-11e8-a137-69efa6eedcfd    1つめのイベント: 第2回enPiT-Proスマートエスイーセミナー: アジャイル品質保証と組織変革

おわりに

以上、ConnpassのAPIの叩き方のサンプルとつまずきポイントをご紹介しました。AWSAPI GatewayとLambdaを用いてAPIを作り、それをコールするというのは何度かやったことがありましたが、他人が作成したAPIを叩くというのは初めてだったので、思いの外つまずき、そして勉強することがありました。

現在、ConnpassのAPIを使ったボットを作成しようとしています。時間の使い方が下手でなかなか作業が進められておらず、まだ動くものになっていませんが、ソースはGitHubで公開していますので、ご興味ある方は眺めてみてください。

github.com

Node.jsのhttpsモジュールを用いた通信処理をPromiseで書き直して解読してみた

JS初心者がよくわからないまま書いたLambda

以前、このような記事を書きました。LINEのMessaging APIを用いた簡単なbotの実装の紹介記事です。実はこちらのブログで最も多くのブクマ(16ブクマ)を集めている記事となっています(2018.07.15現在)。

ky-yk-d.hatenablog.com

表題の通り、この記事を書いたのはJavaScriptを触り始めたころです。全体的に、見よう見まねでコーディングしていたのですが、中でも理解できていなかったのはhttpリクエストを送っている箇所です。この部分については完全にコピペ、意味も「あーなんか本文くっつけて送ってんだなー」くらいの理解でした。

記事の執筆から2ヶ月弱が経ち、この箇所について多少なりともわかってきたので、かつての自分と同レベルの方(来月くらいには自分もまたそこに戻っているかもしれない・・・)のためにメモを残しておくことにしました。httpリクエストを送信する部分を書き換えているので、そこを見ていきます。

書き換え(httpリクエスト送信部)

Before

書き換え前のコード

let https = require('https');
 
exports.handler = (event, context, callback) => {
// ---中略---
    var req = https.request(opts, function(res) {
        res.on('replyData', function(res) {
            console.log(res.toString());
        }).on('error', function(e) {
            console.log('ERROR: ' + e.stack);
        });
    });
    callback(null, replyData);
    req.write(replyData);
    req.end();
}

問題点/当時の理解レベル

  • requireは「httpsを使うためのおまじない」だった(Node.jsのhttpsを利用している、そしてそれがモジュールだという意識はなかった)
  • exportsを「なんか最初に実行されるやつでしょこれ」という認識だった(モジュールがわかっていないので外部公開も当然わかっていなかった)
  • on()でイベント登録をしている箇所で、bodyだったところをreplyDataに書き換えてしまっていた(イベントというものを理解していなかった)
  • 処理の途中でcallbackしていた(最後でなきゃいけないという決まりはないが、通信の結果を返すようにすべきだと今は思う)
  • callback()の第一引数がなぜnullなのかわかっていなかった
  • req.write()は「本文書き込んでいるんしょ」という認識(間違っていないけどWritable Streamということは意識していない)

After

書き換え後のコード

exports.handler = (event, context, callback) => {
// ---中略---
    let promise = sendRequest(opts, replyData).then((res)=>{
        console.log('---DONE---');
        console.log('typeof:', typeof(res));
        callback(null, res);
    },(err)=>{
        console.log('---ERROR---');
        callback(err, 'errorMsg' + err.stack);
    });
    console.log('typeof promise:', typeof(promise));
    console.log('promise:', promise);
}

// リクエスト送信部を切り出した関数
async function sendRequest(opts,replyData){
    return new Promise(((resolve,reject)=>{
        console.log('Promiseの引数の関数開始');
        let req = https.request(opts, (response) => {
            console.log('---response---');
            response.setEncoding('utf8');
            let body = '';
            response.on('data', (chunk)=>{
                console.log('chunk:', chunk);
                body += chunk;
            });
            response.on('end', ()=>{
                console.log('end:', body);
                resolve(body);
            });
        }).on('error', (err)=>{
            console.log('error:', err.stack);
            reject(err);
        });
        req.write(replyData);
        req.end();
        console.log('Promiseの引数の関数終了')
    }));
};

修正箇所

  • リクエスト送信部をハンドラ関数から切り出した。
  • httpsモジュールを用いた通信をPromiseでラップした.
  • 意識的にイベント登録をした。
  • 非同期処理が終わった後にcallbackが呼び出されるようにした。

処理の流れと解読

動作を確認するために余計なコードがたくさん含まれているのですが、このLambda関数の挙動は下記のような流れになっています(成功した場合)。順に解読していきます。ログも載せておきます。

テスト実行ログ

メッセージの場合
データ作成
---START---
Promiseの引数の関数開始
Promiseの引数の関数終了
typeof promise: object
promise: Promise { <pending> }
---response---
chunk: {"message":"Invalid reply token"}
end: {"message":"Invalid reply token"}
---DONE---
typeof: string

1. sendRequest()が呼び出され、引数の処理が行われる

ハンドラ関数がsendRequest()を呼び出すと、当然ながらnew Promise()が実行されます。引数に渡した関数の処理は、この時点で実行されるようですね。https.request()ClientRequestオブジェクトを返します(ここで接続=非同期処理が開始されていることになります)。

そして、ClientRequestオブジェクトに対して、on('error', ...)とすることでerrorイベントへのリスナーを登録しています。ClientRequestは、Writable Streamインターフェースを実装した出力ストリームの一つで、EventEmitterでもあります。EventEmitterは、Node.jsの特徴の一つであるイベントループの重要な構成要素と言ってよいと思います。

ちなみに、http(s).request()に第二引数(オプション)としてコールバック関数を渡すことで、responseイベントへのリスナーも登録していることになります。意味的にはonce('response', (...)=>{...})としても同じになるはずです。

The optional callback parameter will be added as a one-time listener for the 'response' event.

(参考)HTTP | Node.js v10.6.0 Documentation

そして、req.write()で本文を送信し、req.end()で出力ストリームを閉じています。いずれも、Writable Streamインタフェースで宣言されているメソッドです。ここまで終わると、Promiseオブジェクトが生成され、呼び出し元に返却されます。

2. 呼び出し元の処理が実行される

Promiseが返ってきたら、呼び出し元の処理が走ります。返却されてきたPromiseに対して、then()メソッドで成功ハンドラと失敗ハンドラを付加しています。Promiseを返した非同期処理の側でresolve()が実行された場合は第一引数の関数が、reject()が実行された場合は第二引数の関数が実行されることになります。

ハンドラの登録まで実行してから、promise変数に代入をし、型と内容を表示しています。この時点では、まだPromiseの中身の処理は行われておらず、Promiseは未解決(pending)となります。通常なら、awaitを付けてasync関数を呼び出し、解決後の値を変数に代入するのでしょうが、今回は動きの確認のためPromiseをそのまま代入しています。

3. 非同期処理の結果を受け取る

ここまでの間に、https.request()で開始した通信が行われています。JavaScriptはシングルスレッドなので、同時に複数の処理を実行することはできないのですが、時間のかかる処理を外部に依頼してしまって、終わるまでは別の処理を実行しておくことはできます。これが非同期処理ということですね。細かい理屈は現在調査中なので、後日まとめられればと思っています。

しばらく経つと、リクエストに対するレスポンスが返ってきます=responseイベントが発生します。このresponseイベントに対するリスナーが、http(s).request()の第二引数で登録したコールバック関数です。コールバック関数の引数(コールバックを渡された側が、呼び出し時に渡して返してくれるもの)は、IncomingMessageオブジェクトで、これはReadable Streamインタフェースを実装した入力ストリームです。送信するリクエストがWritable Streamだったのに対し、こちらはReadableとなるわけですね。

こちらもClientRequestと同様、EventEmitterなので、イベントリスナーを登録することができます。ここでは、dataイベントとendイベントのリスナーを登録しています。なお、これらのイベントは、IncomingMessage固有のものではなく、Readable Streamのものです。また、setEncodeing()エンコーディングを設定していますが、これもReadable Streamのものです。

二つのイベントの意味は、レスポンス本文が送られてきたら(dataイベントが発生したら)、順次body変数に格納し、全て取得し終えたら(endイベントが発生したら)resolve(body)を実行しろ、ということです。resolve()が実行された時点で、呼び出し元でpendingとなっていたPromiseが解決されます。

4. then()で登録しておいた成功ハンドラが実行される

Promiseが解決されると、呼び出し元であらかじめthen()で登録しておいた処理が実行され、解決した値(resolve()の引数)であるbodyの値が、resとして渡されてきます。ログにはresがstring型であると出力されますし、callbackした結果としても下記の通り、レスポンスの本文が表示されます。

f:id:ky_yk_d:20180716010212p:plain

ここまで実行されて、Lambda関数の処理が全て終了したことになります。めでたしめでたし。

今後の課題

書き換えて処理を追ったらずいぶん長文になってしまいました。簡潔にまとめるつもりだったのですが、書きながら裏取りのつもりで調べていたらその過程で学ぶことが多くありすぎ、まとめることを放棄しました(汗)

今回の書き換えと本記事の執筆のなかで、Promiseやasync/await、Node.jsのI/Oの仕組みなどに期せずして接しました。せっかく学ぶきっかけが得られたので、これからもう少し深く勉強して、後日自分なりにまとめてみようと思います!

Vue Routerのナビゲーションガードによるアクセス制限を試した&コードを読み解いた

前回の記事に引き続き、Vue Routerについての記事となります。

ky-yk-d.hatenablog.com

Vue Routerでアクセス制限を実現する

今回は、ナビゲーションガードを利用したアクセス制限を実現するコードを解読していきます。実現したいのは、「特定のパスに対してアクセス制限をかける」ことです。

今回は、コードの中核部分を『基礎から学ぶVue.js』(およびそのサポートページ)に負っています。初心者にわかりやすく、また実践的でもあり、そしてサポートページが実に充実している書籍です。ありがたい。

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

今回のコードを置いているGitHubリポジトリはこちら(すでに更新が加わっています)。このリポジトリに限らず、コードの書き方等に指摘あればぜひお知らせください!

github.com

Vue Routerの設定とナビゲーションガードの導入

前回、Vuer Routerの設定は以下のようにしていました。

let router = new VueRouter({
  routes: [
    {
      path: '',
      component: Top,
      children: [
        {
          path: '',
          component: ChildA,
          name: 'childA'
        },
        {
          path: '/childB',
          component: ChildB,
          name: 'childB',
          meta: {
            requiresAuth: true
          }
        }
      ]
    },
    {
      path: '/helloworld/:msg',
      component: HelloWorld,
      name: 'helloworld'
    }
  ]
});

/childBというパスに対して、meta要素にrequiresAuth: trueを指定しています。この要素を利用して、認証が必要なパスと不要なパスとを区分していきます。

ナビゲーションガード(グローバルガード)のコード

ナビゲーションガードを導入します。利用するのは「グローバルガード」です。

今回記載したソースコードは下記のようなものです。こちら、ほぼ『基礎から学ぶVue.js』記載のコードとなっています。

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!store.state.isLogin) {
      next({
        path: '/',
        query: {
          redirect: to.fullPath
        }
      })
    } else {
      next();
    }
  } else {
    next(); 
  }
});

このコードを読み解いてみます。

Vue Routerの様々なオブジェクト

router.beforeEach()は、ルーターインスタンスのメソッドで、画面遷移前に実行される処理を引数に記載します。処理の中では、遷移先をnext()で指定します。上の例の場合、一定の条件下でリダイレクトさせるために利用しています。

次に、ifの条件となっているto.matched.some(record => record.meta.requiresAuth)についてです。toは、遷移先を示すルートオブジェクトです。単なる文字列ではありません。公式ドキュメントの記述を引用します。

遷移先のURLの、現在の URL をパースした情報と、その URL とマッチしたルートレコードを保持しています。ルートレコードはroutes設定の配列 (とchildren配列) 内のオブジェクトのコピーです。

matchedは、ルートポブジェクトのプロパティで、この「現在のルートのネストされた全パスセグメントに対しての ルートレコード を保持している配列」です。

この場合、VueRouterのコンストラクタに渡しているオブジェクトのroutes配列の1つ目の要素(path: ''の部分)と、その中のchildren配列の2つ目の要素(path: '/childB'の部分)とを要素とする配列ということになります。

Array.prototype.some()

そして、some()メソッドはArray.prototype.some()です。

developer.mozilla.org

some()メソッドは、to.matchedという配列の各要素をそれぞれrecordとして扱い、record.meta.requiresAuthが真となる要素があれば真を返します。今回の場合、childB側の要素が条件に合致するため、some()メソッドが真を返し、認証の確認部分が実行されるというわけですね。

※今回の例では、requiresAuth: trueと指定しているchildBがネストの最下位ですが、some()を利用することで、上位のパスにrequire sAuth: trueを指定されていれば認証確認の対象となります。

認証の確認とリダイレクト、そしてVuexへ

認証確認部分はシンプルです。if(!store.state.isLogin)でログインしているかどうかを確認し、ログインしていなければnext()の引数(ルートオブジェクト)で認証不要なパスを指定します。上記の例では、クエリ文字列でリダイレクト元(本来の、許可されなかった遷移先)の情報を付加しています。

f:id:ky_yk_d:20180707184943p:plain

以上によって、「特定のパスにアクセス制限をかける」ことが実現できました。そのまま使えるサンプルコードや、親切な解説書の存在はとてもありがたいですが、その先で自力でAPIドキュメントを読んでみると、理解度が全然違いますね。Array.prototype.some()も、今回はじめて知りました。

今回は、Vue Routerのナビゲーションガードを用いて特定のパスにアクセス制限をかけてみました。今回用いたのはグローバルガードと呼ばれる最も基本的なナビゲーションガードでしたが、他の種類のナビゲーションガード、あるいはナビゲーションガード以外のVue Routerの機能も少しずつ試していきたいと思います。

また、ちらっと登場したstore.state.isLoginは、実はVuexを利用しています(アクセスの仕方がこれでいいのかは不明・・・)。まだVuexは消化不良なのですが、ある程度の段階で記事にまとめようと思います!

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

Vue Routerで「ネストされたルート」を試した

久しぶりにVue.jsの記事です。基本的な機能は使えるようになってきているので、Vue Routerに手を伸ばしてみました。

紹介 | Vue Router

Vue Routerを試そう

Vue Router自体は、以前ご紹介したUdemyの"The Ultimate Vue JS 2 Developer Course" (結局最後まで終えていません・・・)のなかで利用したことがあります。

ky-yk-d.hatenablog.com

この時は、基本的な画面遷移の機能と、パラメータを渡す「動的ルートマッチング」の機能を利用していましたが、今回「ネストされたルート」の機能を試したのでご紹介します。GitHubリポジトリは下記。

github.com

ネストされたルートとは

ネストされたルート | Vue Router

f:id:ky_yk_d:20180701214153p:plain
公式ガイドより

ネストされたルートは、上の公式ガイドに記されているように、ネストされたコンポーネントの特定の構造に対して別個のURLを割り当てるために利用する機能です。

単純な<router-view><router-link> では、それが設置された直下のコンポーネントを丸ごと制御することしかできません。上の例でいえば、Userコンポーネント丸ごとを扱うことしかできません。もしProfileコンポーネントとPostsコンポーネントに別個のURLを割り当てようとすれば、Userコンポーネントを固定しなくてはなりません(そのレベルの切り替えはできなくなる)。

実装してみる

今回、この機能を利用した例として、Topコンポーネントの配下にChildAあるいはChildBというコンポーネントが入るネスト構造にURLを割り当てる画面を作成してみました。

TopコンポーネントはさらにAppコンポーネントに属しているため、App.vue内で下記のようにVue Routerを設定します。

App.vue

let router = new VueRouter({
  routes: [
    {
      path: '',  
      component: Top,
      // ここにname要素を指定するとエラーになる
      children: [
        {
          path: '', 
          component: ChildA,
          name: 'childA'
        },
        {
          path: '/childB', 
          component: ChildB,
          name: 'childB',
          meta: {
            requiresAuth: 'true'
          }
        }
      ]
    }
  ]
});

Top.vue

<template>
  <div id="top">
    <h2>Hello Vue Router World!</h2>
    <router-view></router-view>
  </div>
</template>

以上のように設定しておくと、トップページにアクセスした場合にはTop配下にChildAコンポーネントが表示されます。言い換えれば、App.vueの中にある<router-view>にはTopコンポーネントが、そしてTop.vueの中にある'<router-view>`にはChildAコンポーネントがそれぞれ入ることになります。

つまり、この構造に対して、''というパスが割り当てられているということになります。<router-link>で名前を用いる場合には、nameで指定したchildAでアクセスすることができます。

http://localhost:8080/#/

f:id:ky_yk_d:20180701215608p:plain

一方、[Top-ChildB]というまとまりに対しては、'/childB' というパスが割り当てられ、名前はchildBとなります。下記のスクリーンショットから、Topコンポーネントの配下にChildBコンポーネントが配置されていることがわかると思います。

http://localhost:8080/#/childB

f:id:ky_yk_d:20180701215520p:plain

注意点

[Top-ChildA]というまとまりに対してパスを割り当てているため、routerの設定でTopコンポーネントのレベルにname要素を指定しようとするとエラーとなります。これについては、うっかり指定してしまった場合はエラーメッセージで教えてくれるので、それほど困らないポイントかと思いますが、最初気づかなかったので書き残しておきます。

感想

簡単にネストされたルートが使えることがわかりました。ネスとされたルートが使えると、実装できるページの幅も広がりますね。Vue Router、すごい!

Vue Routerのその他の機能(ナビゲーションルートなど)も、少しずつ使ってみています。コンポーネントをバシバシ使うようになると、アプリケーション全体での状態の管理が煩雑になってきているなと感じます。今回実装してみた中でも、フラグ(ログインの有無)を用いてアクセス制限をしてみているのですが、ひどい実装になってしまいました。

というわけで、次はVuexを導入してみようと思います。ゆっくりとした歩みではありますが、2ヶ月前(↓)からは随分進歩しました(笑)途中になっているUdemyの講座もあることですので、これから1,2ヶ月でVueについては「一通りわかった」と言える状態を目指します!

ky-yk-d.hatenablog.com

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js