YAPC::ASIA Tokyo 2015に参加してきた #yapcasia
YAPC::ASIA Tokyo 2015
2015年8月22日に開催されたYAPC::ASIA Tokyo 2015に参加してきました。1日目は参加できず2日目のみの参加です。なお僕は初参加で、かつYAPCは今回で最後らしいです。
セッション
スピーカーのNathan LeClaireさんはDocker Machineの中の人。Polyglotとは複数言語、複数のツールを扱う人のことを指す。
今まで、エンジニアが新しい技術を学ぼうとするとき、毎回開発環境の方法を学ばなければならず、それが新しいユーザ参入の障壁となっていた。特に今のフロントエンド界隈は6ヶ月で新しい技術が生み出され(AngularJS,React.js,Grant,Gulp,etc..)、そのたびに環境構築に学習コストを払うのは辛い。
Dockerの魅力はそういった参入障壁をなくし、誰でも簡単に開発を始められるようにすること。ユーザの島と技術の島の橋渡しをする存在ということでした。
なお、質疑応答でDocker.incの中の人はほとんどがboot2dockerを使っているとのこと。
Docker3兄弟とは、
- Docker Machine
- Docker Compse
- Docker Swarm
のことを指す。(勝手に呼んでいる)
これにDocker Toolboxが加わり4兄弟になった。Docker Toolboxは3兄弟 + VirtualBox + GUIツールの全部入り。
Docker3兄弟のおかげでDockerがゆるふわに使えるようになった。開発環境では十分利用可能で、プロダクションに使うにはまだ課題があるがロードマップが提示されている。
歴代の実谷委員長が集まり、今までのYAPCの思い出話から、イベント運営に関するノウハウが語られました。以下内容を箇条書き。
- 入場者数は300人から2000人
- スポンサー数は6社から60社
- ワイエーピーシー?ヤップク?ヤップシー。
- ✕ 「ヤップシーアジア」◯ 「ヤップシーエイジア」
- ライチタイムをリジェクトしたらTwitterが「お腹すいた」で溢れた
- トーク観るのは素人
- LTはYAPC発祥(Asiaではない)
- 高橋メソッドもそこで生まれた
- 大学を会場にしたいときはヒエラルキーを理解する
www.slideshare.net
mozaic.fmのJxckさんによるHTTP2のお話。
HTTP2はすでにRFCになっており、策定するフェーズから使うフェーズになっている。既にいくつかのHTTP2実装が世に出されている。
HTTP1.1はシンプルな分、これ以上速度をあげることに限界がきていた。Keep AliveやFile Concatなどの実装を駆使して対応してきたが、これらはバッドハックであり、ビルドシステムなどが複雑化していく。
HTTP2は1つのコネクション上でストリームを多重化することで、3 way handshakeが1回で済み、パフォーマンスの一番美味しいところを維持できる。
かつ、セマンティクス(各メソッドの意味とかHTTPステータスの意味とか)は維持され、下位互換も保証される。
我々はどうするべきか?
- よく知らない/導入しない
HTTP1.1はこれからも生き続けるので問題ない
- 理解した/導入しない
戦略としてはありだが、今後もパフォーマンスのためにバッドハックをし続けなければならない。どこかで見定める必要あり。
- よく知らない/導入する
エコシステムの成熟しだいではあり。そもそもHTTPはよく知らなくても動くべきだし、既にクライアント(ブラウザ)は知らないうちに導入されている。(ただ、エンジニアとしてはどうなの...)
さいごに
特に何を聴くか考えずに来たけど割りとDocker寄りになったので自分でも興味があるのだろう。Dockerちゃんと勉強します。
とても素晴らしいイベントだっただけに1日しか行けなかった悔しさとこれで最後という悲しみがあります。実行員会の方々、スピーカーの方々、その他運営に関わった方々には大変感謝しております。ありがとうございました。
Photos
Six apart pic.twitter.com/IjySAKvKlB
— ぺら (@Peranikov) 2015, 8月 22
emacs pic.twitter.com/bXlIKLDWQX
— ぺら (@Peranikov) 2015, 8月 22
vim pic.twitter.com/iTJdNCStSQ
— ぺら (@Peranikov) 2015, 8月 22
diffコマンドでファイルではなく標準出力を比較する(Process Substitution)
『アジャイルサムライ横浜道場 特別編「RSGT再演 くじびきイテレーション改』 に参加してきました #agilesamurai #横浜道場
2015年5月12日に開催された『アジャイルサムライ横浜道場 特別編「RSGT再演 くじびきイテレーション改』 に参加してきました。
台風ですが、横浜道場始まってます
#agilesamurai #横浜道場 (at 西公会堂・西地区センター) [pic] — https://t.co/jZAmiWCFWJ
— KIMURA Takao (@tw_takubon) 2015, 5月 12
www.slideshare.net
今回は特別回として今給黎さんを講師にお招きし、ゲームを通してスクラムを体験するというワークショップを行いました。細かいルールはスライドの方に書いてありますが、1ターンを1dayとし、1ゲームを1スプリント、場のカードをバックログに見立て、それを見ながら見積もりをします。そして手札のカードを使ってバックログを消化し、最後にふりかえりをするというスクラムの一連の流れがこのゲームに凝縮されています。
これを5セット行いますが、セットとともに追加ルールが加わります。加わるルールは以下になぞらえられたものでした。
- 個々人が決められたタスクしかできない(単能工)
- タスクを協業できる(協業)
- 個々人の能力が違い、それぞれの能力をチームは把握している
- 個々人の能力が違い、それぞれの能力をチームは把握していない
これらを通して学んだことは、
- 決められた仕事しかできないと、スプリント内で消化できるタスクが制限される
- 優先度が高くコストが重いタスクでも、協業できれば(タスクを分割すれば)確実に消化できるようになる
- 個々人の能力をチームが把握していれば、見積りの精度が増す。
- 逆に、個々人の能力をチームが把握していないと、アクシデントが起こる可能性が増す(見積もりを予測しにくくなる)
といったことでした。事実、僕らのチーム*1は能力を把握していないときの見積もりのブレが1、把握しているときが5と顕著に現れました。
さいごに
今回のワークショップはとても白熱し、たのしくスクラムが学べたかと思います。 講師をしていただいた今給黎さん、スタッフの方々、参加された方々ありがとうございました。
*1:チーム名は「Dieふごう」にしました
kawasaki.rb #23 に参加した & 寄稿した #kwskrb
2015/4/23に行われたkawasaki.rbに参加してきました。
今回はこちらのブログに内容をまとめるのではなく、kawasaki.rb公式ブログに寄稿という形をとりました。以下がその記事です。
kawasaki.rb #023を開催しました #kwskrb
大体の内容は上の記事に書かれているのでこちらには個人的な内容を書きます。 寄稿する流れとなったのは以下のツイートから
— chezou (@chezou) 2015, 4月 22
と、kawasaki.rb発起人のchezou氏がMacを会社に置いてきてしまったので、代わりにパーフェクトRubyの電子版が入っていた僕のMacで進行することにしました。初めて進行をしましたが、予想以上に大変でした。傍から見ててもてんやわんやしてたと思います。
というわけで今回試したコードの履歴は僕のpryのhistory上にあったので、ついでに寄稿することにしました。
終わってから
そういえば、今回のところは以前読書会した「なるほどUnixプロセス」で深く理解してたはずなのに、あまり気の利いた発言できなかったなぁとか終わってから思ってました。良い本ですので読んだほうが良いです。紹介は以下から。
最後に
kawasaki.rbの皆様ありがとうございました。次回もよろしくお願いします。
Fluxを修得するためにkriasoft/react-starter-kitをコードリーディングした
発端
react-starter-kitのコード読んでて、Server側でStoreのコード呼んでServerSide Renderingしててこれがisomorphicか...! ってなってる
— ぺら@Civ中毒リハビリ中 (@Peranikov) 2015, 4月 2
実践的なFluxの勉強するためになにかいいサンプルコードないかと漁っていたら、KriaSoftがGithub上で公開しているreact-starter-kitが自前でReact.jsでのページネーションを実装してたりいろいろと参考になったのでコードリーディングしてみました。
kriasoft/react-starter-kit · GitHub
DEMO
ちなみにGithubのdescriptionにも書かれていますが、このstarter-kitはBabel (ES6), JSX, Gulp, Webpack, BrowserSync, Jest, Flowとモダンなツールが全部入りなので、あらゆる方面から勉強になりました。
なお、これから貼るコードは抜粋なので、ソースコードを落としてエディタで見るかDevelopmentToolを実行しながら確認した方が良いかと思います。
src/templates/index.html
<!doctype html> <html class="no-js" lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title><%- title %></title> <meta name="description" content="<%- description %>"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="apple-touch-icon" href="apple-touch-icon.png"> <link rel="stylesheet" href="/css/bootstrap.css"> <script src="/app.js"></script> </head> <body> <!--[if lt IE 8]> <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> <![endif]--> <%= body %> <!-- Google Analytics: change UA-XXXXX-X to be your site's ID. --> <script> ... </script> </body> </html>
まずはindex.htmlから。GoogleAnalyticsのコードなどを除けばこれだけ。 bodyは変数となっており、ここはserver側で定義しています。
src/server.js
// The top-level React component + HTML template for it var App = React.createFactory(require('./components/App')); var templateFile = path.join(__dirname, 'templates/index.html'); var template = _.template(fs.readFileSync(templateFile, 'utf8')); server.get('*', function(req, res) { var data = {description: ''}; var app = new App({ path: req.path, onSetTitle: function(title) { data.title = title; }, onSetMeta: function(name, content) { data[name] = content; }, onPageNotFound: function() { res.status(404); } }); data.body = React.renderToString(app); var html = template(data); res.send(html); });
サーバ側はexpressで動作しています。ReactのComponentであるAppを定義し、renderToStringで描画しています。Isomorphic感が出ています。
src/components/App/App.js
render() { var page = AppStore.getPage(this.props.path); invariant(page !== undefined, 'Failed to load page content.'); this.props.onSetTitle(page.title); if (page.type === 'notfound') { this.props.onPageNotFound(); return React.createElement(NotFoundPage, page); } return ( <div className="App"> <Navbar /> { this.props.path === '/' ? <div className="jumbotron"> <div className="container text-center"> <h1>React</h1> <p>Complex web apps made easy</p> </div> </div> : <div className="container"> <h2>{page.title}</h2> </div> } <ContentPage className="container" {...page} /> <div className="navbar-footer"> <div className="container"> <p className="text-muted"> <span>© Your Company</span> <span><a href="/">Home</a></span> <span><a href="/privacy">Privacy</a></span> </p> </div> </div> </div> ); }
実際にbody部に描画されるのはApp.jsのrender内に定義されています。 また、ページネーションを実現しているhandleClickもこのApp.jsで定義されています。
handleClick(event) { if (event.button === 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.defaultPrevented) { return; } // Ensure link var el = event.target; while (el && el.nodeName !== 'A') { el = el.parentNode; } if (!el || el.nodeName !== 'A') { return; } // Ignore if tag has // 1. "download" attribute // 2. rel="external" attribute if (el.getAttribute('download') || el.getAttribute('rel') === 'external') { return; } // Ensure non-hash for the same path var link = el.getAttribute('href'); if (el.pathname === location.pathname && (el.hash || link === '#')) { return; } // Check for mailto: in the href if (link && link.indexOf('mailto:') > -1) { return; } // Check target if (el.target) { return; } // X-origin var origin = window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); if (!(el.href && el.href.indexOf(origin) === 0)) { return; } // Rebuild path var path = el.pathname + el.search + (el.hash || ''); event.preventDefault(); AppActions.loadPage(path, () => { AppActions.navigateTo(path); }); }
根幹は最後にあるAppActions.loadPageで、その前のif文は対象としないリンクを弾くためのガード節です。
休憩
Actionが出てきたので、Fluxについておさらいしておきます。
よくあるFluxのフローをまとめた図です。今見てきたところはViewの部分で、これからViewからのイベントハンドリングを管理するAction、各Storeにメッセージを発信するDispatcher、データを管理するStoreへと流れていきます。コードリーディングする際にも、一連の流れに沿っているFluxの考え方の恩恵が受けられますね。
src/actions/AppActions.js
loadPage(path, cb) { Dispatcher.handleViewAction({ actionType: ActionTypes.LOAD_PAGE, path }); http.get('/api/page' + path) .accept('application/json') .end((err, res) => { Dispatcher.handleServerAction({ actionType: ActionTypes.LOAD_PAGE, path, err, page: res.body }); if (cb) { cb(); } }); }
App.jsから呼んでいたloadPageの抜粋です。Dispatcher.handleViewActionを呼んだ後に、サーバへGETでリクエストし遷移先のbodyを受け取り、Dispatcher.handleServerActionを呼んでいます。サーバ側より先にシンプルなDispatcherのコードを見てみます。
src/core/Dispatcher.js
let Dispatcher = assign(new Flux.Dispatcher(), { /** * @param {object} action The details of the action, including the action's * type and additional data coming from the server. */ handleServerAction(action) { var payload = { source: PayloadSources.SERVER_ACTION, action: action }; this.dispatch(payload); }, /** * @param {object} action The details of the action, including the action's * type and additional data coming from the view. */ handleViewAction(action) { var payload = { source: PayloadSources.VIEW_ACTION, action: action }; this.dispatch(payload); } });
Dispatcherの仕事は単純で、各StoreにActionからの通知を横流しします。ここでは、Serverからのデータか、Viewからのデータかでメソッドを使い分けています。Dispatcherはpayloadと呼ばれるオブジェクトでactionオブジェクトをラップし、データ元などの情報を付加してdispatchメソッドでStoreへ通知します。
Storeで通知を受ける前に、Actionから呼んでいたサーバ側の処理を覗いてみます。
src/server.js
// // Page API // ----------------------------------------------------------------------------- server.get('/api/page/*', function(req, res) { var urlPath = req.path.substr(9); var page = AppStore.getPage(urlPath); res.send(page); });
ページのbodyを返すサーバのAPIでは、なんとStoreのコードを呼んでいました。
src/stores/AppStore.js(Serverからの呼び出し)
var pages = {}; ... if (__SERVER__) { pages['/'] = {title: 'Home Page'}; pages['/privacy'] = {title: 'Privacy Policy'}; } var AppStore = assign({}, EventEmitter.prototype, { ... /** * Gets page data by the given URL path. * * @param {String} path URL path. * @returns {*} Page data. */ getPage(path) { return path in pages ? pages[path] : { title: 'Page Not Found', type: 'notfound' }; }, ...
AppStoreのgetPageでは、与えられたパスに対するオブジェクトを返しています。存在しないパスに対するハンドリングも行っており、ここでルーティングの役目を担っています。
サーバ側はこれで終わりなので、StoreがDispatcherからの通知を受けたところから再開します。
src/stores/AppStore.js(Dispatcherからの通知)
AppStore.dispatcherToken = Dispatcher.register((payload) => { var action = payload.action; switch (action.actionType) { case ActionTypes.LOAD_PAGE: if (action.source === PayloadSources.VIEW_ACTION) { loading = true; } else { loading = false; if (!action.err) { pages[action.path] = action.page; } } AppStore.emitChange(); break; default: // Do nothing } });
AppStoreの下部の方でDispatcher.registerを実行しており、これによりDispatcherからのdispatchメソッドによって通知が受け取れるようになっています。
通知を受けた後に処理をしているのはActionTypesがLOAD_PAGE、つまりAppActionのloadPageメソッドからの通知を受けた時で、かつPayloadSourcesがVIEW_ACTION以外、つまりSERVER_ACTIONの時ですね。(VIEW_ACTIONの時にもloading = trueと代入していますが、この変数を実際に使っている箇所はありませんでした。)
AppSotreのpagesオブジェクトに、パスと対応するbodyの情報をセットし、emitChangeを呼んでStoreの仕事は終わりです。
/** * Emits change event to all registered event listeners. * * @returns {Boolean} Indication if we've emitted an event. */ emitChange() { return this.emit(CHANGE_EVENT); },
emitChange内部ではただEventEmitterのemitを呼んでいるだけですね。 で、後はViewがStoreからの通知を受け取ってレンダーするだけ…かと思っていたのですが、このサンプルではView側でStoreの通知を監視していませんでした。そこで、まだ解説していなかったコードを見ていきます。
src/components/App.js(AppActions.loadPage後)
handleClick(event) { ... event.preventDefault(); AppActions.loadPage(path, () => { AppActions.navigateTo(path); });
handleClick内でAppActions.loadPageを実行後、コールバックでAppActions.navigateToを実行しています。
src/actions/AppAction.js
navigateTo(path, options) { if (ExecutionEnvironment.canUseDOM) { if (options && options.replace) { window.history.replaceState({}, document.title, path); } else { window.history.pushState({}, document.title, path); } } Dispatcher.handleViewAction({ actionType: ActionTypes.CHANGE_LOCATION, path }); },
AppActionsのnavigateToでは、pushState/replaceStateで履歴を操作していました。その後はactionTypeをActionTypes.CHANGE_LOCATIONとしてDispatcher.handleViewActionを実行しています。そして、CHANGE_LOCATIONをどこで監視しているのか調べていくと、
src/app.js
function run() { // Render the top-level React component let props = { path: path, onSetTitle: (title) => document.title = title, onSetMeta: setMetaTag, onPageNotFound: emptyFunction }; let element = React.createElement(App, props); React.render(element, document.body); // Update `Application.path` prop when `window.location` is changed Dispatcher.register((payload) => { if (payload.action.actionType === ActionTypes.CHANGE_LOCATION) { element = React.cloneElement(element, {path: payload.action.path}); React.render(element, document.body); } }); }
と、app.jsのrunメソッドの中でDispatcher.registerを呼んでいました。 この後はCloneされたAppのrenderが呼ばれ、ここで描画が完了します。
最後のここの部分に関しては、Fluxに従うのであればStoreから通知を受けたViewで処理するのではないのかと思いましたが、アプリケーション全体に関わる部分だからapp.jsで処理をしているとかでしょうか。
ちなみに、App.js内でpopstateをaddEventListenerすることでブラウザバックにも対応していました。
... componentDidMount() { window.addEventListener('popstate', this.handlePopState); window.addEventListener('click', this.handleClick); } ... handlePopState(event) { AppActions.navigateTo(window.location.pathname, {replace: !!event.state}); }
わからなかった英単語まとめ その1
英語の記事を読んだりした中でわからなかった単語をまとめるメモ
curiosity
a 好奇心
[用例] from [out of] curiosity 好奇心から、物好きに
b <...したい> 気持ち
[用例] She satisfied my curiosity to know the reason. そのわけを知りたいという私の好奇心を満足させてくれた
satisfied
a 満足した、満ち足りた
[用例] a satisfied customer. 満足した客
detect
a 見つける,看破する; 発見する; 検出する.
[用例] detect a lie. 嘘を見破る
sophisticate
a <人を>世間慣れさせる, 洗練させる
b <機械などを> 複雑化する、精巧なものにする
kawasaki.rb #22 に参加してきました #kwskrb
2015/3/25に開催されたkawasaki.rb #22に参加してきました。
Doorkeeper
Kawasaki.rb #022 - Kawasaki.rb | Doorkeeper
Togetter
パーフェクトRuby読書会
以降のコードはRuby 2.2.0で試しています。
3-5-12 ::を使ったメソッド呼び出し
Time::now
ドットでなく::でメソッドを呼ぶことができます。ちなみにrubocopに怒られます。
3-5-13 メソッド定義の取り消し
undefを用いることでメソッドの定義を取り消すことができます。
def greet puts 'hora' end greet # => "hora" undef greet greet # => undefined local variable or method `greet'
ところでこれっていつ使うんだろうという話題になり、例えばRails上でデザイナと協業する際、View側で代入メソッドを呼ばせたくない時などにundefして置く、という話を聞きました。
3-5-14 メソッドに別名をつける
aliasを用いることでメソッドに別名を付けることができます。
alias greet puts greet "hi" #=> "hi"
また、元のメソッドをundefしても別名としたメソッドは影響を受けません。
undef puts greet "ho" #=> "ho"
ここでは以下のコードが話題となりました。
def foo raise end alias bar foo bar #=> `foo': unhandled exception
と、呼び出しているのはbarですが例外はfooで発生したというメッセージになっています。
さらに、
def foo raise end alias bar foo undef foo # fooをundefしてみる bar #=> `foo': unhandled exception
と、fooはundefしているにも関わらずfooの名前が表示されるという現象があります。
3-6-1 文字列の入出力
puts、print、sprintfなどの説明がありました。sprintfは、そのショートハンドであるString#%の方がよく使うという話をしました。
sprintf("%04d", 1) #=> "0001" "%04d" % 1 #=> "0001"
String#%のパラメータは配列でも渡せます。
"%03dパーセント中の%02dパーセント" % [100, 50] #=> "100パーセント中の50パーセント"
なんとハッシュでも渡せるとのことでした。
"あなたは%<num>08d人目のお客様です" % {num: 10} #=> "あなたは00000010人目のお客様です"
なんか「名前付きキャプチャ」っぽい!ということで、名前付きキャプチャの話題となりました。
/(?<num>\d+)/ =~ "10人目" num # => "10"
個人的にはこの機能を知らなくて、いきなり変数にバインドされるというのはいくらRubyでも衝撃的でした。 ただ、
m = /(?<num>\d+)/.match("10人目") m[:num] #=> "10"
とすればシンボルでアクセスできるので安全に取り扱うことができます。
今回の読書会はここまででした。今回はなんとも濃い内容でした。
セッション
受託開発でAnsibleを導入した話 @Peranikov
www.slideshare.net
会社でAnsibleを試して少し知見が溜まったのでしゃべりました。 触ったと言っても1日ちょっと試しただけですが、それでもきちんと動くものにはなりました。これもAnsibleのシンプルさのおかげだと思っています。
今回のスライドではtaskについてしか触れていませんが、実際にはvars(変数定義)やtemplate(テンプレートエンジンでconfigファイルを動的に変更)などの機能もあるので実運用にはかなうんじゃないかなと思います。
なにより、PostgresモジュールがAnsible自体に組み込まれているのは個人的には凄く大きいです。(外部のPlaybookに依存したりしなくて済む)
Gemの作り方 ~BacklogのAPIを利用して~ @kon_yu さん
Gemまだ作ったことないです... RubyGemsの作り方から公開とハマりどころまで丁寧に解説されているので、実際作ることがあったら参考にしたいです。
おわりに
kawaski.rbのみなさま、NTT-AT様ありがとうございました。次回もまたよろしくおねがいします。