emahiro/b.log

Drastically Repeat Yourself !!!!

空のstructにはomitemptyが効かない

以下のようなコードを書いたときに

https://play.golang.org/p/JVvctX-ir-w

{} を返ってくることを期待しているのに、 {"field":{}} が返ってくる、ということにずっとハマっていました。

これは encoding/jsonomitempty の振る舞いとして空のstructにomitemptyのtagを指定しても、空structとして扱ってしまって(値が入っているものとして扱ってしまって)key除外されないというものに起因していました。

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. json - The Go Programming Language

pointer型のstructにした場合には、key除外されます。

https://play.golang.org/p/YjD9D_Ot-Ot

そもそものomitemptyの挙動を理解してませんでした。。。。

omitempty むずかしす。。。。

2018年始の目標の4半期報告

前回更新から少し間が撒いていまいましたが、新年度が始まったので年始に決めたことの経過と4半期振り返りをしようかなと思います。

ema-hiro.hatenablog.com

エンジニアとしてコミットしたといえるプロダクトを作る。

業務の話なので詳しいことは書けませんが、1~3月で1つ大きな案件を消化したので、こちらは少しは達成できてるかなと思います。
今現在も別の面白そうなプロジェクトに関わってて、しんどいこと大半だし、それのお陰で最近ブログ書く時間すら取れていなかったのですが、キャリアの中では1つのモノ作りに集中できてる期間だと今のところは言えそうです。

これは引き続き継続していきたいですね。

月2冊以上の技術書を読む

1月は出来たけど2、3月は出来てないでいました。 新しい知識を詰め込むというより、温故知新じゃないですけど、基礎的なところ、HTTPの仕様だったり設計のinputだったり、業務で必要になるために技術書じゃない仕様書やドキュメントを読む機会が多かったです。
新しい知識を得るため、よりも今まで読んでた本を読み直したり、改めて復習したりと言ったことが多かったです。

少し仕事も落ち着いたので、また新しく色んな技術書を漁りたいと思っています。

githubへの1commit/weekをしていくこと

全く出来ませんでした。進捗0です。
特に言語を書くというよりも書く一歩手前の知識のinputが必要でその知識を使って仕事をこなしている期間がメインで、新しく何か作ったり、何かを試そうというモチベーションがあまり今現在湧いてこなかったのが原因です。

こちらはまだ、やりきれないですし、その前時点の知識のinputをしないと行けないので、これからの夏くらいまでは一旦こちらは目標から外したものとして扱おうと思います。

新しいサービスは積極的に使っていく

これは継続的に出来ている気がします。とは言え、新しく使い始めたもので未だに使っているものはほとんどなく、むしろiPhone内のアプリを断舎離しています。

そんな中でも年度初めから使うようになった Voicy というサービスがあるのですが、これはすごく良いなと実感します。

voicy.jp

今まで耳から情報を得るということをしてこなかったのですが、音楽聞かない場合、耳って自分自身の感覚の中で常に暇している場所だなと気づいて、朝の通勤中とかにベンチャーニュースとか経済ニュースをずっと聞いて耳からながらで情報を収集しています。

耳からなので、習慣化するのもすごく楽で今では1習慣に3~4回くらいは通勤で必ずvoicyを使っています。

新しい言語を一つ仕事レベルで習得すること

まだ出来てないですね。目標はnodeですがまだ着手できていないです。
ただ、これは少し進めたいので夏までは頑張って時間を捻出して頑張ります。

まとめ

案外進捗出てないなと思いました。 自分でまとめてるのにw

もう少し、決めた目標にコミット出来るように仕事を効率的に完了させて、自分の時間を取れるようにしたいなと改めて思いました。

Lerning about Cache part1

仕事で真剣にCache戦略を考える機会があったので、これまでなんとなくしか理解してこなかったブラウザキャッシュについて調べてみました。
今回はpart1です。

参考

Real World HTTP
HTTP キャッシュ - HTTP | MDN
HTTP キャッシュ  |  Web  |  Google Developers

summary

  • キャッシュとは?
  • 更新日時でCacheする
  • etagでキャッシュする

Cacheとは?

本エントリではブラウザCacheに絞って調べました。

そもそもCacheとはウェブサイトのリッチ化の流れの中で

すでにダウンロード済みで、内容に変化がなければダウンロードを抑制し、それによってパフォーマンスを上げるメカニズム (Real World HTTP P.48)

とされています。
コンテンツの差分がなければそもそもサーバーにアクセスせずとも、手元にキープしておいたコンテンツを使うメカニズムのことで、サーバーアクセスを減らし、応答速度を高速化させてるように見せることができます。

EndUserにとっては見るコンテンツが変わらなければ、裏でサーバー通信をしていようが、手元にためてたコンテンツを表示させようが大差はありません。

通常

f:id:ema_hiro:20180326022046p:plain

Cacheあり

f:id:ema_hiro:20180326022054p:plain

ざっくり簡単に書くとこんな感じです。

リソースが最新かどうかを確認する

Cacheする以前に、リソースの更新の有無を確認する方法をまずは調べます。
というのも、あるリソースを取得するときに、200OKでレスポンスを受け取った場合、bodyに取得したリソース(jsonなどの形式)が入ってきます。
リソースも情報を含んでいるので、データのサイズに応じて通信はかかるし、取得するまで時間がかかってしまいます。このリソースが変わらないのに、毎回通信が発生したり、そもそもリソースの情報を全て取得する必要はなく、「新しい状態かどうか」だけ返してくれればbodyにリソースの情報を含めずに、手元にあるデータを再利用していいというお墨付きをサーバーから受け取ることが出来ます。

そのお墨付きを与えるパターンが以下の2つです。

  • 更新日時を利用する: Last-Modified/If-Modified-Since
  • Etagを利用する: If-None-Match

上記2つのパターンについてはCacheの有効性を確認するためにリクエストヘッダーに更新確認用の値を付与してリクエストをします。

更新日時を利用して最新状態を確認する

リソースの新旧のみを比較します。その比較のための目印として、更新日時 を利用します。
これは一番わかり易いと思いました。理由は、リソースが更新された場合は、リソース内部の UpdateAt が更新されているはずで、ここだけ見れば、そのリソースが新しい状態かどうかを確認できます。

この確認をするためには リクエストヘッダーにIf-Modified-Sinceをつけて、手元のリソースの最終更新時刻を値として付与してリクエス します。

If-Modifewd-Since: (手元のリソースのUpdateAt GMTのタイムゾーンを使用する)

変更がある(最終更新時刻が変更されている) 場合は、サーバーはそのまま通常通り200 OKを返してbodyにリソースの情報をいれてクライアントに返します。
変更がない(差一周更新時刻が変更されていない)場合は、サーバーは 304 Not Modified を返し、bodyはレスポンスに含まれません。この場合は、クライアントは手元にあるリソースを再利用可能なお墨付きを貰っている状態です。

Etagを利用して最新状態を確認する

更新日時を利用した最新状態の確認では、リソースのUpdateAtを見ることでリソースが最新状態にあるのかどうかを確認しましたが、Etagを使う方法もあります。 ※ Etag ... Entity Tag の略

この場合、リソースのデータ構造の設計時にEtagが入っていることが必要になります。

Etagをリクエストヘッダーに付与するときは If-None-Match ヘッダーを利用します。

If-None-Match: (Etag)

クライアントからリクエストする時にEtagを If-None-Match ヘッダーに付与することで、 - Etagが更新されてなければリソースに変化なしとしてリソースを取りに行かずに304 Not Modifiedを返し、bodyはレスポンスに含めません。 - Etagが更新されていれば、そのまま200OKを返して、bodyをレスポンスに含めます。

Expiresについて

文字通り 有効期限 です。
更新日時にしろ、Etagにしろ、かならずリソースの状態を確認するために、サーバーにアクセスしないといけません。リソースの状態によってレスポンスにbodyを含めるか、304のみを返すかの違いがあり、通信そのものを減らすことが出来る場合はありますが、結果として Cacheの有効性、リソースの状態を確認するための通信が発生する ことに変わりはありません。

この通信自体をなくしてしまうために、有効期限をもたせることが出来ます。
有効期限を持たせるには Expiresヘッダーを付与します。

ExpiresヘッダーにはCacheが有効な(期限切れになる) 日時 が格納されます。 ※ あとで記述するmax-ageは今からの相対秒数が格納されるのと混同しやすい

Expires: (期限切れになる日時 GMTのタイムゾーンを利用する)

ここで設定された日時はあくまでも、サーバーアクセスをするかどうかの判定にしか使われません。
(Real World HTTP)

このため、有効期限以前にアクセスした場合は、Cacheを自動的に使います。サーバーにはアクセスしません。X秒後の時間をしていても、X秒たった時点で自動的にサーバーに取り行くようなことはせず、古いコンテンツが使われ続けます。
X秒たったあとにリロードした場合にExpiresが切れているのでサーバーアクセスを行います。

動的に常に変更する要素には余り使うべきでなく、CSSやHTML内の滅多に更新されない要素に対して使うのが望ましいヘッダーです。

まとめ

とりあえずpart1としてざっとまとめてみました。Cacheについて実は挙動だけでなく、何がCacheなのか、リクエストヘッダーも含めたところから考える機会がなかったので一度おさらいしてみてよかったです。part2ではCacheControl周りを調べます。

gin✕GAEの環境でhandlerのunittestを書く

前提

goのhandlerレイヤー、MVCのApplicationで言うところのcontrollerのレイヤーのテストを今まで書いてきたことは少なかったのですが、意図しないinputに対して、正常なレスポンスを返さない(400とか401とかを返す)ようなEPを想定した時にhandlerとは言え、ちゃんとテストを書いておいたほうが良いなと思ったので、今回は普段使っているgin✕GAEの環境でのhandlerのunittestを書く方法を調べました。

summary

  • GAE上で動かしているginのApplicationにおいてhandlerのunittestを書きます。
  • 使った手順は以下の2つ
    • CreateTestContextを使って書く
    • testeratorを使って愚直に書く

CreateTestContextを使う

これはテスト用のcontextとginのrouting(engine)を提供してくれるginのテスト用のメソッドです。

refs:

godoc.org

github.com

当初はこの CreateTestContext を使って

// handler.go

func Method(c *gin.Context){
  // hogehoge
}


func TestMethod(t *testing.T){
  gin.SetMode(gin.TestMode)
 
  w := httptest.NewRecorder()
  _, r := gin.CreateTestContext(w)
  
  r.GET("/", Method)
  req, _ := http.NewRequest("GET", "/", nil)
  r.ServeHTTP(w, req)
  
  assert.Equeal(t, 200, w.Code())
}

というコードを書くことを想定してました。

しかし、1つ罠があって、手元で動かしているginのversionが古すぎて CreateTestContext が使えないことがわかりました。

ある程度のversionのginであれば CreateTestContext を使うパターンが最も効率がいいですが、これが使えないので、handlerのテスト毎にそれぞれRoutingを定義してテストを書くという方式を試しました。

testeratorを使って愚直に書く

CreateTestContext が使えないginのversionにおいては愚直にhandlerごとのテストでroutingを定義してEPに対するテストを書きます。
ただし、ginのcontextをappengineのcontextから生成するのに一工夫必要で今回は testerator というappengineのテスト高速化のライブラリの力を借りました。

これはappengineのプロセス(instance)を高速に立ち上げてくれるテスト用のライブラリです。

refs

github.com

godoc.org

qiita.com

testeratorを使うときの注意点は SpinUp した後テストが完了したら SpinDown してプロセスを明示的に完了させる必要があります。
終了させないと延々appengineのプロセスが立ち上がり続けて、ciが落ちるかもしれません。

// handler.go

func Method (c *gin.Context){
  // hogehoge
}

func TestMethod(t *testing.T){
  gin.SetMode(gin.TestMode)

  instance, _, _ := testerator.SpinUp()
  defer testerator.SpinDown()
  
  r := gin.New()
  r.GET("/", Method)
  req, _ := instance.NewRequest("GET", "/", nil)
  w := httptest.NewRecorder()
  
  r.ServeHTTP(w, req)
  
  assert.Equal(t, 200, w.Code())  
}  

これでテストをやってみると

$ goapp run -v ./ -run TestMethod
appengine: not running under devappserver2; using some default configuration
=== RUN   TestMethod
# 略
--- PASS: TestMethod 
Pass
ok ./handler

このようにテストが通ります。

まとめ

普段requestやhandlerのテストを書くことは少ないですが、inputとそれに対して返ってくるレスポンスが期待したレスポンスになっているのかはテストとして残しておいたほうが良いと思いました。

YAPC::Okinawa 2018に参加してきた話

少し日が空いてしまいましたが、先週の3/2~3/4に恩納村で開催されたYAPC::Okinawa 2018 ONNASON に参加してきました。

yapcjapan.org

YAPC::Okinawaに参加するまで

普段の業務はGAE/Goを使って開発していて、perlに触れる機会はほぼないので、perlの経験はほぼ皆無でしたが、とりあえず参加してみての感想まとめてみます。
ちなみに自分自身は渋谷にある、日本で最大規模のperlで書かれたサービスを作っている会社に所属していて、しかもperlで書かれたプラットフォームの事業部署にいながらperlに触れたことはありませんでした。(普段書いてないから当たり前なんですが。)

なら、なんでYAPCに参加してきたのかってことなんですが、単純に興味があったことと、perlが使われる理由を知ることで、仕事で使うきっかけにしたいと考えていたからです。
というのも、あまりperlをこれから学習することに対してポジティブな印象は持っていなかったので、今まで大規模なサービスを支えてきたperlについて知ることができれば少しでも自分の中で変化があるのではないかということを期待して参加してきました。

参加してみた感想

  • OISTすごい
    • 施設としてすごい
  • コミュニティが成熟している
    • コミュニティそのものに歴史を感じる
    • コミュニティに所属するエンジニアのレベルが高い(年齢も高い...)
  • perlが選ばれる理由は存在する
    • 企業の中で使う場合ツールの1つとしては有用である。
      • 他のLL言語より高速であること
      • system call friendlyであること and so on...
    • でも、perlはやはりメインストリームではなくなってきている(仕方ない側面もありあそう。)

個人的なセッションのまとめ

セッションはすでに講演者の方がスライドを後悔しているものありますし、同時刻別会場で行われていたセッションは見ることができなかったので、僕の聞いた中で印象に残っていたものをpickup してまとめてみます。

GraphQL をプロダクション導入した結果

GraphQLは個人的に興味があったものの、あまり利用例を聞くことがなかったので、プロダクションで使っている事例を聞くことができてよかったです。

普段仕事でjson-schemaを使っているので、差分だったり実運用のツライことなども聞けたのですが、個人的な感想としては 別にGraphQLでなくもていい というものでした。実運用できるのか、考えるキッカケにしたいと思っていたのですが、そこまではいけず。
でも価値ある内容を聞けたと思います。

RESTでのしんどいところや、クライアントサイドから柔軟にqueryを書いてデータを取得できるようにすることは確かに開発する上ではメリットが大きそうだし、そういう知見をこれからも知りたいなと思いましたし、機会があれば触れて、実運用まで持っていってみたいと感じました。

全部Perlが教えてくれたこと

技術的な話ではないですが、OSSに関わるきっかけがperlだったという話でした。

話自体はエモい内容だったのですが、ふと自分ごとに置き換えるとあまりコミュニティに貢献してきたことってなかったなーと聞いてて感じました。

別に無理してまでする必要もないんでしょうけど、普段恩恵受けているツールやコミュニティに対して、出来る範囲で少しでも今後は貢献していったりしたいなーと思いました。
(何をどうするかは全く考えてません。)

Perl in Mercari YAPC::Okinawa 2018 ONNASON

メルカリでのperlの採用事例の話だったのですが、このセッションが なぜperl? という問いかけに対して、最も納得の行く解を提示していたと感じました。

特にperlがsystem call friendlyでシステムプログラミングへの関心を醸成するためのツールとして使われていて、メルカリのSREは研修で必ずperlに触れているというところは、意図明確で納得感がありました。

インフラの構築をクライドがしてくれて、アプリケーションレイヤーではweb系のLL言語しか触れて来なかった自分のようなタイプは確かに、システムプログラミングなどのいわゆる低レイヤーの知見はほとんどないまま、エンジニアを続けていたこともあり、そういうperlの使い方もあるし、1つのツールとしてしっかりサービスを支えていました。

低レイヤーのことを知った上で改めてアプリケーションのコードを書くとコードの書き方にいい影響があると、低レイヤーに足踏み入れた友人からも聞いているので、知見がある状態で低レイヤーに馴染む機会があるのはすごく良いと感じました。

まとめ

総じてperlに触れた経験はほぼ皆無の状態で参加しましたが、参加して良かったです。
コミュニティの成熟度合いを知ることができましたし、セッションの内容もperlの技術的な内容に囚われず、多岐に渡っていてperl知らなくても楽しむことが出来ました。(とはいえ、知ってたらもっと楽しめたんだろうなとも思います...)

歴史のある言語だからこその知見、僕も高校生~大学生の頃にお世話になったコミュニティサービスや、未だにお世話になっているブログサービスなどの一斉を風靡した大規模サービスを支えてきた知見が詰まってるツールなんだなーと思うとperlの見方が変わりましたし、その知見は知りたいなーと興味が湧くきっかけになりました。
(実際に仕事で使うかは現時点では未定です。)

あと、仕事扱いでしたが、久しぶりに沖縄行けたので良かったです(笑)
当日大雨だったことと蒸し暑いことを除けば、花粉が飛んでないし、温かいしで最高に快適でした。

f:id:ema_hiro:20180303173916j:plain

100記事継続できた振り返りと所感とこれから

この記事がちょうど100記事目です。

一年弱くらいのんびり書いてたらいつの間にか100記事に到達しました(祝)
特に目標を決めていたわけではないですが、とりあえず気づいたら100記事書いていたので、飽き性の自分がここまで書き続けられた理由を備忘録としてまとめてみます。

継続させたモチベーション

  • wiki力を上げること
  • 無理しないこと

この2つが大きな要因だと思います。

wiki力を上げること

去年の7月からプラットフォーム開発に関わる部署に移動して、仕事をしていく中で、コードを書くこと以上にドキュメントを書くことが多くなって、仕様や設計をちゃんとwikiに残すという作業が格段に増えてました。
そうした業務をこなす中で自分の書いたwikiそのものが、自分で書いたくせに自分で読みづらくてどうにかしたいなーと思ったのがキッカケでずっと休んでいたブログ再開しようかなと思い立ちました。
(今までも幾つかのプラットフォーム渡り歩いてきましたが、とりあえずエンジニアだし、ブログ書くならはてブじゃね?みたいな軽いノリで何年も前に作成したアカウントで再開しました(笑))

自分のwiki力のなさの大本は、人に読んでもらうための文章 をキャリアを通して仕事として書いてこなかったことに起因していたので、まずは読みやすい文章を書くために

  • 下書き
  • 章立て
  • トンマナ合わせ
  • まとめを書く

という三点を意識して常に書くようにしてました。とは言え未だに h2にするか h3 するかとか見出しで悩むことはたくさんあります。

自分みたいな中小のベンチャー出身でドキュメントよりコードみたいな環境で社会人スタートすると、ドキュメント書くことを疎かにしがちだなーと改めて痛感しましたし、結局ドキュメント残しておかないと後々自分が苦労することがわかってきたので、ドキュメント文化の中で育ってきた方々は本当にすごいと感じます。
とてもじゃないけど、 コードがドキュメントだ なんてもう言えなくなりました。

少なくとも書き始めた頃よりは、少しばかりはまともな文章を書けるようになったという実感はあります。
まだまだtypoや誤字脱字は多いので、ちゃんと下書き、草稿段階で修正できるようにしないと思ってますが。

ちなみに下書き用には色々ツールを試した結果 boostnote を愛用しています。

(追記) 2020年現在すでに boostnote は使用していません。

無理しない事

深く掘りすぎない。
ボリュームにもよりますが30分程度で書き上げることが出来るくらいのコンテンツと量を意識することで隙間時間を使って継続できたんだと思います。

内容としてはちょっとしたツールの使い方や、言語や仕様のドキュメント読んだ備忘録程度のものでもいいと割り切っていたので結果続けることができたんだと思います。

地味にIDEとかの使い方ってメモっておくと価値あるなーと実感したり、案外やりたいこと同じだけど、ネット上に転がってなかったりするコンテンツってあるなーって実感したりしました。
こういうのはあとになって自分でも見返しすことがよくあります。

とは言え無理しないとは言っても、少しは意味のある記事の比率を増やしていきたいなーとは思っています。

まとめ

結果としてwiki力を上げるために少しでも文章を書く訓練として続けることが出来ました。
副次的な効果として、ブログを書くようになってからタイピング速度が更に上がり、業務でもwikiの作成速度が向上しました(笑)

また無理しないことを決め、ある程度、意味あることを残すというより「続ける」こと自体を目的化したことで、習慣が作れるようになってきました。
今となっては書いてない期間が続くと何か書かなきゃ!と思うことも増えてきました。

この習慣がついてからというもの、日々の仕事でちょっと調べたことだったり、わからなかったこと躓いたことをメモしておくクセが付きました。
あとで(業務情報に関わる部分はもちろん除外して)ブログに書こうと思い始めると、仕事上で色々気づこうと意識する回数が増えましたし、何より、ブログにまとめるには手順を覚えておく必要があるので、背景と手順まで含めてメモっておくクセがついたことは良かったなと思いました。

ただinputするよりも、output前提のinputの方がinputとしての質が高い というのは本当なんですね。

雑感とこれから

今思うと、とりあえず続けてみるっていうのもすごく良いモチベーションになっていたなと感じます。

当分はこのスタンスで行こうかなと思っていますが、それでもそろそろちゃんと意味のあるエントリーも書いたり出来るようにしていきたいなと思っていて、続けることを目的としつつもそうした目標も持ちながらゆるゆると続けていきたいなーと思います。

gae上のアプリに対してService Workerを登録する

やりたいこと

gae上で動いているアプリに対してService Workerを登録します。

やること

  • service worker用のjsファイル sw.js を登録すること。
  • sw.js ファイルのレスポンスヘッダーに Service-Worker-Allowed:/ を登録すること。

手順

1. sw.jsを読み込むには、app.yaml にてstaticファイル以下に配置した sw.js を読み込むようにします。

    - url: /sw.js
      static_files: static/js/pathTo/sw.js
      upload: static/js/pathTo/sw.js
      expiration: 0m

参考:

stackoverflow.com

2. Service-Worker-Allowed: /sw.js のレスポンスヘッダーに付与します。

// r *http.Request
r.Request.Header.Set("Service-Worker-Allowed", "/")

これを static/ 配下のroutingにセットします。

ちなみにFWのginなら以下のように書けます。

// main.go
r := gin.New()
g := r.Group("/static")
g.Use(func(c *gin.Context) {
    c.Header("Service-Worker-Allowed", "/")
    c.Next()
})
g.Static("", "./static")

これでgae上でservice workerを使うための sw.js を登録することが出来ます。

追記

20180307 GAE上で動かしているアプリ上にservice-workerを登録するためのjsを仕込むには、service-worker登録用のjsをどうサーブするのかを考えないと行けないのですが、以下の場合に置いてGAE上のデプロイで sw.js が404になってしまうという事象に遭遇しました。

  1. app.yamlファイルでsw.jsをサーブするpathを指定する。上記例では/sw.js としているところ。
  2. routingでstaticでファイルをサーブする箇所を指定して Service-Worker-Allowed ヘッダーを登録する。

skip_files.yamlやignore設定してない状態で sw.js が上がるものかと思っていましたが、サーブ元が同じファイルをyamlとroutingの2箇所で同じ指定をしているとそのファイルにアクセスできませんでした。(多分上がっていない?)

これを解決するために

  • routingでのstaticファイルのサーブ先の指定されている場合は、yamlファイルでのstaticファイルのサーブ先の指定しない。(yamlの設定はいらない)

ということがわかりました。正常に sw.js がアップロードされました。

参考にしたstackoverflowのエントリーはroutingでstaticファイルをサーブするなどを考慮していない純粋なGAEでのstatic file のサーブ方法だったので問題なかったですが、プロジェクトによってはstaticファイルのサーブをroutingで管理している場合もあると思うので、プロジェクトの状況に応じてsw.jsのサーブ方法を修正したほうが良いかもしれません。

サンプルコードには上げてしまったのですが、gin上で g.Static("", "./static") でroutingにおいてstaticファイルのサーブ先を指定している場合は、yamlファイルでの設定は特にしなくても良く、ヘッダー情報だけ追加してService-Workerが動くpathを制限すればよかったということがわかりました。

routingでstaticファイルのサーブをしている場合と、yamlで指定する場合において、何かしら競合が起きると正常に動作しないということがわかりました。

おまけ

ginでのstaticファイルの指定方法について深掘りしてみました。

ginのStaicファイルの登録メソッドの定義は以下。

// Static serves files from the given file system root.
// Internally a http.FileServer is used, therefore http.NotFound is used instead
// of the Router's NotFound handler.
// To use the operating system's file system implementation,
// use :
//     router.Static("/static", "/var/www")
func (group *RouterGroup) Static(relativePath, root string) IRoutes {
    return group.StaticFS(relativePath, Dir(root, false))
}
  • relativePath: 相対パス上記コードではGroupで /static を指定してしまっているので特に意識しなくていい
  • Dir(root, false): ProjectRootのディレクトリから対象ディレクトリまでの絶対パスを指定する。trueにすると http.Dir() と同じ動作をします。

以下のコードを見るとわかるけど true のときは File, error を返すようになります。 falseのときは File のみ返します。

// @gin/fs

type (
    onlyfilesFS struct {
        fs http.FileSystem
    }
    neuteredReaddirFile struct {
        http.File
    }
)

// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used interally
// in router.Static().
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
// a filesystem that prevents http.FileServer() to list the directory files.
func Dir(root string, listDirectory bool) http.FileSystem {
    fs := http.Dir(root)
    if listDirectory {
        return fs
    }
    return &onlyfilesFS{fs}
}
// @http/fs

// A Dir implements FileSystem using the native file system restricted to a
// specific directory tree.
//
// While the FileSystem.Open method takes '/'-separated paths, a Dir's string
// value is a filename on the native file system, not a URL, so it is separated
// by filepath.Separator, which isn't necessarily '/'.
//
// An empty Dir is treated as ".".
type Dir string

func (d Dir) Open(name string) (File, error) {
    if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
        strings.Contains(name, "\x00") {
        return nil, errors.New("http: invalid character in file path")
    }
    dir := string(d)
    if dir == "" {
        dir = "."
    }
    f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))
    if err != nil {
        return nil, err
    }
    return f, nil
}

refs:

http - The Go Programming Language

環境を変えた時に先にしておくといいことをまとめてみた

最初に

※ これは個人的な経験をもとにして記載しました。

Summary

転職したり、異動したりで新しい環境に所属することはエンジニアに取って珍しい話ではありませんが、新しい環境に行った時に先んじて理解しておいた方が良いと思われることをまとめて見ました。

環境編

  1. チュートリアルを読む
  2. トイレの位置を把握する
  3. 勤怠ルールを確認する
  4. New Commer 専用のチャット部屋を作る

エンジニアリング編

  • デプロイルールを覚える
  • デプロイをする

環境編

チュートリアルを読む

ある職場はいい職場だと思います。
異動直後、入社直後のチュートリアルがあると、教えてもらう側も教える側もコスト少なく、必要な情報を伝えることが出来ますし、例えば入っておくべきメーリスや、最低限のslackのチャンネルなどNewCommerが自律的にセットアップできるような仕組みはあるとすごくよいと思いました。

もしない場合は、すぐに作ったほうが良いと思います。

  • エンジニア向け
  • ビジネス向け

など色々種類はあると思いますが、業種分あるのがおそらく理想なのだと思います。

自分もそうですが、NewCommerのころの最初の1ヶ月位は結構緊張して、生産性が上がってこないので、極力、気を使うことを減らしてあげて

トイレの位置を把握する

重要です。できれば個室の個数も数えておくと良いと思います。
入りたてのころはこういう些細なところで体力消耗するので当日に調べておく、できればピークタイムを最初の一週間くらいで覚えておくと良いと思います。

喫煙者の方は喫煙所のある階、及び喫煙ルールとかも調べておくと良いと思います。近頃は禁煙の流れなので、電子タバコしか吸えなかったりとか細かいルールがあると思うので調べておくと良いかもしれません。

勤怠ルールを確認する

重要です。いくら自由な職場だったとしても最低限のルールはあるものだと思うので、そのルールは予め確認しておくといいと思います。
勤怠は同じ会社でもチームによって違ったり、職種によって異なったり、勤怠連絡の仕方とかもメールなのかチャットなのか色々ルールがあると思うので、当たり前ですが入社当日に知っておくべきだと思います。

※ NewCommer向けチュートリアルに書いて有るべきですね。

New Commer 専用のチャット部屋を作る

※ これは実際に運用されているのを見てすごく良い仕組みだと実感しています。こういうことに気づける人材になりたいです。

初日からチャットであれこれ話せる人は(いるかもしれませんが、)まれな人材だと思います。
New Commerである以上、最初の数日は同僚の顔と名前も一致しないし、ましてチャットで話されている内容なんて、まず事業も環境も知らないのにわかるわけがないと思っています。

しかも、雑談チャットとかでもいきなり自分から声をかけるのは難しいと思います。周りは気軽に聞いてねー!みたいなことを言っても、僕もそうですが、初心者がいきなり気軽に話しかけに行けるわけがありません。
結構ストレスです。

なので、予め歓迎用チャンネルとか作って、部署の人、業務で関わる人は先に入っておく。そして、New Commerはそこで色んなことをつぶやきベースで聞いてみて、部署の人はそのつぶやきを拾ってあげる。そういう流れにすると、馴染みやすくなるのではないかなと思います。自己紹介なんかもそこですると良いかなと思いました。

チャットは例え部署のprivateな雑談チャットだとしても、公共の場です。公共の場でいきなり自分の発言をするのは負荷がかかります。そのため、ある程度NewCommerのpersonalな環境を用意してあげて、ゆるふわに色んなことを話せる場を作る、そうするとチャットでの発言がしやすくなって、ひいては仕事の話もしやすくなるのではないかなと思います。

ちなみに、チャンネル名は welcome-XXXX とか tutrial-XXXX とかが良いのではないかなーと思います。対外的にもわかりやすいですし。
※ これは入社時点で予め作ってあるとすごく良いですね。

コミュニケーションは仕事の基本なので、そのコミュニケーションの最初の一歩をどれだけ後押ししてあげられるかがその人が馴染める初速につながっていくと思います。
これによりコミュニケーションが円滑に進み、仕事のスピードに繋がり、ひいては事業スピードに繋がっていくと思うので、この仕組みは本当に良い仕組みだなと思っています。

エンジニア編

エンジニアとしては

  • 開発環境構築
  • システムの理解

など最初にやるべきことはある程度決まっています。

その中で個人的に、ある程度慣れないとやらないことだけど、先にしておくと実はコスパが良いのではないかと思ったのがデプロイ です。

デプロイを先に理解する意図

開発者たるもの、作った機能のコードを書くことだけが仕事ではありません。
リリースして事業に貢献する ことが仕事だと思っています。

そのためリリースをするためのデプロイ権限をエンジニアは持たないといけないと思います。
しかしJOIN当初は当たり前ですが、デプロイ権限はなく、権限を持つ人に依頼することになるのが一般的だと思います。

デプロイ権限を持たないデメリット

リリースが自分以外の誰かに依存してしまうことで幾つか弊害があると感じます。 - リリースするだけなのに、その「誰か」のスケジュールを確認しなければ行けない - 何かバグがあった場合にも切り戻しも依頼しないといけない - つまり、自分以外の誰かに依頼する以上、案件を全て自分でハンドリング出来ない。

デプロイ権限があることのメリット

誰かに依存することがありません。

  • スピード感持ってリリースできる
  • 案件への責任感が生まれる
  • もし仮に問題があっても、実装者である自身が気付けるので、切り戻しが早く行える。

そして何より、自分から進んで案件を拾いにいけると思います。
書いたコードが実際に自分の手で世の中に届けることができる権限を先に渡しておくことで、ずっとスピード感持って開発をしていけるような気がしています。

デプロイを標準化する

デプロイ手順を簡略化、べき等化しておくことは、チームの責任だと思います。
ちょっとしたデプロイでさえ秘伝のタレ化していたり、細かな手順を踏まないと行けなかったりというだけで、開発のモチベーションが削がれます。
誰がしても同じようにデプロイ出来る仕組みを作っておくことは、受け入れチームの責務だと思います。
ある程度揃えておきましょう。

また、権限を取るためのチェック項目や、デプロイ手順もドキュメントにまとめておくと良いと思います。  

デプロイて権限管理等が悉皆されていると後回しになりがちなことだと思います。
ただ、これを早い段階で誰かに依存せずにNewCommer1人で出来るようにしておけば開発がより早く進むと考えています。

デプロイするにあたって

デプロイルールを覚える

Documentや権限付与のための簡単なチェックシートがあるといいですね。
読み終えたら自動的に権限付与、くらい裁量があるとことがスムーズに運ぶと思います。

デプロイする

簡単なチケット等の実装をして、見守られつつも早い段階でデプロイまでしておくと良いかと思います。
最初のデプロイは、冷や汗もんですよね。

僕は異動3ヶ月目くらいでようやくもらってその前後で生産性や仕事へ意識が変わった実感があったので、これは先に経験しておくとよかったなぁと後悔したところだったので記載しました。

まとめ

僕は特にそうですが、新しい環境ってめちゃめちゃ緊張します。
一緒に働く人の顔と名前も定かでない状態でいきなり100%のパフォーマンスは出せません。
NewCommerにできるだけ早く100%の力を出させてあげる、そのための小さな障害やストレスになりそうなものは予め軽減しておくことは会社、受け入れるチームの責任であると思っています。

めんどくさがらずにチュートリアルも歓迎チャンネルも用意しておくときっとNewCommerが馴染むまでの時間を軽減できます。
そしてそれがそのまま事業スピードにつながると思うので、こういうちょっとした気遣いやオーバーヘッドかもしれないことをこなしておくといいです。
最後にトータルで見たときに結果を実感するようなものなので、最初は腰が重いかもですが、ある・なしで本当に異なるなーという実感があります。

個人でもwelcomeチャンネルで即レスしたり、新しく入った方が早く環境に馴染める努力はしていきたい所存です。

【Golang】部分文字列を抽出する

golangの文字列から特定の位置の文字を取り出したい時に [:] を使えます。

package main

import (
    "fmt"
)

func main() {
    s := "abcdefg"
    fmt.Printf("%+v\n", s[:])
    fmt.Printf("%+v\n", s[1:3])
}

/* output
> abcdefg
> bc
*/

cf. https://play.golang.org/p/Gp0PYKtnZfA

但し、マルチバイト文字の場合は落とし穴があるので注意が必要です。UTF-8の文字数では使えません。

package main

import (
    "fmt"
)

func main() {
    s := "あいうえお"
    fmt.Printf("%+v\n", s[:])
    fmt.Printf("%+v\n", s[1:3])
}
/* output
> あいうえお
> ��
*/

cf. https://play.golang.org/p/fToYo08XtaD

UTF-8の文字列で部分文字列を抽出するについては以下のような utf8string をパッケージを使うと良いらしいです。

qiita.com

GoLandでriotのtagファイルをシンタックスハイライトする

Overview

GoLand(というかIntelliJ)がデフォルトで対応していないtemplateファイルをカスタム登録する方法

以前tmplファイルの設定方法は書きました

ema-hiro.hatenablog.com

手順

前回と同様です。
Preference ▶ Editor ▶ FileType ▶ HTMLを選択。
riot.jsでは *.tag をRegistered Patternsに追加します。

f:id:ema_hiro:20180215010613p:plain

Use httpmock with fmt

Summary

golangのhttpのmocking packageである httpmockfmt packageを使ってstring型に変換したときにハマった話を書きます。

httpmock packageはこちら

github.com

How to Use

var resJson = `
{
  "id": 1,
  "name": "taro"
}
`

func TestHttpMock(t *testing.T){
  RegisterResponder("GET", "https://sample.com/users/1", httpmock.NewStringResponder(200, resJson))
  // some unit test
}

httpmock を使ってurlとmethodを指定するだけで、mockしたいリクエストのResponseを定義できる。
上記では /users/1 の内容に対して resJson で指定したStringをresponseとして登録します。

ハマったところ

上記の別パターンで幾つかresponseの内容を追加でmockしたいとします。以下のようなパターンです

resJson1 = `{
  "id": 1,
  "studentID": 123,
  "name": "taro",
  "age":20,
  "sex": "male"
}`

func TestHttpMock(t *testing.T){
  RegisterResponder("GET", "https://sample.com/users?name=taro&studentID=123", httpmock.NewStringResponder(200, resJson))
  // some unit test
}

上記のようにクエリパラメータでURLを指定してリクエストのmockを用意するケースを考えます。
例えば以下のように、 studentID が幾つかあって、複数のクエリ条件のもとmockするデータを用意するとき

resJson1 = `{
  "id": 1,
  "studentID": 123,
  "name": "katsuo",
  "age":20,
  "sex": "male"
}`

resJson1 = `{
  "id": 2,
  "studentID": 456,
  "name": "wakame",
  "age":18,
  "sex": "female"
}`

resJson1 = `{
  "id": 3,
  "studentID": 678,
  "name": "tara",
  "age":10,
  "sex": "male"
}`


func TestHttpMock(t *testing.T){
  studentIDs := []int64{123,456,789}
  for _, id := range studentIDs{
  RegisterResponder("GET", fmt.Sprintf("https://sample.com/users?sex=male&studentID=%d", id), httpmock.NewStringResponder(200, resJson))
    // some unit test
  }
}

コードは適当ですが、こんなケースをしている、urlを動的にイテレーションでループさせて一括でユニットテストを書きたいみたいなシチュエーションがあると思います。
しかしこのコードは動きません。原因は 適切に特殊文字エスケープされていない からです。

Mockの部分を若干修正したコードが下記です。

func TestHttpMock(t *testing.T){
  studentIDs := []int64{123,456,789}
  for _, id := range studentIDs{
  RegisterResponder("GET", fmt.Sprintf("https://sample.com/users?sex=male%26studentID=%d", id), httpmock.NewStringResponder(200, resJson))
    // some unit test
  }
}

&特殊文字%26エスケープしました。
実はこれでもまだ動きません。

理由は、エスケープした結果の %26% の部分が、format関数で指定されている % をバッティングしてしまって、正確にクエリパラメータを読み込めてなかったからです。

そのため、エスケープした結果、ちゃんと%として使われる必要がある ということだったのです。goのfmtの標準パッケージの中に書いてます。(https://golang.org/pkg/fmt/#hdr-Printing)

fmt - The Go Programming Language

%% a literal percent sign; consumes no value

つまり、%% -> % の文字列として扱えます。なので下記になります。

func TestHttpMock(t *testing.T){
  studentIDs := []int64{123,456,789}
  for _, id := range studentIDs{
  RegisterResponder("GET", fmt.Sprintf("https://sample.com/users?sex=male%%26studentID=%d", id), httpmock.NewStringResponder(200, resJson))
    // some unit test
  }
}

これでMock完了です。

まとめ

httpmockでfmt.Sprintfで文字列型にするときにはエスケープのことを考慮することが必要です。

Cookpad TechConf2018に行ってきた!

こちらに参加してきました

techconf.cookpad.com

基調講演

遅れて参加したので聞いてません。

クックパッドの “体系的” サービス開発

BMLループ

buildの失敗

  • 手戻り?

mesureの失敗

  • ログの取り忘れ

learnの失敗

  • 数字は動いた
  • しかしイマイチ効果がわからない
  • 数字は得られたが学びがない
  • 属人的

Buildの前に行っておくこと

  • BMLループを前から順に行わない
    • 手戻り防止
    • 逐次にやろうとすると結構大きな手戻りが起こる
    • 最初にサイクル全体を設計する
  • 効率的な学び
    • 施策結果に対する予想
      • 現実の理解
      • サービスに対する理解
      • 想定との良し悪し->なんで??
    • どういう結果が出そうなのか?
      • mesureやlearnのフェーズで考える内容を考えておく

Mesureの前にしておくこと

  • 計測結果の選定
  • KPI設定
    • 指標やKPIはそれ単体で存在することは珍しい
    • 通常は別の指標とバッティングする
    • 事前にバッティングする箇所を予想しておく
  • ログの確認、SQLの実行

Learn前にしておくこと

  • 指標解釈の整理
  • 結果の想定
  • 👉成功のイメージを共有する
    • その施策が成功した時に、ユーザーはどういう体験をしているのか?

社内ツール

  • Build
    • 価値仮説シート
    • Chanko
    • EasyAB
  • Mesure
    • 社内ツールがある
  • Learn
    • Report.md
      • 施策の分析レポートをMarkdownで作成し、PRベースで管理していく
      • Report.mdを先に作ってサイクルを決める

まとめ

  • 仮説の実行から学びを得るサイクルを先に設計する
  • 前から逐次体に行わない

クックパッドクリエイティブワークフロー

Cookpadは1サービスを作るのに140人のエンジニアがいる

  • ユーザーのシーンごとにGroup分けしている。

料理きろくのアプリ版チーム

  • Missionオーナー
  • デザイナー
  • エンジニア ✕ 2
  • エンジニアとデザイナーがお互いを補完する

目的と仮説を明確に

  • 常になんのためにしているのか意識する

Github issueでのアイデア発散

デザインレビュー

  • 目的・背景・コンテキストを明確化
  • 職種関係なく、横串でデザインを評価する
  • 品質向上

数値は全員が見れる場所に

テスト

  • 考慮漏れ・見つめ直す
    • TsuyoiUI・・・社内のチェックツール
      • issue作ったら、issueテンプレートに自動的に反映する

リリース・分析

  • あの時誰かがしてたな???(゜゜)問題
    • チーム内外の共有をすぐしたい
  • Report.mdの誕生
    • 見たい時にすぐ見れるレポートの管理

デザイナの役割

  • デザインリリースマネージャ
    • リリース単位で体験やUIの変更箇所を把握、デザイン周りを一貫してみる役割

What/How to design test automation for mobile

テスト自動化について

  • 開発サイクルの効率化?
  • 自動化がモチベーション?

テストで話が噛み合わないところのコミュニケーションの方法

SPLIT がキーワード

-よくわからないものを分割していく

Scope

  • どこをテストの対象範囲とするか?
    • モバイルアプリを使っているユーザー
    • どこまで踏み込んでいってテストをするのか?

Phase

  • 開発中?orリリース後?
    • in Production
      • 世に出したあとのテスト
        • A/Bテスト
        • カオスエンジニアリング

Level

  • なにをテストするのか?
  • どこを自動化するのか?

sIze

  • テストの種別

    • UnitTest
    • IntegrationTest
    • ...
  • テストサイズを分割する

  • どういう形に落とし込んでいくのか?

Type

  • テストを目的別に区分

範囲、時間的なもの、どこまでするのか、どこまで自動化するのか?にどんどん分割していく。

Cookpad Android for Globalでの事例

  • 事前のコミュニケーションが大事
    • 理想的なプロダクトのラインを決める
  • Scope事例
    • ユーザーが実際に使う状態に近しい環境
    • ネットワークが関係する環境のテスト
  • Phase
    • in developent
      • 開発中だった
      • sizeを再定義 3つから4つ
        • UnitTest
        • IntegrationTest
        • UI Components based
        • User Senario based

iOSはパフォーマンスをテスト自動化しようとしている

  • 以前似たような状況にあって、パフォーマンスが劣化した例があった。

Rubyの会社でRustを書くということ

CookpadRubyの会社

  • 多くのサービスがRuby on Rails
  • 但し全てのサービスがrubyで書かれているわけではない
    • Middlewareなどはjava、Goで書かれてたりする
  • CookpadRubyの会社だけではあるが、Rubyだけの会社ではない。

Push通知を配信基盤をなんとかする

  • 都度配信

    • イベントごと
  • 一斉配信

    • 特定層に一斉配信

Rustで書き直す前のPush通知配信基盤とは?

  • 殆どの部分をアプリケーションが担っていた。
  • 基盤というにはあまりに機能が少なすぎた

もともとは世界最大のRailsアプリケーション

  • Pushするアプリケーションは1つ以上にある
    • しかし今はマイクロサービス化が進んでいる。
    • 1つのときは基盤が貧弱でもなんとかなった
    • 同じロジックが色んなアプリケーションにコピペされている。
      • DBを共有することは避けたい
      • DBに接続することも避けたい

新規版では全てのステップを配信基盤で担う

  • UserId指定
    • 配信先を受信設定に基づいてfilterする
    • Push通知の配信見積もり

厳しい性能要件があるアプリケーションをrubyで書くのはしんどい

  • Rustには色んなメリットがある。
    • 型、安全性、並行性
    • トレイト
    • データ競合のあるロジックはコンパイラが起こってくれる

Rust is not magic

  • Rustで書いたからと行って勝手に高速になるわけではに。
  • 早いソフトウェアを書くのはプログラマ市議

Facebookのデータローダの考え方を使う

  • クエリをまとめる

OSSを開発

github.com

github.com

※ ここからはさきは説明形式なのとrustなので詳しくわからず

  • ブロッキングな処理をFutureに変換しているとみなせる
    • JSのプロミス的な概念

Rustのいいところ

  • マルチスレッドが安全

    • マルチスレッドとイベントループを混ぜ込むみたいな複雑なアーキテクチャも採用できる。
  • とりあえずロックとかなくなる

  • 安心して高速化
  • 型安全、強力

cookpad storeTV 〜クックパッド初のハードウェア開発〜

StoreTVは三者にメリットがあるサービス

  • スーパー
  • ユーザー
  • 食料品メーカー

ハードウェアでも改善サイクルを回していく - ユーザーにあててサイクルを回す

サイクル

  • 第1サイクル
    • 最小価値影響
      • 売り場で料理動画を探す
        • 料理動画を探す・見れる
        • 300台配布
      • キッティング
        • 1台10分
        • 週ごとに料理動画を作った
    • 価値検証
      • 電話調査
        • 動画見てて楽しい!
        • 自分の作りたい動画探せる!
      • アプリの利用ログ
      • 売上 -> Posデータの提供を受けた
        • StoreTVの採用した売り場は他の売り場の1.3倍の効果アリ
    • その他の問題
      • 動画の数
        • 注力商品のみで良いと思っていた
          • 売り場にあってない
      • 見た目の問題
        • 目立たない
      • キッティング
        • 手作業が手間
      • 端末管理
        • アプリの更新が手作業
  • 第2サイクル
    • スケール
      • なにをやって、なにをやらないか
      • 得意なことは自分たちでやる、不得意なことは誰かに任せる
    • 動画の数
      • 毎月100本
    • 見た目
      • 不得意
      • ケースを作成する
        • 業者に依頼
          • 中国製
          • 中国に行ってきた
            • ロゴがバグってるw
      • 不得なことは1人でやらない、でも任せっきりはだめw
    • キッティング
      • 不得意
        • 業者に依頼
          • どうしても人力に頼らざる得ない
            • アプリケーションを整えた
    • 端末管理
      • 不得意
        • 自動アップデート
          • MDMによる一元管理(Mobile Device Managiment)
    • 問題点
      • サイネージサイズ
        • 大きすぎる
          • 売り場に置けない
      • 開発新興
        • バグが増え始める
  • 第3サイクル
    • 収益化
      • 広告配信の基盤開発
      • 広告接触者数のカウント
    • サイネージサイズ
    • 開発進行
      • 長時間安定して動画
      • バグの検出
        • 時限製のバグが発覚した
          • コードフリーズ後の長時間再生
      • 顔認識の機能
        • 端末計算リソースを食う
        • エグザイルを利用したww
          • なお、エグザイルに1台やられたww

Challenges for Global Service from a Perspective of SRE

クックパッドのグローバル・サービスってなに?

  • 海外向けのレシピサービスを提供している
    • 22言語68カ国で提供
    • イギリスで提供している

2017年のグローバルサービスの成長

  • 対応言語数
    • 15->22言語
      • 7言語の増加について
        • 全言語対応 ≠ 世界対応
        • 去年のスライド見てね

techconf.cookpad.com

現状の課題や挑戦

  • 特定の国のユーザー体験が悪い

    • 国ごとに差が出始める。
      • 原因がわからないと改善できない。
  • 世界中のユーザー体験を測定する

    • CatchPointを利用した。
      • CatchPoint Systemsのメトリクスを利用した
    • 原因を調査できるようになった。
    • インドネシアと米国で比較
      • TLS接続
      • Time to First Byteが遅い
      • -> 米国からの距離が遠い。
        • Cookpadのグローバル版は米国リージョンにある。
    • データセンターをユーザーに近い場所に移動
      • マルチリージョン化
        • しかし管理コストがかかってくる
    • CDNの導入
      • Fastlyを導入
      • 米国から遠い国のユーザー体験が改善
  • イベントのバリエーションが多い
    • ex.
      • アルゼンチンは独立記念日パステリートというお菓子を食べる
      • 日本だとバレンタインの時にユーザーが増える
    • 展開国が増えるとイベントバリエーションが増大する
      • SREは「毎月バレンタイン」
    • 課題改善
      • Amazon Auroraを導入
        • オートスケーリング
      • Dockerアプリ開発環境の提供
        • ECS + hakoでのデプロシステムの導入
        • hako-consoleを使って状態を管理
          • 日本で培った技術をグローバルに応用
  • デプロイのオペレーションコストが高い
    • ネットワークが安定しない、日常的に停電が起きる国とかある。
      • デプロイを他の人に依頼する問題
        • 世界中どこからでもデプロイ出来るようにする
        • 米国にデプロイサーバーを用意
          • デプロイサーバーでのマニュアルオペレーションの課題もある
    • 改善策
      • slackによるbotデプロイ
  • toilが急増する
    • toilとは?
      • 骨の折れる業務
      • アカウント管理業務とか
    • SREの対応する依頼業務が増大
      • toilの割合が増大
      • SREが本来したい業務ができなくなった
    • 世界中に社員がいるからこその課題。
    • アカウント管理
      • nginx + omniauth
      • アカウント管理のセルフ管理化(移譲)
    • SREのマルチリージョン対応
      • 時差の壁を超えたりとか

動き出したクックパッドのCtoCビジネス

  • komercoの発表
    • もので毎日の料理を楽しくするプラットフォーム
      • 料理を盛る器や鍋がいいと、もっと料理が楽しくなる

komercoにはサーバーサイドエンジニアがいない

  • サーバレス
  • Firebaseで開発している

(このあたりやたらFirebaseの宣伝というか、いいこと話してること多かった)

※ Firebase Japan User Group入っておこう。

firebase.asia

OpenSourceにすること

  • 少人数で品質を担保するため
    • Opensourceすると品質が向上する
    • 外部の人がチームのリソースになる
    • 再利用できる
      • 他チームで利用できる

技術一覧はこちら

Pring

  • firebase Model framework

github.com

Orderable

  • Order processing framework

github.com

開発の高速化の先になにがあるのか?

  • 大胆な戦略変更
    • 複数回の仕様変更
    • エンジニアが消耗しない
    • コミュニケーションコストがかからない
    • たくさん試すことができる
  • 事実は我々の中にない、市場に聞くべき
  • たくさん試すべき

Solve "unsolved" image recognition problems in service applications

画像分析のお話

昨今の画像分類問題

  • 理想的な状況下では「解けた」と言われている
    • 適切なラベルの付与
    • 適切なカテゴリの設計
    • closed set
      • 現実世界はopen setである
      • ラーメンの画像分析にケーキの画像がくることなんて容易にある。
  • 私たちが本当にときたい問題はなんなのか?
  • 解くべき問題の多くは「間違っている」
    • 本当に時対問題をtry and errorで探していく

料理きろくの進化と現在

  • 機械学習の観点から見る重要な点
    • クイックスタート
      • 要素技術が成長している
    • モデルの改善と苦手なカテゴリの考慮
      • 間違いやすいやつら
      • 植物はサラダと分類される
      • テストデータの拡充
      • 局所性を取り込むためのpatch化
        • 画像の一部だけに料理が移っていた時
        • ふと、お店で出てきた画像とかどうするんだろう?
          • 自分で作った料理ではなさそう???
          • それは分類されてもいいのかな???
    • 料理きろくのその先へ
      • 勝利写真のレシピカテゴリに分類
      • 単純な分類に見えて実は非常に難しい
        • openset における予測
    • 類似カテゴリをどう予測するか
    • 類似画像に対する分類

類似モデルは生まれるのか?

  • カテゴリを設定した時にどういう画像が生まれやすいのか?

モバイルへの移植

  • どうサービスに載せるか
  • 次に来そうな領域

Beyond the Boundaries

※ 基調講演

  • 技術を正しく理解し、「ふつうに使う」
  • 技術スタック
    • web
    • mobile
      • Swift
      • Kotlin
      • プロトタイピング
        • React Native
        • Firebase
    • Infra
      • AWS
      • docker
      • Hako
  • 自分たちの道具は自分たちで持続可能にする
    • コミュニティへの還元
  • 「境界」を認識し、乗り越える強い組織へ

まとめ

2年ぶりにCookpad Tech Confに参加してきました。
領域としてはプロダクト作りからグローバルでのサービス展開、機械学習まで幅広い話が聞くことができました。
個人的には、SREの話やPush基盤作りの話が面白いなーと感じましたが、HTTPやCookpadのプラットフォーム作りの話をもう少し聞いてみたかったと思いました。

HTTPリクエストでReferrerを送信しない

Referrerの制御方法について調べたので備忘録です。 下記ページを参考にしてましたがIE11での挙動など自分で追加で調査した箇所があるので付け足して行きます。

qiita.com

Referrerとは?

HTTPリクエストを投げたときの参照元です。
ブラウザがHTTPリクエストを投げる時、Referrerヘッダーに参照元(元いたページ)のURLが入ります。

Referrerヘッダーを制御する意図

特定のページ上でSNSウィジェットだったり、Youtubeウィジェットだったり、外部サイトの情報をiframeで埋め込むことは今時のウェブサービスであれば使われている画面が数多くあります。
しかし、このAPI呼び出しをする場合でも外部サイトへのリクエストとみなされるのでブラウザの開発者ツールで外部サイトのリクエストの中身を見ると Referrer: ヘッダーに参照元(元いたページ)のURLが表示され、Referrerヘッダーに乗って、外部サイトにURLが送信されます。

例えば、URLが非公開URLであったり、ワンタイムでなく時限token付きURLであった場合、外部に漏れては行けない情報が漏洩する可能性があります。

こういう時、セキュリティ上のリスクを抑えるため、参照元ページのページ情報を Referrer: ヘッダーに乗せずにリクエストを送信しないといけなくなります。

これはReferrerを制御する意図です。

制御方法

イマドキのブラウザ

イマドキのブラウザと古いブラウザで対応方法が異なります。

今時のブラウザ

などは最初に記載した参考のQiitaの記事の内容で大丈夫です。

<meta name="referrer" content="no-referrer">

こいつをheadのmetaタグに入れてやれば、Referrerは送信されません。

しかし問題があります。
このmetaタグの content="no-referrer" はIE11では使えません。

つまり、IE11でアクセスされたら上記のmetaタグを埋め込んでいても、Referrerが送信されて情報が外部サイト漏れます。

参考: https://webtan.impress.co.jp/e/2015/04/14/19750

IE11対策

IE11を始めとした古いブラウザには以下で対応します。

<meta name="referrer" content="never">

support ブラウザを調べる

以下で調べられます。

Can I use... Support tables for HTML5, CSS3, etc

確認したいブラウザにカーソルを合わせてみると使える contentを知ることが出来ます。

IE11の場合

f:id:ema_hiro:20180204030004p:plain

Edgeの場合

f:id:ema_hiro:20180204030018p:plain

(Edgeも対応してないんかい!!!!)

まとめ

Referrerを使った脆弱性について調べることができました。
何気なく使っているiframe埋め込みでも使い方次第では脆弱性になるので、ブラウザのサポート状況は常に確認する必要がありますね

(ほぼ)初めての自宅(リモート)作業をしてみた話

注) プログラミングネタではないです。

自宅作業の経緯

職場でインフルエンザが流行し、感染防止のために学級閉鎖の如く、所属している部署全体で自宅作業推奨になったため、期せずして自宅作業をする機会を得ました。
リモートで仕事するのはほぼはじめてだったので、実際にやってみて感じたこと、工夫したことをまとめました。

※ これは職場の緊急的な措置で、決してこの状態を肯定しているわけでも、期待しているわけでも、リモートにすべきだみたいな意見を持っているわけはありません。ただ、個人としては少しワクワクはしていました(笑)
※ なお、普段は基本的に出社するのがルールになっています。

自宅作業時期

1末〜2頭の3日間。
※ 本当は4日の予定でしたが最終日は予定があったので出社しました。

さまり

先に結論を述べておくと、僕個人の感覚としては、「やっぱり仕事は会社でしたい」ということを再確認しました。
エンタープライズ向けサービスの企業ではリモートが推奨されてますし、世の中の流れとしてもリモート推し的な空気を感じますが、個人の感覚としては会社の方が生産性が上がると感じます。
これは僕の主観なので、リモートの方があっている人ももちろんいると思います。
ただ、やはり100%リモートにするには色々超えないと行けない壁が大きい気がします。

ちなみに、リモートで業務をしてみて、仕事をする上でよかったことや、普段と違うことで気づいたこともありました。
仕事をする上でにリモートも選択肢としてあるのはプラスなことだと思いました。

リモートをしてみて

  • よかったこと
  • わるかったこと
  • 気づき/工夫したこと

よかったこと

通勤時間分寝れる

重要です。起きた時に「あ、そうか今日いかなくて良いんだ」って思って30分余計に寝ました。(起きろよ)

ひたすら自由

会社は公共の場なので、当たり前ですが周囲に気を使います。
それがないのでストレスフリーでした。
ジャージや寝癖のまま仕事しても誰もに文句言われませんし、誰かの目を気にする必要もありません。(ただ、この弊害はあります。後述します。)

細切れ時間を有意義に使える

ビルドしている最中やテスト回している間だったり、チャットのレス待ちのちょっとした時間に洗濯機回したり、食器洗ったり、掃除機かけたりしてました。
会社にいると、手持ち無沙汰の時間にコーヒー買いに行ったり、スマホゲームでガチャ回したり、ニュース見たり、Twitterしたりと、あまり「生産的でないこと」に意識をもってかれてますが、家事をすると少し生産的なことをした気分になって良いです。

自宅に引きこもるのでエコ

僕は基本家にいると決めた時はずっと引きこもるのでほぼ自炊です。
光熱費はかかりますが、外食したり飲料水買ったりするお金を使わずに過ごせるので、僕にとっては非常にお財布に優しくエコでした。

わるかったこと

メリハリつかない

多分、僕がリモートに向いてない理由の1番はコレです。
生活の延長で仕事しているので、よく言われるようにメリハリつかないです。
なんかダラダラしてるなーって感じることがありました。
今回は4日間限定だったので、なんとかなりましたが、これ、ずっと続いたら確実に自分はサボり始めると思いました。

プライベート見られるのはいい気分はしない。

オンラインMTGでビデオ通話したんですが、インカメで部屋の一部が映るのはあんまりいい気分しませんでしたw
こういうとき書斎とかあるとすごくいいんだろうなー。

(たぶん)太る

手の届くところにご飯やお菓子があるので、ひたすら何か食べながら仕事してました。コレ続けたら多分太ると思います。

気づき/工夫したこと

ここからは個人の感覚レベルでの気付きや、工夫したことを書き出します。

身だしなみは整えたほうが良い

寝間着でデスクに座ってもなにも始められません。
顔洗って、髭そって、着替えましょう。
コレだけで大分違いました。

家事大事

お昼ごはん作ったり、細切れ時間に洗濯もの干ししたり、業務とはコンテキストが全く違うことをするのでいい意味で頭の切り替えになります。というか勝手に切り替わります。
職場だと昼時にもネットニュース見たりとか、中度半端にエンジニア脳が残るので、実はこれ、脳のエネルギーを無駄に消費しているだけなんじゃないかって思いました。

オンラインMTGは十分MTGになる。

ハングアウトでオンラインMTGしたんですが、もうこれ十分MTGになりますね。
オンラインMTGの進歩を身をもって痛感しました。
段取りをちゃんとしてもらったというのもありますが、別にその場にいなくても会議はできるもんですね。
(ただ、インカメは大敵ですw👆上述)

ちょっとしたコミュニケーション大事

お昼行く時にslackに「昼行ってきます〜」と書いて、戻ってきたら「戻りましたー」と書いて自分のステータスをちゃんと伝えること大事だと思いました。
不要な憶測や不信感軽減のため。

お昼ご飯に行く

f:id:ema_hiro:20180202030033p:plain

お昼ご飯から戻る

f:id:ema_hiro:20180202030037p:plain

※ 同部署にいるyoichiroさんにリモートで働く上での教訓を聞いていたので意識的に行いました。

ちゃんと説明するようになる

基本チャットしかコミュニケーションツールないので、自分のステータスをちゃんと説明するようになります。(上述の内容と少し被りますが。)
会社にいると対面・口頭確認に逃げられますが、リモートだとそれが効かないので、冗長でもちゃんと説明するようになります。
ただ、神経使いますし、チャットするだけで疲れちゃうなーってこともありました。
ここらへんは慣れなのかもと思いますが、ぶっちゃけあんまり意識してなかったツケですね。普段からもっと丁寧にコミュニケーションしとけよっていう話でもあります。

当たり前ですが、コミュニケーションって大事。 とはいえ、やはりチャットだけだと100%意図伝えきれないこともあるのかなーと感じて、会社で対面でコミュニケーション取った方が文章だけのコミュニケーションよりストレスレスだし、意思疎通は格段に速いと思います。
(言葉遣いにとかに対して厳しい人だと余計に疲れそうな気もします。)

椅子にはお金をかけたほうがいい。

自宅にはアーロンチェアを入れてます。同僚でも自宅の椅子が劣悪なのでやはり出社しますという方がいて、リモートするなら自宅の設備投資はマストだと感じました。

通勤時間が気分転換になるんだなーと再認識

僕には通勤時間にぼーっと景色眺めてたりするのが案外気分転換になってたんだなーって再認識しました。
会社にいかないとそもそも外に出ないので、それはそれで精神衛生上よろしくないなと。外の空気は吸ったほうがいいという教訓。
(※ 完全に個人的な感覚です。)

結論

最初にも述べましたが、やっぱり自分には会社の方が仕事するにはあってる気がしました。
理由はコミュニケーションコストと仕事モードへの切り替え。

やはり、対面のコミュニケーションによるスピーディーな意思疎通をリモートでは超えられないと思いました。
また、個人的には場所が仕事モードに切り替わるための重要な要素だったので、自宅のままだとどうしても勢いが出ませんでした。

ただ、リモートしてみて普段自分があんまり意識してなかったことにも気づけたこともありました。
リモートでの綿密なコミュニケーションだったり、通勤ストレスなかったり、ちょっとしたコミュニケーションの重要性認識したり。

働き方改革が標榜されてるからか、会社にいかないと仕事できないなんて時代遅れだ!リモート導入しろ!的な会社にいかないことが正であるみたいな空気を感じるのですが、実際にしてみた身としては、制度うんぬんの問題というよりも、個々人の状況に応じて取りうる選択肢に幅があることが大事なんじゃなかろうかと思いました。
いつもは会社に行くけど、たまにのっぴきならない理由で出社できなかったり、休むほどでないけど、風邪っぽくて周囲に感染させるのを防止させるために自主的に自宅作業にしたり、その他家族の問題があったりと、色々な理由があるときにリモートもいいよって言われるとすごく心理的に楽だろうなとは思います。実際そういう声聞くし。
(まぁ、書いてる僕自身は独身ひとり暮らしなので、ここらへんはあまり優先度上がってこないことではありますがw)

あんまり極端でどちらかに寄った意見ばっかり目にする機会が多いので、もっとその中間というか、時と場合によってどちらも選べるよみたいな考え方が広まっていくと良いと思いました。

GraphQLを書いてみた話

仕事ではjson-shemaを使ったRESTを使っています。

API定義がドキュメント化されていることの意義

マイクロサービスアーキテクチャを元に開発していると、コンポーネント間のインターフェースの定義なしにはやっていけないと感じています。
もしjson-schemaがなかったら、各コンポーネントのroutingを見て、対応するhandlerがどんなjsonを返すのかそのコードを見に行くしかないです。
また、server sideの側の実装次第ではResponseの内容が急に変わってしまってクライアント側が予期せぬjsonが帰って来るなんてことがあるかもしれないです。

全てをserverサイドで行ったり、モノリシックなアーキテクチャで大規模サービスを運用することが難しくなってきている今、コンポーネント間のインターフェース定義を外出して、それを見ればインターフェースの内容がわかるようになるというのは開発の効率化を鑑みても必要な事になりつつあるように感じます。

幾つか試そうと思ったのですが、少し前から話題になっていて、個人的にも興味があった GraphQL を実際にgoで書きながら実験的に実装してみようと思います。

graphqlの基礎単語

Query

データの取得をする時に使う。
RESTでいうGETの時に使うと思われるクエリ。

Mutation

データの更新をする時に使う。
RESTでいうPOST/PUT(PATCH)/DELETE のときに使うと思われる。

Sample

https://github.com/graphql/graphiql でGraphiQLのライブデモ を触ることが出来る。 このライブでもを元にQueryを勉強してみると、

まずRootのQueryがあります。

f:id:ema_hiro:20180130014423p:plain

この中で allFilms のQueryの定義の中をみると

f:id:ema_hiro:20180130014653p:plain

以下のように引数の各型とResponseの型が決まっています。(ResponseはFilmsCollection型)

f:id:ema_hiro:20180130014710p:plain

I/Fの定義は

  • args(引数)
    • after: String型
    • first: int型
    • before: String型
    •  last: int型

※ FilmのListを取得するメソッドなので、◯話~□話以内とかを指定するための first/last、◯話以内/以上全てを指定するための before/after みたいな定義方法なのかと思います。なんでbefore/afterがStringかは不明(intでいいじゃん)

  • I/FのResponse
    • FilmsCollection(アプリ内で定義)

と読むことが出来ます。 また FillsCollection もどういう定義なのかを追っていくことが出来ます。

graphqlを動かす環境の用意

今回は毎度おなじみgolangで書いてみようと思います。

http://graphql.org/code/#go によると公式にgoのライブラリがあるのでこれを使います。

使用ライブラリ: https://github.com/graphql-go/graphql
Godoc: https://godoc.org/github.com/graphql-go/graphql

depでgraphql-goをinstall

$ mkdir graphql_samples
# direnvでプロジェクトルートをGOPATHに指定します。
$ echo "export GOPATH=$(pwd)" >> .envrc
$ direnv allow
$ mkdir src && mkdir src/graphql_samples && cd src/graphql_samples
# dep を使ってインストール
$ dep init
$ dep ensure -add github.com/graphql-go/graphql                                         
Fetching sources...

"github.com/graphql-go/graphql" is not imported by your project, and has been temporarily added to Gopkg.lock and vendor/.
If you run "dep ensure" again before actually importing it, it will disappear from Gopkg.lock and vendor/.

これで準備完了です。

実際にスキーマを書いてみてる

graphql-go のREALDMEを参考にして書いてみます。

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/graphql-go/graphql"
)

func main() {
    // schema定義
    fields := graphql.Fields{
        "hello": &graphql.Field{
            Type: graphql.String,
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                return "world", nil
            },
        },
    }

    rootQuery := graphql.ObjectConfig{
        Name:   "RootQuery",
        Fields: fields,
    }

    schemaConfig := graphql.SchemaConfig{
        Query: graphql.NewObject(rootQuery),
    }

    schema, err := graphql.NewSchema(schemaConfig)
    if err != nil {
        log.Fatalf("failed to create schema error. err: %+v", err)
    }

    query := `{
      hello
  }`

    params := graphql.Params{
        Schema:        schema,
        RequestString: query,
    }

    r := graphql.Do(params)

    if len(r.Errors) > 0 {
        log.Fatalf("Failed to execute graphql operation. err:%+v", r.Errors)
    }

    j, err := json.Marshal(r)
    if err != nil {
        log.Fatalf("Failed to marshal json. err: %+v", err)
    }

    fmt.Printf("%s, \n", j)
}

動作させてみると

$ go run main.go
{"data":{"hello":"world"}},

Query(GETのリクエスト)では data というプロパティにGETした結果が入ってきます。
schemaFields で定義した "hello" プロパティに返さえるデータの型と結果 Resolve (ここではfuncで文字列を返しているだけですが)が入ってきます。

Getの結果をFiledType指定出来るのは非常に安全にリクエストを定義できていいなと思います。
このあたりは json-schema でも似たような異してますね。

ちなみに以下のような形で int型を入れてみた場合でも

// schema定義
fields := graphql.Fields{
  "hello": &graphql.Field{
    Type: graphql.String,
    Resolve: func(p graphql.ResolveParams) (interface{}, error) {
      return 1, nil
    },
  },
}

実行していみると

# int型
$ go run main.go
{"data":{"hello":"1"}},

nilにしてみます。

// schema定義
fields := graphql.Fields{
  "hello": &graphql.Field{
    Type: graphql.String,
    Resolve: func(p graphql.ResolveParams) (interface{}, error) {
      return nil, nil
    },
  },
}

出力結果は以下

$ go run main.go
{"data":{"hello":null}},

intの場合はjsonでも "1" とString型に、nilの場合は null がちゃんとjsonのdataプロパティ内の hello プロパティに入ってきました。
GraphQLにおいてはResponseの定義は FieldType に絶対変換されてくるので、定義を外出しておけば、クライアントサイドのエンジニアは定義を読めばクライアント側の実装には迷うことなさそうです。

まとめ

まずは簡単なクエリから書いてみました。
普段仕事でjson-schemaを使っているのですが、json形式でなく、コードの形で定義を管理できるのはこれはコレで見やすいと感じました。

次は簡単なAPIを書いてみたり、定義の外出しをやってみようと思います。

勉強記録はこちらに書いていこうと思います。

github.com

参考

developers.eure.jp

speakerdeck.com