Cookpad 5day service dev internship に参加した!

2017/09/11 から 5日間で開催されたcookpadのサービス開発インターンシップにエンジニア枠で参加してきた。

internship.cookpad.com

インターンシップ概要

cookpadインターンシップは二種類あって、今回参加したのはサービス開発を実践的に行う方です。 こちらのインターンシップは就業フェーズは無くて、5日間かけてサービス開発のサイクルを回して1つのサービスを作ろう!という趣旨らしい。

サービス開発は “デザイナー” と “エンジニア” がペアになって、課題発見・価値仮説・ユーザーストーリー・実行・検証といった一連の流れをcookpad製のフレームワークに沿って行っていく。 もちろん価値が見いだせなければ価値仮説まで戻ってやり直すし、ユーザの導線や周知ストーリーも考えないといけない。

日程としては

  • 1日目:元々価値仮説の済んでいる状況から検証まで行いサービス開発に慣れる
  • 4〜5日目:実際に価値仮説から開始して最終発表までサービス開発

で行われる。もちろん後半は実装時間も含まれているし、発表資料も用意する必要があるしで大変……。

f:id:everysick:20170925164007p:plain
やっていってる

インターン成果物

今回のインターンシップテーマは “一人暮らししている人の料理が楽しみになるサービス” を作れというもので、 自分たちのチームは課題発見から価値仮説までがすんなり進んだおかげか、検証もある程度行えて、最終発表までにカタチにすることができた。 作ったアプリは僕の実装が雑なので完成度が高いとは言えないけど、ペアの方が優秀だったのでデザインの適用がサクッと進んで本当に助かった。

ちなみに作ったのはkurashiruクックパッド料理動画のようなサクサク見れる料理動画をスマホ1つで簡単に撮れるアプリ。 以下は最終発表資料で、少しだけ内容を簡素に手直ししている。

speakerdeck.com

余談というか、成果物をApp storeに載せたいと考えていたんだけど、機種別の対応が必要なのと、先日iOS11にしたらまったく起動しなくなってしまったのでまだ先になりそう…。

以下、ポエムと感想。

提供することは難しい

インターンシップでもっとも大変だったことが「これ本当に価値あるの?」という問いに対して、 自分の感性だけでなく定性・定量的なデータを持って「あります。」と言い切るまでブラッシュアップすることだった。 このインターンシップではユーザインタビューという形で定性的なフィードバックをもらい、 価値仮説をし直したりしたので最終的には自信を持って取り組めたけど、最初はやっぱり手が進みそうにない感があった。

もちろん、ユーザーインタビューをするにはアプリとして動作の流れができていないといけないし、 多少自信が無くても価値を提供できることを想定して作らないといけない。 それでユーザーインタビューの結果ズタボロになると結構ずっしりくる。 講義段階で、社内デザイナーの方が “正解はない” という言葉を使っていた。でも “外れはある” と思う。 ただその外れを引いたときには絶対に間違った方向はわかるから、 正解を見つけるためでなく、失敗を理解するために何度も開発サイクルを回すのは本当に大切なんだと実感ができた。

サービスを実装すること

今回はiOS向けでアプリを作ったのだけど、macOS使ってるけどxcode開かないユーザーだったので結構実装は大変だった。 メンターの方がiOSエンジニアで本当に助かったし、頼りになる方だったのでtipsとか色々聞くこともできた。たぶん違う業種の方だったら終わらなかったかもしれない…。

大変だった。大変だったんだけど、実装はしないとそもそもアプリは完成しない。 頑張って、苦しんで、機能をすべて実装しても、それはアプリとして最初から想定していた最低限の機能だからあって当たり前だと思っている。 自分のマインドとして、全て実現できなかったらそれは設計段階で「無理」と言い切れなかったエンジニアの落ち度だし、もしくは実力不足だと考えてる。

正直、今回も全てを実装しきれたわけではなかったので悔しいと思っているし、見積もりも甘かったと思ってる。 これから先何を仕事にしていくかわからないけど、エンジニアとして知識や技量を積んで、実現能力を高めたいなと実感した。

さいごに

お昼は人事の方が美味しいごはん作ってくれたので体験が最高だった…本当に美味しかったです…ありがとうございました…。 普段は1日1食か2食しか食べないのだけど、インターンシップ期間中で3食摂る癖が付いて今も調子が良い。すごい。 講師・メンターの方々、人事の方々、他参加者の方々、5日間ありがとうございました。お疲れ様でした!

f:id:everysick:20170925003225j:plainf:id:everysick:20170925003246j:plain
美味しかった幻のカレーと出汁茶漬け

NGINX unit v0.1 と所感

せっかくなので動作までの作業ログと所感をまとめて書いておく。最新のインターフェースや環境構築については公式README.mdのほうが詳しいので参考程度に。記事内リンクについてはv0.1時点のものを引用しているためmasterとは差分があるので注意。

www.nginx.com

環境

Vagrantubuntu-xenialを用意してgolangの受け答えができるところまで作った

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"

構築

unitの本体を入れる

configurationの受け答えしたり、(将来的に)アプリケーションのリロードや監視などのAPIが生えるであろうパッケージ。yum だと面倒な手順いらないらしいけどaptだとkeyの登録が必要だったりする。インストール手順はREADME.md#ubuntu-packagesを見てほしい。

goのプロジェクト作成

テスト環境なのでディレクトリはGOPATH以下に作ってしまう

$ mkdir -p $GOPATH/src/github.com/your-name/nginx-unit
$ cd $GOPATH/src/github.com/your-name/nginx-unit
$ touch nginx-unit.go

nginx-unit.go

このunitパッケージはgolangだけ必要らしい。PHP, pythonはピュアな応答をするモジュールを作成するだけで済むが、Golangはおそらくバイナリ上でポート設定等をgracefulにやりたかったのでパッケージを別にする必要があったのだと思ってる*1。それはそうと、unitって名前どうにかならないのかな。リネーム前のnginext よりはいいんだけど。

package main

import (
    "fmt"
    "net/http"
    "unit"
)

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("Content-Type", "text/plain")

    fmt.Fprintf(w, "Proto  : %s\n", r.Proto)
    fmt.Fprintf(w, "Method : %s\n", r.Method)
    fmt.Fprintf(w, "URL    : %s\n", r.URL.Path)
    fmt.Fprintf(w, "Host   : %s\n", r.Host)
}

func main() {
    http.HandleFunc("/", handler)
    // port はダミーなので適当で良い
    unit.ListenAndServe("8080", nil)
}

nginx/unitをコンパイル

各種言語用のパッケージをコードからビルドする必要があるのでcloneしてきて必要なものをビルドしていく。依存パッケージのインストールからパッケージのインストールまでをやると動く。特にv0.1に関しては入れるだけで済んだ。

起動

goのプロジェクトをビルドしてパスを控える

$ cd $GOPATH/src/github.com/your-name/nginx-unit
$ go build

unit本体の起動

$ sudo service unitd start

一応socket探す(手元では /run/control.unit.sockにあった)

$ find / -name 'control.unit.sock'

config.json

configuration用のjsonexecutableへは先程のビルドしたバイナリファイルへのパスを書く。また、ポート・ワーカー数などは適当な数に設定する

{
  "listeners": {
    "*:8040": {
      "application": "golang"
    }
  },
  "applications": {
    "golang": {
      "type": "go",
      "workers": 1,
      "executable": "full/path/to/nginx-unit/binary/file",
    }
  }
}

jsonの内容はAPI、もしくはserviceに生えてるインターフェースから適用する

$ sudo curl -X PUT -d @config.json --unix-socket /run/control.unit.sock http://127.0.0.1:8040/
# または
$ sudo service unitd restoreconfig /full/path/to/json

ここまでするとconfigに書いた内容でgolangのプロセスが立ち上がっているのでcurlで確認

$ curl curl http://localhost:8080/foo/bar
Proto  : HTTP/1.1
Method : GET
URL    : /foo/bar
Host   : localhost:8040

さらにconfig.json書き換えることで複数のパスを指定し、複数のプロセスを管理することもできる。

おお、簡単に動くじゃんと思ったが、機能としては現状ここまでらしく、hot reload がされなかったり(インターフェースがまだない?)unitをrestartするとconfigurationは全てpurgeしたり*2するなど全く実用段階ではないことがわかった。

所感

そもそもNGINX unitで何が嬉しいのかっていうと、今のところ言語間のインターフェースを吸収することでUnicornとかuwsgiを必要とせず、さらにアプリケーション内にデプロイ用のコードを個別に実装する必要がなくなるっていうのが強みにかなと思う。あとはHTTPで橋渡しができるのでそれがとにかく楽であった。 公式のKey Featuresを見る限りは動的なプロセス管理の対応や、サービスメッシュになっていくぞという宣言があるのでそこにも期待していきたい。所詮NGINX Application Platformの一機能としてのNGINX unitだが、unitのKey Featuresだけでも高機能なので、完動すればだいぶ心地の良いものになるんじゃないかと思っている。

Linear is Uniqueness ?

幾つかの言語では線形型システム(Linear type system)を採用することによって、線形型を持つオブジェクトを正確に一度だけ使用するということを型システム上で検査している。 それらの型情報は、メモリ管理、ファイルIOなどの資源に対するアクセスを制限するのに役立つ。 具体的には、破壊的変更による副作用の隠蔽や、静的にメモリを解放することが可能になったりする。 また、似たような概念として、たびたび一意型(Uniqueness type)という存在が提示されるときがある。 この一意型も線形型と同じく、オブジェクトを正確に一度だけ使用するということを保証している。

線形型・一意型はそれぞれ部分構造型システム(Substructural type system)の一種である。 部分構造型システムは、部分構造論理という計算規則に制限を加えた論理体系上で計算される型システムの呼称で、いくらかの言語の型システムには線形型のなどが採用されている*1。 他にも、Affine type system(アフィン型システム)はRustが採用しているなどの理由でよく聞くようになった。*2

これらはそれぞれどういう違いがあるのか、ある程度まとまったので日本語にして書き出してみた。 結果から述べると、オブジェクトがただ一つ存在しているという事実の証明にはどちらを用いても変わりはない。しかし、これらが保証する性能は実行時の計算に役立つことがある。

まず言葉の定義として、オブジェクトが線形性を持つということを、この記事では型システムによって線形型が付けられたこととし、 オブジェクトが一意性を持つということを、この記事では型システムによって一意型が付けられたこととする。 このとき、線形性・一意性は以下のような意味を持つ。

線形性

オブジェクトに対して線形性が認められるとき、"以降にオブジェクトは複製(参照)されない" ということが保証される。つまり、全てのオブジェクトに対して線形性が示された場合、各オブジェクトは一度の使用で破棄をしても問題はないことが保証される。また、非線型のオブジェクトの存在を認めたとき、"以降にオブジェクトは複製(参照)されない" という事実はそのオブジェクトの未来に対する再利用可能性を示す。

一意性

オブジェクトに対して一意性が認められるとき、"以前にオブジェクトは複製(参照)されていない" ということが保証される。つまり、全てのオブジェクトに対して一意性が示された場合、各オブジェクトは一度の使用で破棄をしても問題はないことが保証される。また、非一意のオブジェクトを認めたとき、"以前にオブジェクトは複製(参照)されていない" という事実はそのオブジェクトの過去に対する再利用可能性を示す。


これらの解釈は参考文献*3を基に独自に解釈した部分が混ざっているため、必ずしも正確であることは言えない。もし明らかに異なる言葉の用途や定義などが存在している、または見当違いのことを書いているのを発見した場合はコメント等で指摘してほしい。

*1:ATS2では線形型、Cleanでは一意型によって参照がただ一つということを型システム上で検査している

*2:正しくはRustの前身であるCycloneにおけるRegionにてAffine typeを採用している[要出典]。Rustコミュニティが使っていると示唆する文献は無い

*3:De Vries, Edsko, Rinus Plasmeijer, and David M. Abrahamson. ”Uniqueness typing simplified.” Symposium on Implementation and Application of Functional Languages. Springer Berlin Heidel- berg, 2007.