OPEN8テックブログ 2021-12-01T10:24:18+09:00 open8tech Hatena::Blog hatenablog://blog/8599973812323618691 新卒フロントエンジニア経験談 hatenablog://entry/13574176438028383344 2021-12-01T10:24:18+09:00 2021-12-01T10:24:18+09:00 はじめに 苦労したこと コードの規模がデカい 何が大変だったか どのように克服したか 何もわからんReact, Redux 何が大変だったか どのように克服したか トリッキーすぎるCSS 何が大変だったか どのように克服したか まとめ はじめに 初めまして、2021年新卒の中島です。 いつの間にか入社から7,8ヶ月経っていることに気付き、時の流れが早すぎて焦りを感じています。 僕は入社してから半年はフロントエンジニアとしてReact, Reduxを中心としたフロントエンド開発を行ってきましたが、10月からバックエンドエンジニアとして働くことになりました。 そこで今回の記事ではこの半年間のフロン… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/rindy1123/20211130/20211130100206.jpg" alt="f:id:rindy1123:20211130100206j:plain" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"><ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#苦労したこと">苦労したこと</a><ul> <li><a href="#コードの規模がデカい">コードの規模がデカい</a><ul> <li><a href="#何が大変だったか">何が大変だったか</a></li> <li><a href="#どのように克服したか">どのように克服したか</a></li> </ul> </li> <li><a href="#何もわからんReact-Redux">何もわからんReact, Redux</a><ul> <li><a href="#何が大変だったか-1">何が大変だったか</a></li> <li><a href="#どのように克服したか-1">どのように克服したか</a></li> </ul> </li> <li><a href="#トリッキーすぎるCSS">トリッキーすぎるCSS</a><ul> <li><a href="#何が大変だったか-2">何が大変だったか</a></li> <li><a href="#どのように克服したか-2">どのように克服したか</a></li> </ul> </li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>初めまして、2021年新卒の中島です。 いつの間にか入社から7,8ヶ月経っていることに気付き、時の流れが早すぎて焦りを感じています。</p> <p>僕は入社してから半年はフロントエンジニアとしてReact, Reduxを中心としたフロントエンド開発を行ってきましたが、10月からバックエンドエンジニアとして働くことになりました。</p> <p>そこで今回の記事ではこの半年間のフロントエンド開発の集大成として、VideoBRAINのフロントエンド開発をしていく中で自分がどのようなことにつまずいたか、またどのようにその壁を乗り越えてきたかをお話ししたいと思います。</p> <p>今後新卒のフロントエンジニアとして働いてみたいという方々の参考になれば嬉しいです。</p> <h1 id="苦労したこと">苦労したこと</h1> <h2 id="コードの規模がデカい">コードの規模がデカい</h2> <p>入社してから<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を見て、すごい量だなと思いました。</p> <p>僕は大学生の頃<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生としてフロント・バックエンド開発を行っていたのですが、基本的にまだ世に出ていないサービスを作ることが多く、0→1のフェーズで開発することが多かったため、コードの規模は個人開発のものより少し大きいくらいでした。</p> <p>それらのコードの規模と比べるとVideoBRAINのコードはかなり大きく、圧倒されました。</p> <h3 id="何が大変だったか">何が大変だったか</h3> <p>VideoBRAINは既に世に出ているサービスで、さらにサービスの性質上フロントのロジック等が複雑なので、とにかく何が起こっているのかを読み解くのが難しかったです。</p> <p>ファイル数も多く、どこから手をつけていいかわからないという状況でした。</p> <h3 id="どのように克服したか">どのように克服したか</h3> <p>まずはUIや機能がどのコードに結びついているのかを把握するようにしました。</p> <p>その上で非常に役に立ったのが、<a href="https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en">ReactのChrome拡張機能</a>でした。</p> <p>この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a>を使うことでどの部分がどのコード(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>)かをすぐに把握することができるようになりました。</p> <p>おかげでどこに何があるかまでは把握し切れていないけど、修正したいファイル・コードがどのあたりにあるのかがわかるようになり、規模の大きいコードでもスムーズに開発を行えるようになりました。</p> <p>React以外でもVue.jsやSvelteなどの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>でも<a class="keyword" href="http://d.hatena.ne.jp/keyword/Chrome">Chrome</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a>があるみたいなので、フロントエンド開発をする際はぜひ使ってみてください。</p> <h2 id="何もわからんReact-Redux">何もわからんReact, Redux</h2> <p>既に言及していますが、弊社のフロントエンド開発では<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>としてReact,状態管理ライブラリとしてReduxを使用しており、僕にとってこれらのツールを使うのは初めての経験でした。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>ではVue.jsやSvelteを使っていましたが、この二つとは違った特徴があるので少し苦戦しました。</p> <h3 id="何が大変だったか-1">何が大変だったか</h3> <p>まずReactの基本的な書き方に最初は戸惑いました。useState, useEffectとはなんぞやという感じだったので、最初はその辺りを学ぶ必要がありました。</p> <p>また、描画時の条件分岐や反復処理などは<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a><a href="#f-b02b523d" name="fn-b02b523d" title="弊社はTypeScriptを導入しているので、正確にはTypeScriptになります">*1</a>の文法で行うというのもVue.jsやSvelteにはなかったので最初は慣れませんでした。</p> <p>Reduxに関しても状態管理はUIとの紐付きが薄いため把握するのが難しく、コードを地道に追っていく必要がありました。</p> <h3 id="どのように克服したか-1">どのように克服したか</h3> <p>一番最初は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Youtube">Youtube</a>に載っているReactやReduxの動画で手を動かしながら、全体的な開発の流れや開発方法などの概要を把握するようにしました。</p> <p>個人的には動画が一番情報として頭に入りやすいので、概要を把握するには動画をいつも活用しています。</p> <p>概要を把握した後は公式ドキュメントで情報をインプットしていました。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>やライブラリなどの動きを細かく把握するのには公式ドキュメントを読むのが一番いいと思っていますが、最初から公式ドキュメントを読み進めようとすると情報量が多くなかなか大変なので、動画や他の記事で概要を把握してから公式ドキュメントに手を出すのがいいかなと思います。</p> <p>ある程度インプットできたら後は慣れるしかないですかね。</p> <h2 id="トリッキーすぎるCSS">トリッキーすぎる<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a></h2> <p>個人的にフロントエンド開発をやっていて一番大変だなと思ったのは<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>です。</p> <p>プログラミングを勉強し始めてWebページを作ろうとなったときは<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>でどんどん整っていく自分のページを見て感動した記憶がありますが、まさかフロントエンド開発最大の敵になるとは当時思ってもいませんでした。</p> <p>ちなみに弊社では<a href="https://styled-components.com/">styled-components</a>を使っています。</p> <h3 id="何が大変だったか-2">何が大変だったか</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>は何かと言うことを聞いてくれないので、思い通りにならないのが大変でした。</p> <p>まず自分で<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を書いても画面が崩れたり、変更が反映されてなかったりしました。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a>で書き方を調べて参考にしながら書いても他の箇所にかかっている<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の影響で調べた通りにいかないなど辛い思い出ばかりです。</p> <p>泥沼にハマれば、ロジックを実装するよりも時間がかかってしまうこともしばしばありました。</p> <h3 id="どのように克服したか-2">どのように克服したか</h3> <p>正直<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>に関しては克服できている気がせず、未だに<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>怖いなーと思っています。</p> <p>実装してみてうまく動かなかったり反映されなかった場合はとにかく調べることしかできませんでした。</p> <p>参考にしてたサイトとしては主に<a href="https://www.w3schools.com/">W3Schools</a>だったり、<a href="https://developer.mozilla.org/en-US/">MDN Web Docs</a>を使っていました。</p> <p>どうやって<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を克服できるかわかる方いらっしゃったら教えてください。</p> <h1 id="まとめ">まとめ</h1> <p>今回はこの半年間で経験したフロントエンド開発についてご紹介しました。</p> <p>VideoBRAINのフロントエンド開発は簡単ではなかったですが、今まで単純なUIしか実装したことがなかったのですごく勉強になりました。</p> <p>特に動画の編集画面がどのような仕組みで動いているのかが入社前から気になっていたので、知ることができて非常にためになりました。</p> <p>今後はバックエンド開発をメインに行うことになるので、次はバックエンド開発について話すことになるかもしれません。</p> <div class="footnote"> <p class="footnote"><a href="#fn-b02b523d" name="f-b02b523d" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">弊社はTypeScriptを導入しているので、正確にはTypeScriptになります</span></p> </div> rindy1123 技術顧問 古川陽介さんによるオープンエイト社内勉強会を実施しました! hatenablog://entry/26006613790854164 2021-08-11T17:04:44+09:00 2021-08-11T17:04:44+09:00 オープンエイト プロダクト開発部の有田です。 2021年4月、Japan Node.js Association などでも活動されている古川陽介さんがオープンエイトの技術顧問に就任されました。 open8.com この記事は、就任にあわせて古川さんが過去に発表された「エンジニアになる覚悟」を現在のフロントエンドエンジニアの周辺環境や、オープンエイトの技術スタックなども踏まえて社内勉強会での発表をお願いし、先日社内で実施された勉強会の内容についてご紹介させていただきます。 当日の資料は SpeakerDeck で公開していただいているので、本記事と合わせてお楽しみください。 speakerdec… <p>オープンエイト プロダクト開発部の有田です。</p> <p>2021年4月、Japan Node.js Association などでも活動されている古川陽介さんがオープンエイトの技術顧問に就任されました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen8.com%2Fnews%2Fpressrelease%2F9808%2F" title="Japan Node.js Association の代表理事 古川陽介氏が技術顧問に就任動画編集クラウド「Video BRAIN」をはじめとするプロダクトの技術開発向上と開発体制の強化を促進 | 株式会社オープンエイト" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://open8.com/news/pressrelease/9808/">open8.com</a></cite></p> <p>この記事は、就任にあわせて古川さんが過去に発表された「エンジニアになる覚悟」を現在のフロントエンドエンジニアの周辺環境や、オープンエイトの技術スタックなども踏まえて社内勉強会での発表をお願いし、先日社内で実施された勉強会の内容についてご紹介させていただきます。</p> <p>当日の資料は SpeakerDeck で公開していただいているので、本記事と合わせてお楽しみください。</p> <p><iframe id="talk_frame_754191" src="//speakerdeck.com/player/d2a778eb498a4276b83b38d76aa5cd8d" width="710" height="532" style="border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/yosuke_furukawa/hurontoendoenzinianinarujue-wu-in-open8">speakerdeck.com</a></cite></p> <h3>フロントエンドエンジニアはUX(ユーザー体験)を向上するエンジニア</h3> <p>勉強会の導入として、フロントエンドエンジニアとはどんな人なのか、なぜ必要とされているのかについてお話しいただきました。</p> <p>フロントエンドエンジニアという職種はそれほど歴史が長くなく、ここ数年でよく聞くようになった印象がありますが、それはここ数年のデ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9">バイス</a>の進化や、それに付随して求められるようになった UI/UX の変化も要因になっていて、今日では「UXを向上する」という役割を担う人としてフロントエンドエンジニアが求められているというお話しでした。</p> <h3>UX を向上するために必要なこと</h3> <p>UX を向上するエンジニアという理解の上で、具体的に求められることはどのような知識や行動かについて、当日の勉強会では「ブラウザの体験を壊さない」「ブラウザとサーバについて知る」という2つのテーマで深堀していただきました。</p> <h4>ブラウザの体験を壊さない</h4> <p>ブラウザの体験を壊さないというと難しく考えてしまいますが、例としては更新、戻る、進むなどのブラウザの機能や、アドレスバーに表示された URL、フォーム入力時に Enter キーが入力されたときの挙動など、普段ブラウザを使っていて自然と利用する機能についてお話しいただきました。</p> <p>ここでは例として、検索結果のような一覧性のある画面で複数ページにまたがるような表示があった際に、3ページ目を表示した状態でブラウザの戻るボタンをクリックしたとき、2ページに戻ると期待していたのに検索フォームに戻されてしまうようなケースをあげられていました。</p> <h4>ブラウザとサーバについて知る</h4> <p>常に変化し続けているフロントエンド関連技術ですが、Web アプリケーションはブラウザや <a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> だけで成り立つことは少なく、サーバとのやりとりがほぼ確実に発生します。 そのため、UX を向上させるにはブラウザなどのフロントエンドに関連する知識とあわせて、サーバについても理解が必要になります。</p> <h4>ブラウザについて</h4> <p>フロントエンドエンジニア向けの発表ということもあり、おさらい的なものではありましたが、HTML などのリソース読み込みや<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>など、画面表示までの基本的な動きについてあらためて紹介していただき、Web アプリのパフォーマンス指標としてよく利用される First Paint などの指標や、DOM と <a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a> の関連性についてもご説明いただきました。</p> <h4>サーバについて</h4> <p>サーバについては、フロントエンド開発では <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> の呼び出しなどで使用している HTTP(S) リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト・レスポンスがどのような手順や通信の上で成り立っているかを、普段サーバサイドやインフラからは少し縁遠いエンジニアでもイメージできるよう、<a class="keyword" href="http://d.hatena.ne.jp/keyword/DNS">DNS</a> ルックアップ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/TCP">TCP</a> や HTTP(S) などについて図を交えてご説明いただき、自然に利用している通信処理は多くの手順や通信によって成り立っていることがわかるような内容となっていました。</p> <h3>フロントエンドエンジニアとしての心構え</h3> <p>フロントエンドエンジニアに求められる知識などについてのお話しいただいたあとには、古川さんのキャリアやこれまでのご経験をふまえて、日頃意識されている「知識に垣根を作らない」と「継続する」についてお話しいただきました。</p> <p>「知識に垣根を作らない」については勉強会をお願いしたきっかけのひとつとなった「エンジニアになる覚悟」でも紹介されていましたが、フロントエンドエンジニアであっても、バックエンドや運用などに好奇心をもって取り組み、フロントエンドの知識を軸にしてスキルを広げていくことの面白さについて、「継続する」については技術や周辺環境の変化が激しいフロントエンドのエンジニアリングを学び続けるために「楽しむ」ことの大切さについてお話しいただきました。</p> <h3>射撃しつつ前進</h3> <p>最後に、プライバシ保護の対応や HTTP/3 のようなネットワークレイヤの変化など、フロントエンドエンジニア的には激動の時代が来ているなかで求められる素養は「変化を楽しみながら、時に苦しみながらも前に進む」ことを理解すること。というまとめの後に、Joel on Software の一節である「射撃しつつ前進」をご紹介いただき、発表は終了しました。</p> <h3>まとめ</h3> <p>最新の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>や考え方、セキュリティやプライバシなどの周辺環境についても気を配りつつ、UX を向上させることが求められるフロントエンドエンジニアのキャリアは覚悟も求められる職種ではありますが、楽しみ・苦しみながらも前に進めるよう、技術顧問の古川さんにサポートいただきながら進んでいきたいと思います。</p> ariarijp 【Insight BRAIN】フロントエンドのテスト実施状況 hatenablog://entry/26006613787046347 2021-07-21T10:17:23+09:00 2021-07-21T10:18:38+09:00 はじめに 皆さんはじめまして、 オープンエイト・プロダクト開発部の村田です。時が経つのは早いもので、知らない間に入社2年目となっていました。 それはさておき、今回はオープンエイト が提供するSNSの分析ツール「Insight BRAIN」において実施されている、フロントエンドのテストについて記事を書かせていただきました。プロダクトの品質を維持するために、フロントエンドでどのような取り組みを行ってきたのか、本記事では余すところなく解説させていただきます。 Insight BRAINとは? オープンエイト が提供するAIで動画を作成できるツールVideo BRAINに対して、Insight BRA… <h3>はじめに</h3> <p>皆さんはじめまして、 オープンエイト・プロダクト開発部の村田です。時が経つのは早いもので、知らない間に入社2年目となっていました。</p> <p>それはさておき、今回はオープンエイト が提供する<a class="keyword" href="http://d.hatena.ne.jp/keyword/SNS">SNS</a>の分析ツール「Insight BRAIN」において実施されている、フロントエンドのテストについて記事を書かせていただきました。プロダクトの品質を維持するために、フロントエンドでどのような取り組みを行ってきたのか、本記事では余すところなく解説させていただきます。</p> <h3>Insight BRAINとは?</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kaimaru31/20210715/20210715151432.png" alt="f:id:kaimaru31:20210715151432p:plain" width="1200" height="476" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>オープンエイト が提供するAIで動画を作成できるツールVideo BRAINに対して、Insight BRAINは企画と分析領域をサポートし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/SNS">SNS</a>等に投稿した動画に対しての効果測定、また更なる企画立案を可能にしてくれるものです。</p> <p>Insight BRAINではバックエンドは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>、フロントエンドはTypeScriptとReactを採用し、マイクロサービスとしてそれぞれで開発を行っています。</p> <h3>Insight BRAINで行われているテスト内容</h3> <p>Insight BRAINのフロントエンドで現状行われているテストとして、主に以下のような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>単位での<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C3%B1%C2%CE%A5%C6%A5%B9%A5%C8">単体テスト</a>を行っています。</p> <p>・<b>スナップショットテスト</b></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>に特定のpropsを渡した時に生成されるDOMを作成しておいたsnapshotデータと比較するテスト。 これにより、周辺のコード変更があっても<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>のUIが変化していないことを担保できる。</p> <p>・<b>イベント発火テスト</b></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の動作をシミュレートすることでイベントやその関数が呼ばれていることを確認するテスト。 これにより、周辺のコード変更があっても該当箇所に関して正しくイベント発火することを担保できる。</p> <p>・<b>関数の挙動テスト</b></p> <p>作成した関数に対して、テストデータを与えて正しい出力となるか確認するテスト。</p> <p>これらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C3%B1%C2%CE%A5%C6%A5%B9%A5%C8">単体テスト</a>を行うため、Insight BRAINではJestとEnzymeを用いて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ごとにテストを行っています。</p> <p><b>Jest</b></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a>が提供している<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>のテスティング<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a></p> <p><b>Enzyme</b></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Airbnb">Airbnb</a>が提供しているReactのテスティングライブラリ</p> <p>テストファイルは以下のようにsrc配下の<code>__test__</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに振り分けられ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>ごとのテストが容易になっています。</p> <pre class="code" data-lang="" data-unlink>src/ ├ components/ | └ componentFiles.../ componentName.tsx ├ pages/ | └ componentFiles.../ componentName.tsx └ __test__/ └ components/ componentFiles.../ componentName.test.tsx |_ pages/ componentFiles.../ componentName.test.tsx </pre> <h3>具体的なテストコードの内容</h3> <p>では、具体的にはどのようなテストコードを書いているのか説明します。</p> <p>例として、投稿内容を表示するPostModalという<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>について書かれたスナップショットテスト及び、イベントの発火テスト、また関数の挙動のテストについて解説します。</p> <p><b>①通常(データが正しくある場合)のスナップショットテスト</b></p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> React <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synComment">// enzymeに関するインポート</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> shallow <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;enzyme&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> EnzymeToJson <span class="synStatement">from</span> <span class="synConstant">&quot;enzyme-to-json&quot;</span><span class="synStatement">;</span> <span class="synComment">// Component自体のインポート</span> <span class="synStatement">import</span> PostModal <span class="synStatement">from</span> <span class="synConstant">&quot;src/Components/Elements/PostModal/&quot;</span><span class="synStatement">;</span> <span class="synComment">// APIで取得されるはずのモックデータのインポート</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> account <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;src/__tests__/mock/sns_accounts/{sns_account_id}&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> post <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;src/__tests__/mock/{sns_account_id}/posts/{post_id}&quot;</span><span class="synStatement">;</span> <span class="synComment">// ①通常(データが正しくある場合)のスナップショットテスト</span> test<span class="synStatement">(</span><span class="synConstant">&quot;&lt;PostModal /&gt;+データがある場合のスナップショット&quot;</span><span class="synStatement">,</span> <span class="synStatement">()</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synComment">// shallow renderingとして子コンポーネントの振る舞いに関わらずテストする。</span> <span class="synStatement">const</span> subject <span class="synStatement">=</span> shallow<span class="synStatement">(</span> <span class="synStatement">&lt;</span>PostModal modalVisible<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">true</span><span class="synIdentifier">}</span> snsAccount<span class="synStatement">=</span><span class="synIdentifier">{</span>account<span class="synIdentifier">}</span> postContentData<span class="synStatement">=</span><span class="synIdentifier">{</span>post<span class="synIdentifier">}</span> <span class="synComment">// jest.fn()でモックの関数を発火できる。</span> handleModalVisible<span class="synStatement">=</span><span class="synIdentifier">{</span>jest.fn<span class="synStatement">()</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">);</span> expect<span class="synStatement">(</span>EnzymeToJson<span class="synStatement">(</span>subject<span class="synStatement">))</span>.toMatchSnapshot<span class="synStatement">();</span> <span class="synIdentifier">}</span><span class="synStatement">);</span> </pre> <p>ここでやっていることは、PostModal<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>をshallow renderした結果をsubjectに代入し、 EnzymeToJsonでJestのテスティング関数であるexpectの引数に合うような形に変換。その結果をtoMatchSnapshot()で、既存のスナップショットテストと相違があるかどうか確認しています。 このテストを実行すると、snapshotsファイルが生成され、テスト実行時点でのDOMがアウトプットされるとともに、以前取得したsnapshotの結果と相違がある場合、エラーとして通知されます。</p> <p><b>②データがnullの場合のスナップショットテスト</b></p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// import 省略</span> <span class="synComment">// ②データがnullの場合のスナップショットテスト</span> test<span class="synStatement">(</span><span class="synConstant">&quot;&lt;PostModal /&gt;+データがnullでのスナップショット&quot;</span><span class="synStatement">,</span> <span class="synStatement">()</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synStatement">const</span> subject <span class="synStatement">=</span> shallow<span class="synStatement">(</span> <span class="synStatement">&lt;</span>PostModal modalVisible<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">true</span><span class="synIdentifier">}</span> snsAccount<span class="synStatement">=</span><span class="synIdentifier">{</span>account<span class="synIdentifier">}</span> postContentData<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synType">null</span><span class="synIdentifier">}</span> handleModalVisible<span class="synStatement">=</span><span class="synIdentifier">{</span>jest.fn<span class="synStatement">()</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">);</span> expect<span class="synStatement">(</span>EnzymeToJson<span class="synStatement">(</span>subject<span class="synStatement">))</span>.toMatchSnapshot<span class="synStatement">();</span> <span class="synIdentifier">}</span><span class="synStatement">);</span> </pre> <p>このテストも①と同じスナップショットテストですが、何らかの理由でデータがnullになっていることを想定したテストです。このようにして、テストの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A5%D0%A5%EC%A5%C3%A5%B8">カバレッジ</a>率を高めようとしています。</p> <p><b>③イベントの発火テスト </b></p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// import 省略</span> <span class="synComment">// ③イベントの発火テスト</span> test<span class="synStatement">(</span><span class="synConstant">&quot;&lt;PostModal /&gt;+handleModalVisibleの発火テスト&quot;</span><span class="synStatement">,</span> <span class="synStatement">()</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synStatement">const</span> testHandleModalVisible <span class="synStatement">=</span> jest.fn<span class="synStatement">();</span> <span class="synStatement">const</span> subject <span class="synStatement">=</span> shallow<span class="synStatement">(</span> <span class="synStatement">&lt;</span>PostModal modalVisible<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">true</span><span class="synIdentifier">}</span> snsAccount<span class="synStatement">=</span><span class="synIdentifier">{</span>account<span class="synIdentifier">}</span> postContentData<span class="synStatement">=</span><span class="synIdentifier">{</span>post<span class="synIdentifier">}</span> handleModalVisible<span class="synStatement">=</span><span class="synIdentifier">{</span>testHandleModalVisible<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">);</span> subject .dive<span class="synStatement">()</span> .find<span class="synStatement">(</span><span class="synConstant">&quot;ModalImage&quot;</span><span class="synStatement">)</span> .simulate<span class="synStatement">(</span><span class="synConstant">&quot;click&quot;</span><span class="synStatement">);</span> expect<span class="synStatement">(</span>testHandleModalVisible<span class="synStatement">)</span>.toHaveBeenCalled<span class="synStatement">();</span> <span class="synIdentifier">}</span><span class="synStatement">);</span> </pre> <p>続いてイベントの発火テストです。subjectという変数に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>した結果を入れているのは①、②と同じですが、ModalImageという<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>が持つonClickの関数を発火させたかったため、その階層までdiveという機能を使ってDOMの階層を移動しています。その上でsimulate("click")で関数を発火させ、expectに渡したtestHandleModalVisibleが呼ばれているか(toHaveBeenCalled)テストしています。このテストを行い、該当<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>での関数の発火に失敗するとエラーとして通知されます。</p> <p><b>④関数の挙動テスト </b></p> <pre class="code lang-javascript" data-lang="javascript" data-unlink> <span class="synStatement">import</span> <span class="synIdentifier">{</span> omitNumberOfDigits <span class="synIdentifier">}</span> from <span class="synConstant">&quot;src/Modules/OmitNumberOfDigits&quot;</span>; <span class="synComment">// ④関数の挙動テスト</span> test(<span class="synConstant">&quot;Omit number of digits. Return omitted number that is string type.&quot;</span>, () =&gt; <span class="synIdentifier">{</span> expect(omitNumberOfDigits(300)).toEqual(<span class="synConstant">&quot;300&quot;</span>); expect(omitNumberOfDigits(1340)).toEqual(<span class="synConstant">&quot;1.3K&quot;</span>); expect(omitNumberOfDigits(1599)).toEqual(<span class="synConstant">&quot;1.5K&quot;</span>); expect(omitNumberOfDigits(10000)).toEqual(<span class="synConstant">&quot;10.0K&quot;</span>); expect(omitNumberOfDigits(1_470_000)).toEqual(<span class="synConstant">&quot;1.4M&quot;</span>); <span class="synIdentifier">}</span>); </pre> <p>最後は関数に関するJestのテストです。 omitNumberOfDigitsという、引数として与えられた数字の大きさに応じて「K」, 「M」と言った単位を付与する関数について、正しい結果をアウトプットしているかをテストしています。 expectに関数の返り値を与え、toEqualで意図した結果と等しいかを検証しています。</p> <p>社内では今回紹介したような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C3%B1%C2%CE%A5%C6%A5%B9%A5%C8">単体テスト</a>を引き続き行っていくだけでなく、今後複数<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>をまたぐ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%EB%B9%E7%A5%C6%A5%B9%A5%C8">結合テスト</a>や、視覚的に描画結果を確認できるビジュアルテストなども検討し、テストを強化していきたいと考えています。</p> <h3>おわりに</h3> <p>これからもInsight BRAIN、Video BRAINともにますます機能を追加していき、皆様に満足いただけるサービスとなるよう社員一同、精進していきたいと思っています。 開発で得られた知識をブログで還元できるよう、個人としても精進していきます!</p> kaimaru31 REST APIについての学び hatenablog://entry/26006613781003843 2021-06-30T17:01:26+09:00 2021-06-30T17:01:26+09:00 フロントエンドエンジニアの松井です。 サーバーサイドからAPIをもらって、リクエストを投げる事は日々業務でやっていますが、APIそのものを基礎的なものでも手を動かして作りながら、RESTサービス全体がどう動いているのかの理解を深めようとした時の学びを今回記事にしました。 VideoBRAINのサーバーサイドはRubyが採用されてますが、今回自分はJavaScriptで動かせるNode.js + Expressで、DataBaseにはSQLite3を使ってAPIを立てました。 サーバーとは? サーバーもソフトウェアで、ネットワークで繋がったコンピュータ上で、サーバー自身がもっているデータやサービ… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsuihopen8/20210629/20210629202102.jpg" alt="f:id:matsuihopen8:20210629202102j:plain" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> フロントエンドエンジニアの松井です。</p> <p>サーバーサイドから<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>をもらって、リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを投げる事は日々業務でやっていますが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>そのものを基礎的なものでも手を動かして作りながら、RESTサービス全体がどう動いているのかの理解を深めようとした時の学びを今回記事にしました。</p> <p>VideoBRAINのサーバーサイドは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>が採用されてますが、今回自分は<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>で動かせる<code>Node.js + Express</code>で、DataBaseには<code>SQLite3</code>を使って<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を立てました。</p> <h3>サーバーとは?</h3> <p>サーバーもソフトウェアで、ネットワークで繋がったコンピュータ上で、サーバー自身がもっているデータやサービスなどをクライアントのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トに応じて提供する。</p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>とは?</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>と一言で言っても、いろんな場面で使われていて、場面場面で微妙に意味が違いますが、<strong>やり取りの決まりごと</strong>という解釈が個人的に一番腑に落ちます。例えば、<code>https://hoge.com/weather</code> というURLにGETメソッドでリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トして天気情報を取得するというやり取りの決まりごとがあればそれが<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>です。</p> <h3>RESTとは?</h3> <p>RESTは<code>REpresentational State Transfer</code>の略で、Webの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>スタイル(設計思想)。ネットワークシステムの代表的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>スタイルであるクライアント/サーバから派生したもの。</p> <p>RESTの原則は主には以下の4つがあります。</p> <p><strong>1. リソースのアドレス可能性</strong><br> そのアプリケーションが提供する情報やデータを全て「リソース」という考え方で表現する。そのリソースは<a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>によって一意に指し示す事ができると言う事。</p> <p>例えばこんな<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>があったとします。</p> <pre class="code" data-lang="" data-unlink>GET: https://api.XXXXXX.com/v5/users/12345/comments</pre> <p>userID=12345のcommentsデータをGETメソッドで取得できると言う事がこの<a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>から読み取る事ができます。</p> <p><strong>2. ステートレス性</strong><br> ステートレス性はセッション等のクライアントの「アプリケーション状態」をサーバー側が持たないと言う事。<br></p> <p>Video BRAINを例にとると、<br> フロントからHTTPリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを送る時に<code>Key</code>と<code>Token</code>をheaderに<strong>毎回付けて送る</strong>事で<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>との認証を行っている。<br> また、リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト毎に編集している動画のID, シーンのIDなどを全て含めている。</p> <p>という風に、サーバとのやりとりで<strong>必要な情報を毎回全てリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トに載せている</strong>のはステートレスサーバーだからです。</p> <p>ステートレスサーバーの利点はリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを受けてレスポンスを返したら完結なので、サーバー負荷が減らせる。1ユーザーがずっとコネクションを貼っている状態がなくなることで、リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト待ちを減らせるようになる。</p> <p><strong>3. 接続性</strong><br> ある情報に「別の情報へのリンク」を含めることができること。そして、リンクを含めることで「別の情報に接続すること」ができること。</p> <p>Video BRAINを例にとると、<br> レスポンスデータの中にテキスト画像や、素材の<code>URL</code>を含んでいる事があり、それをがこの<code>接続性</code>に該当します。</p> <p><strong>4. 統一インターフェース</strong><br> リソースの取得、作成、更新、削除といった操作は、すべて<code>HTTP</code>で定義されているメソッドを利用すること。よく使うのは、<code>GET(取得)</code>、<code>POST(登録)</code>、<code>PUT(更新)</code>、<code>DELETE(削除)</code>の4種類です。</p> <h2>RESTの設計では<a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>が重要</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>は<code>Uniform Resource Identifier</code>の略で、直訳すると<strong>統一リソース識別子</strong>。<br> つまりリソースを同じルールで一意に識別できるID/名前をつける事です。</p> <p>この<a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>(エンドポイントとも呼ぶ)設計において、リソースを「どう操作するのか」は<strong>HTTPメソッド</strong>によって決めます。</p> <p>つまりは<a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>とHTTPメソッドはそれぞれが名詞と動詞の関係になる様に設計すべきと言われています。</p> <p><strong>何(名詞)</strong>を<strong>どうする(動詞)</strong>で表されていて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>を見ただけで何の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>なのかが分かる、つまり人間が読んで理解できる<a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>になる様に設計する事が大事と言われています。</p> <p>リソースには基本的には<code>名詞の複数形のみ</code>を使う、<code>api</code>という単語を含める、<code>バージョン</code>を含めるなどの<a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>設計の注意点を考慮して、自分はNode.jsでこの様な<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を立てました。</p> <p><code>https://reactexpress-app-matsui.herokuapp.com/api/v1</code></p> <pre class="code" data-lang="" data-unlink>GET:/memos 何を?→ memosを どうする?→ GET(取得)する</pre> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> express = require(<span class="synConstant">'express'</span>); <span class="synStatement">const</span> router = express.Router(); <span class="synStatement">const</span> sqlite3 = require(<span class="synConstant">'sqlite3'</span>); <span class="synStatement">const</span> db = <span class="synStatement">new</span> sqlite3.Database(<span class="synConstant">'memo_data.db'</span>); router.get(<span class="synConstant">'/'</span>, (req, res, next) =&gt; <span class="synIdentifier">{</span> db.serialize(() =&gt; <span class="synIdentifier">{</span> db.all(<span class="synConstant">&quot;select * from memos&quot;</span>, (err, rows) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">if</span> (!err) <span class="synIdentifier">{</span> <span class="synStatement">const</span> data = <span class="synIdentifier">{</span> content: rows <span class="synIdentifier">}</span> res.json(<span class="synIdentifier">{</span> data <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span>); </pre> <pre class="code" data-lang="" data-unlink>POST:/memos 何を?→ memosに どうする?→ POST(追加)する</pre> <pre class="code lang-javascript" data-lang="javascript" data-unlink>router.post(<span class="synConstant">'/'</span>, (req, res, next) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> tx = req.body.text; db.run(<span class="synConstant">'insert into memos (text) values (?)'</span>, tx, (err) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">if</span> (!err) res.send(<span class="synConstant">'OK'</span>) <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span>); </pre> <pre class="code" data-lang="" data-unlink>PUT:/memos/:ID 何を?→ memosの:IDを どうする?→ PUT(更新)する</pre> <pre class="code lang-javascript" data-lang="javascript" data-unlink>router.put(<span class="synConstant">'/:id'</span>, (req, res, next) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> id = req.params.id <span class="synStatement">const</span> tx = req.body.text; <span class="synStatement">const</span> q = <span class="synConstant">&quot;update memos set text = ? where id = ?&quot;</span>; db.run(q, tx, id, (err) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">if</span> (!err) res.send(<span class="synConstant">'OK'</span>) <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span>); </pre> <pre class="code" data-lang="" data-unlink>DELETE:/memos/:ID 何を?→ memosの:IDを どうする?→ DELETE(削除)する</pre> <pre class="code lang-javascript" data-lang="javascript" data-unlink>router.<span class="synStatement">delete</span>(<span class="synConstant">'/:id'</span>, (req, res, next) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> id = req.params.id <span class="synStatement">const</span> q = <span class="synConstant">&quot;delete from memos where id = ?&quot;</span>; db.run(q, id, (err) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">if</span> (!err) res.send(<span class="synConstant">'OK'</span>) <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span>); </pre> <h3>実際に<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を立てるまでに辿ったステップ</h3> <p>さて、この<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を立てるまでに、まず始めにNode.js単体でアプリケーションを作りました(テンプレートエンジンを使ってクライアント画面を生成し、HTMLを返すサーバーサイド<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EB%A5%B9%A5%BF">フルスタ</a>ックアプリ) <br> ↓ <br> 次にDataBase(SQLite3)と連携させて値を永続的に保存出来る様にする <br> ↓ <br> それからサーバー側を<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>化させて<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>を返すだけにし、クライアント側をReactに切り出しました(フロントエンドとバックエンドの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%C2%B7%EB%B9%E7">疎結合</a>化)。それで完成したのがこちらです。 <br> <a href="https://quirky-perlman-c9420b.netlify.app/">https://quirky-perlman-c9420b.netlify.app/</a></p> <p>本当に簡易的なメモアプリですが、構成は以下の様になっています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsuihopen8/20210628/20210628210700.png" alt="f:id:matsuihopen8:20210628210700p:plain" width="1200" height="290" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/HTTP%A5%B9%A5%C6%A1%BC%A5%BF%A5%B9%A5%B3%A1%BC%A5%C9">HTTPステータスコード</a></h3> <p>HTTPにおいてWebサーバからのレスポンスの意味を表現する3桁の数字からなるコード。今回自分が立てた<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>ではExpressが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%C6%A1%BC%A5%BF%A5%B9%A5%B3%A1%BC%A5%C9">ステータスコード</a>を自動設定してくれたのですが、通常はバックエンドエンジニア自身でHTTPステータスの番号を指定するみたいです。</p> <p>Video BRAINでは、データをきちんと返す際の200番は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>が自動で設定するものを使い、データを返さない時やエラーを返す時は自分たちでHTTPステータスの番号を指定して返しているそうです。</p> <p><strong>1xx</strong>:Informational 情報 <br> <strong>2xx</strong>:Success、すべて成功 <br> <strong>3xx</strong>:リダイレクト <br> <strong>4xx</strong>:クライアント側のエラー <br> <strong>5xx</strong>:サーバー側のエラー <br></p> <hr /> <p>さまざまなWebアプリケーションの開発に活用されている<a class="keyword" href="http://d.hatena.ne.jp/keyword/REST%20API">REST API</a>ですが、問題点もあります。</p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/REST%20API">REST API</a>の問題点</h3> <p>・クライアントでレスポンス形式を指定できない為、不要なデータもフェッチしてしまう<br> ・複数リソースが必要な複雑な画面に、複数の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トが必要になる。<br> ・エンドポイントが肥大化して管理が大変。<br> ・フロント、バックエンドでインターフェースの擦り合わせが大変<br></p> <p>これらの問題を解決するために生まれたのが<code>GraphQL</code>です。</p> <h3>GraphQLとは</h3> <p>GraphQLとは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a>が2015年に公開した<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>向けに作られたクエリ言語。<br> RESTは<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>の設計原則なのに対して、GraphQLは言語(クエリ言語、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE%B8%C0%B8%EC">スキーマ言語</a>)や型(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AB%A5%E9%A1%BC">スカラー</a>型等)が標準化された規格になります。<br></p> <p>そもそもクエリ言語とは、コンピュータやデータベースに問い合わせてデータを取得する言語。<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>などがそれに当たる。GraphQLは<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>に問い合わせてデータを取得したり、書き換えたりする。</p> <p><strong>メリット</strong><br> ・GraphQLでは、クライアントが必要なデータの構造を定義することができ、サーバーからは定義したのと同じ構造のデータが返される。したがって、必要以上に大きなデータが返されるのを防ぐことができクエリの効率が良い。<br> ・GraphQLのコードが仕様書的に機能するので、フロント側とのコミュニケーションコストを減らせる。<br> ・エンドポイントが1つで、エンドポイントのURL定義ファイルが増えなくていい。<br></p> <p>当然メリットだけではなく画像や動画などの大容量バイナリの扱いが難しい、データクエリ処理の多くがサーバーサイドに移行されるので、サーバー側の作業が大変などのデメリットもあるみたいです。<a class="keyword" href="http://d.hatena.ne.jp/keyword/REST%20API">REST API</a>からGraphQL <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>に移行するとなれば当然フロント側でも対応が必要になるので容易には行かないと思いました。</p> <h2>まとめ</h2> <p>今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を立てて、フロントからのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを受けて、DataBaseと<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>文でやりとりして、<a class="keyword" href="http://d.hatena.ne.jp/keyword/JSON">JSON</a>を返すというバックエンド側を含めたRESTサービス全体を経験する事が目的だったので本当にミニマムな実装とデプロイでしたが、今までバラバラだった点の知識がぐっと繋がりました。</p> <p>とはいえ、まだまだサーバーもデータベースも学ぶことは多岐に渡るので、基本を学んだ程度ではありますが、自分の担当範囲の外側の理解が深まりました。</p> <p>木を見て森を見ずの状態で、視野が狭い状態で仕事するのは良くないと思います。 サービス全体の解像度を上げれば連携の精度も上がる。これは別にエンジニアの仕事に限った事ではないので引き続き学んでいきたいと思います。</p> matsuihopen8 Alpakka で AWS SQS 接続 (Scala) hatenablog://entry/26006613697957518 2021-03-04T18:45:38+09:00 2021-03-04T18:45:38+09:00 はじめに Alpakka Alpakka とは セットアップ AWS SQS AWS SQS とは ElasticMQ のセットアップ Alpakka で AWS SQS 接続 Subscribe Publish さいごに はじめに こんにちは、オープンエイトの山崎です。 今回は、Akka Streams で Alpakka を使って AWS SQS と接続しデータを送受信する方法について説明します。また本記事内では AWS SQS をローカル環境でシミュレートするために ElasticMQ を使用します。ElasticMQ のセットアップについてもあわせて簡単に説明します。記事執筆時の環境は… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/o8yamazakiy/20210302/20210302152657.png" alt="f:id:o8yamazakiy:20210302152657p:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#Alpakka">Alpakka</a><ul> <li><a href="#Alpakka-とは">Alpakka とは</a></li> <li><a href="#セットアップ">セットアップ</a></li> </ul> </li> <li><a href="#AWS-SQS">AWS SQS</a><ul> <li><a href="#AWS-SQS-とは">AWS SQS とは</a></li> <li><a href="#ElasticMQ-のセットアップ">ElasticMQ のセットアップ</a></li> </ul> </li> <li><a href="#Alpakka-で-AWS-SQS-接続">Alpakka で AWS SQS 接続</a><ul> <li><a href="#Subscribe">Subscribe</a></li> <li><a href="#Publish">Publish</a></li> </ul> </li> <li><a href="#さいごに">さいごに</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>こんにちは、オープンエイトの山崎です。</p> <p>今回は、Akka Streams で Alpakka を使って <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> SQS と接続しデータを送受信する方法について説明します。また本記事内では <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> SQS をローカル環境でシミュレートするために ElasticMQ を使用します。ElasticMQ のセットアップについてもあわせて簡単に説明します。記事執筆時の環境は以下の通りです。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a> 2.13.5</li> <li>sbt 1.4.7</li> <li>Akka 2.6.13</li> <li>Alpakka SQS 2.0.2</li> <li>ElasticMQ 1.1.0</li> </ul> <p>オープンエイトでは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/SNS">SNS</a> 配信効果測定サービス Insight BRAIN において内部のジョブ管理に SQS を利用していますが、その接続処理において実際に Akka Streams と Alpakka を使用しています。</p> <h1 id="Alpakka">Alpakka</h1> <h2 id="Alpakka-とは">Alpakka とは</h2> <p>Akka Streams で様々なリソースと接続するための<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>群を提供するプロジェクトが Alpakka です。Akka の公式プロジェクトとして開発・提供されています。たとえばローカルのテキストファイルをストリーム処理する Source や、処理結果を S3 に保存する Flow など、様々な入力ソースや出力先を Akka Streams の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>として汎用的に使えるかたちにしてくれています。</p> <blockquote><p>Alpakka Documentation<br/> <a href="https://doc.akka.io/docs/alpakka/current/">https://doc.akka.io/docs/alpakka/current/</a></p></blockquote> <p>外部サービスとの連携処理が簡単に構築できてしまうのでうまく Alpakka を活用できれば開発効率の向上が見込めるでしょう。</p> <p>今回はその中から <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> SQS 用のモジュールを使用します。</p> <blockquote><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> SQS • Alpakka Documentation<br/> <a href="https://doc.akka.io/docs/alpakka/current/sqs.html">https://doc.akka.io/docs/alpakka/current/sqs.html</a></p></blockquote> <p>ちなみに「Alpakka」という名前は動物のアルパカから取っているものと思われます。アルパカは英語の綴りだと「Alpaca」ですが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%A3%A5%F3%A5%E9%A5%F3%A5%C9">フィンランド</a>や<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CE%A5%EB%A5%A6%A5%A7%A1%BC">ノルウェー</a>では「Alpakka」と表記するようです。「Akka」という単語が含まれているためこの名前が採用されたのかなと思うのですが、ドキュメントでもそのあたりは特に説明されてなさそうなので正確なところはわかりませんね…。</p> <h2 id="セットアップ">セットアップ</h2> <p>Alpakka は接続対象ごとにモジュールが分かれているので必要なものだけを選んで導入します。たとえば今回は SQS モジュールが必要なので <code>libraryDependencies</code> を以下のように記述します。</p> <pre class="code lang-scala" data-lang="scala" data-unlink>libraryDependencies ++= Seq( <span class="synConstant">&quot;com.typesafe.akka&quot;</span> %% <span class="synConstant">&quot;akka-stream&quot;</span> % <span class="synConstant">&quot;2.6.13&quot;</span>, <span class="synConstant">&quot;com.lightbend.akka&quot;</span> %% <span class="synConstant">&quot;akka-stream-alpakka-sqs&quot;</span> % <span class="synConstant">&quot;2.0.2&quot;</span> ) </pre> <p>実際のコードについては後述します。</p> <h1 id="AWS-SQS"><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> SQS</h1> <h2 id="AWS-SQS-とは"><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> SQS とは</h2> <blockquote><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a> SQS(サーバーレスアプリのためのメッセージキューサービス)| <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a><br/> <a href="https://aws.amazon.com/jp/sqs/">https://aws.amazon.com/jp/sqs/</a></p></blockquote> <p>SQS (Simple Queue Service) は <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> が提供するマネージド型のメッセージキューサービスです。<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> では MQ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Kinesis">Kinesis</a>、MSK など複数のメッセージキュー系サービスが提供されていますが、名前の通り最もシンプルな機能を提供しているのが SQS です。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%D7%A5%E9%A5%A4%A5%A8%A5%BF%A5%EA">プロプライエタリ</a>なサービスであり <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> にロックインされる点はネックですが、低コストで利用できる上に単純な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>なら大体 SQS でカバーできるのではと思います。</p> <h2 id="ElasticMQ-のセットアップ">ElasticMQ のセットアップ</h2> <p>SQS は <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> のサービスなので、開発用にローカル環境で動作させるといったことができないのですが、SQS 互換 <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> を提供する ElasticMQ というアプリケーションを利用することで擬似的にローカル環境で SQS を動作させることができます。</p> <blockquote><p>softwaremill/elasticmq: In-memory message queue with an <a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a> SQS-compatible interface. Runs stand-alone or embedded.<br/> <a href="https://github.com/softwaremill/elasticmq">https://github.com/softwaremill/elasticmq</a></p></blockquote> <p>キューのデータはインメモリで扱われるため永続化されず ElasticMQ サービスを終了すると消えてしまいます。なのであくまでも開発用のツールという位置づけで捉えておくのがよさそうです。</p> <p>ElasticMQ は Alpakka SQS のテストでも利用されているようです。我々も開発環境で利用していますが、これまでのところ実際の SQS と挙動が異なって困ったというようなことは起きていません。</p> <p>ちなみに ElasticMQ 自体は <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a> 製です。ただし Docker イメージでも提供されているため特に <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a> を意識せずに利用できます。Jar 単体で実行したりライブラリとしてアプリケーションに組み込んだり様々な利用方法が提供されていますが、今回は Docker イメージを利用することにします。</p> <p><code>docker-compose.yml</code> を作成し以下のように記述します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">version</span><span class="synSpecial">:</span> <span class="synConstant">'3'</span> <span class="synIdentifier">services</span><span class="synSpecial">:</span> <span class="synIdentifier">elasticmq</span><span class="synSpecial">:</span> <span class="synIdentifier">image</span><span class="synSpecial">:</span> softwaremill/elasticmq <span class="synIdentifier">container_name</span><span class="synSpecial">:</span> elasticmq_test <span class="synIdentifier">ports</span><span class="synSpecial">:</span> <span class="synStatement">- </span>9324:9324 <span class="synIdentifier">volumes</span><span class="synSpecial">:</span> <span class="synStatement">- </span>./elasticmq.conf:/opt/elasticmq.conf </pre> <p>設定ファイル <code>elasticmq.conf</code> を以下のように作成します。今回は <code>test-queue.fifo</code> という名前で <a class="keyword" href="http://d.hatena.ne.jp/keyword/FIFO">FIFO</a> キューを作成します。</p> <pre class="code" data-lang="" data-unlink>include classpath(&#34;application.conf&#34;) aws { accountId = queue } queues { &#34;test-queue.fifo&#34; { defaultVisibilityTimeout = 10 seconds fifo = true contentBasedDeduplication = true } }</pre> <p>上記ファイルが用意できたら <code>docker-compose</code> コマンドでコンテナを起動します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>docker-compose up <span class="synSpecial">-d</span> </pre> <p>コンテナが起動すると <code>test-queue.fifo</code> というキューが自動的に作成され <code>localhost:9324</code> で接続可能な状態になります。そのエンドポイントを指定すれば <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> などを使って SQS として扱うことが可能です。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># AWS CLI でキューをリストしてみる</span> aws sqs list-queues <span class="synSpecial">--endpoint-url</span> <span class="synStatement">'</span><span class="synConstant">http://localhost:9324</span><span class="synStatement">'</span> </pre> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">QueueUrls</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">http://localhost:9324/queue/test-queue.fifo</span>&quot; <span class="synSpecial">]</span> <span class="synSpecial">}</span> </pre> <p>ちなみに、おそらくバグだと思うのですが、現行バージョンだと接続 URL の <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> アカウントの部分は <code>queue</code> という文字列でないとうまく動作しないようです。たとえば <code>elasticmq.conf</code> で <code>aws.accountId = aws01</code> のように記述すれば <code>http://localhost:9324/aws01/test-queue.fifo</code> で接続できそうに思えるのですが手元の環境では正しく動作しませんでした。</p> <h1 id="Alpakka-で-AWS-SQS-接続">Alpakka で <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> SQS 接続</h1> <p>準備が整ったので実際に Alpakka で SQS (ElasticMQ) に接続してみます。</p> <p>まず最初に <code>SqsAsyncClient</code> を生成して SQS 接続情報を設定します。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> java.net.URI <span class="synPreProc">import</span> akka.actor.ActorSystem <span class="synPreProc">import</span> akka.stream.alpakka.sqs.SqsSourceSettings <span class="synPreProc">import</span> com.github.matsluni.akkahttpspi.AkkaHttpClient <span class="synPreProc">import</span> software.amazon.awssdk.auth.credentials.AwsBasicCredentials <span class="synPreProc">import</span> software.amazon.awssdk.auth.credentials.StaticCredentialsProvider <span class="synPreProc">import</span> software.amazon.awssdk.regions.Region <span class="synPreProc">import</span> software.amazon.awssdk.services.sqs.SqsAsyncClient <span class="synType">val</span> endpoint = <span class="synConstant">&quot;http://localhost:9324&quot;</span> implicit <span class="synType">val</span> actorSystem = ActorSystem(<span class="synConstant">&quot;example&quot;</span>) implicit <span class="synType">val</span> sqsClient = SqsAsyncClient .builder() .credentialsProvider( StaticCredentialsProvider.create( AwsBasicCredentials.create(<span class="synConstant">&quot;AK&quot;</span>, <span class="synConstant">&quot;SK&quot;</span>) <span class="synComment">// (1)</span> ) ) .endpointOverride(URI.create(endpoint)) <span class="synComment">// (2)</span> .region(Region.AP_NORTHEAST_1) .httpClient(AkkaHttpClient.builder() .withActorSystem(actorSystem).build()) .build() </pre> <p>(1) 今回は本物の SQS ではなくローカルの ElasticMQ への接続なのでアクセスキーとシークレットキーは適当な値を記述しておきます。</p> <p>(2) <code>endpointOverride</code> でエンドポイントを指定することで接続先を <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ではなくローカルに向けることができます。</p> <h2 id="Subscribe">Subscribe</h2> <p>SQS からメッセージをストリーム取得するには <code>SqsSource</code> を使用します。このとき先程生成した <code>SqsAsyncClient</code> が <code>implicit</code> で参照されます。この <code>SqsSource</code> はその名の通り Akka Streams の Source なので Flow や Sink とつないでデータフローを構築できます。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synType">val</span> queueUrl = endpoint + <span class="synConstant">&quot;/queue/test-queue.fifo&quot;</span> <span class="synType">val</span> settings = SqsSourceSettings() SqsSource(queueUrl, settings) .runWith(Sink.foreach { message: Message =&gt; <span class="synType">val</span> body = message.body println(s<span class="synConstant">&quot;received: ${body}&quot;</span>) }) </pre> <p>設定等に問題がなければこれで SQS と接続された状態になります。試しにこのアプリケーションを実行した状態で <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> を使ってメッセージを送信してみます。グループ ID が必要になるのですがとりあえず適当な値で大丈夫です。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># AWS CLI で &quot;hello&quot; というメッセージを送信してみる</span> aws sqs send-message <span class="synStatement">\</span> <span class="synSpecial">--queue-url</span> <span class="synStatement">&quot;</span><span class="synConstant">http://localhost:9324/queue/test-queue.fifo</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--endpoint-url</span> <span class="synStatement">&quot;</span><span class="synConstant">http://localhost:9324</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--message-group-id</span> <span class="synStatement">&quot;</span><span class="synConstant">x</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--message-body</span> <span class="synStatement">&quot;</span><span class="synConstant">hello</span><span class="synStatement">&quot;</span> </pre> <p>成功すると <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a> アプリケーション側のコンソールに <code>received: hello</code> と表示されるはずです。</p> <p>ちなみにこのままだと SQS 上のメッセージは消えずに残り続けます。なので少し時間が経つとまた同じメッセージを受信してしまいます。一度受信した処理済みのメッセージを削除するには ACK メッセージを送ってメッセージを削除する必要があります。その場合、以下のように <code>SqsAckFlow</code> や <code>SqsAckSink</code> を使用します。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> akka.stream.alpakka.sqs.MessageAction <span class="synPreProc">import</span> akka.stream.alpakka.sqs.SqsAckResult SqsSource(queueUrl, settings) .map(MessageAction.Delete(_)) .via(SqsAckFlow(queueUrl)) .runWith(Sink.foreach { res: SqsAckResult =&gt; <span class="synType">val</span> body = res.messageAction.message.body println(s<span class="synConstant">&quot;received: ${body}&quot;</span>) }) </pre> <p>これで一度受信したメッセージは SQS 上から削除されます。</p> <h2 id="Publish">Publish</h2> <p>SQS にメッセージを送信する場合は <code>SqsPublishSink</code> や <code>SqsPublishFlow</code> を使います。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> akka.stream.alpakka.sqs.scaladsl.SqsPublishSink <span class="synPreProc">import</span> akka.stream.scaladsl.Source <span class="synPreProc">import</span> software.amazon.awssdk.services.sqs.model.SendMessageRequest <span class="synType">val</span> queueUrl = endpoint + <span class="synConstant">&quot;/queue/test-queue.fifo&quot;</span> <span class="synType">val</span> message = SendMessageRequest.builder .messageBody(<span class="synConstant">&quot;hello&quot;</span>) .messageGroupId(<span class="synConstant">&quot;x&quot;</span>) .build Source.single(message) .runWith(SqsPublishSink.messageSink(queueUrl)) </pre> <p>これを実行すると SQS にメッセージが 1 件送信されます。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> で確認することができます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># AWS CLI でキューからメッセージを取得してみる</span> aws sqs receive-message <span class="synStatement">\</span> <span class="synSpecial">--queue-url</span> <span class="synStatement">&quot;</span><span class="synConstant">http://localhost:9324/queue/test-queue.fifo</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synSpecial">--endpoint-url</span> <span class="synStatement">&quot;</span><span class="synConstant">http://localhost:9324</span><span class="synStatement">&quot;</span> </pre> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">Messages</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">MessageId</span>&quot;: &quot;<span class="synConstant">5be5527a-ec4e-4f3f-8ad2-67a713938673</span>&quot;, &quot;<span class="synStatement">ReceiptHandle</span>&quot;: &quot;<span class="synConstant">5be5527a-ec4e-4f3f-8ad2-67a713938673#2406b392-60b7-4019-b3b9-4a62620bdbcc</span>&quot;, &quot;<span class="synStatement">MD5OfBody</span>&quot;: &quot;<span class="synConstant">5d41402abc4b2a76b9719d911017c592</span>&quot;, &quot;<span class="synStatement">Body</span>&quot;: &quot;<span class="synConstant">hello</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> </pre> <h1 id="さいごに">さいごに</h1> <p>Akka Streams はロジックを Source、Flow、Sink といったかたちでモジュール化して扱うことができるためモジュールの組み合わせでデータフローを構築できる点が非常に強力だと感じています。Alpakka は SQS 接続以外にも様々なモジュールを提供しているので、要件がマッチすれば外部サービスとのデータ連携部分は Alpakka で手軽に構築してしまうことができます。その分<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>の開発に集中できるので、うまく活用することで開発効率の向上が図れるのではないでしょうか。</p> <p>ということで、今回は以上となります。非常に駆け足の内容ではありますが本記事が Akka Streams 活用の一助となれば幸いです。最後までお読みいただきありがとうございました。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4798153273/hatena-blog-22/"><img src="https://m.media-amazon.com/images/I/51DNj77500L.jpg" class="hatena-asin-detail-image" alt="Akka実践バイブル アクターモデルによる並行・分散システムの実現" title="Akka実践バイブル アクターモデルによる並行・分散システムの実現"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4798153273/hatena-blog-22/">Akka実践バイブル アクターモデルによる並行・分散システムの実現</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/Raymond%20Roestenburg" class="keyword">Raymond Roestenburg</a>,<a href="http://d.hatena.ne.jp/keyword/Rob%20Bakker" class="keyword">Rob Bakker</a>,<a href="http://d.hatena.ne.jp/keyword/Rob%20Williams" class="keyword">Rob Williams</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2017/12/13</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4844381490/hatena-blog-22/"><img src="https://m.media-amazon.com/images/I/51TxxeeOvOL.jpg" class="hatena-asin-detail-image" alt="Scalaスケーラブルプログラミング第3版" title="Scalaスケーラブルプログラミング第3版"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4844381490/hatena-blog-22/">Scalaスケーラブルプログラミング第3版</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/Martin%20Odersky" class="keyword">Martin Odersky</a>,<a href="http://d.hatena.ne.jp/keyword/Lex%20Spoon" class="keyword">Lex Spoon</a>,<a href="http://d.hatena.ne.jp/keyword/Bill%20Venners" class="keyword">Bill Venners</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2016/09/20</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> o8yamazakiy Akka Typed 移行入門 (Scala) hatenablog://entry/26006613684168898 2021-02-01T18:07:42+09:00 2021-07-06T10:25:18+09:00 はじめに Akka Typed について メッセージの型を宣言できる Immutable なコードで Actor を実装できる Classic Actor から移行する さいごに はじめに こんにちは、オープンエイトの山崎です。 今回は、いわゆる Actor モデル の Scala/Java 実装である Akka に関して、v2.6.0 から導入された Akka Typed の簡単な紹介と既存の Actor (Classic Actor) から Typed Actor への移行方法について Scala 言語を用いて説明します。記事執筆時の環境は以下の通りです。 Scala 2.13.4 Akk… <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#Akka-Typed-について">Akka Typed について</a><ul> <li><a href="#メッセージの型を宣言できる">メッセージの型を宣言できる</a></li> <li><a href="#Immutable-なコードで-Actor-を実装できる">Immutable なコードで Actor を実装できる</a></li> </ul> </li> <li><a href="#Classic-Actor-から移行する">Classic Actor から移行する</a></li> <li><a href="#さいごに">さいごに</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>こんにちは、オープンエイトの山崎です。</p> <p>今回は、いわゆる <a href="https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%AF%E3%82%BF%E3%83%BC%E3%83%A2%E3%83%87%E3%83%AB">Actor &#x30E2;&#x30C7;&#x30EB;</a> の <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/Java">Java</a> 実装である <a href="https://akka.io/">Akka</a> に関して、v2.6.0 から導入された Akka Typed の簡単な紹介と既存の Actor (Classic Actor) から Typed Actor への移行方法について <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a> 言語を用いて説明します。記事執筆時の環境は以下の通りです。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a> 2.13.4</li> <li>Akka 2.6.11</li> </ul> <p>オープンエイトでは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/SNS">SNS</a> 配信効果測定サービス Insight BRAIN において Akka Actor、Akka Http、Akka Streams と全面的に Akka を採用してシステムを構築していますが、その中で昨年部分的に Akka Typed を導入しました。</p> <p>Akka Typed は既存の Classic Actor の課題を解決しコードをよりシンプルかつ堅牢なものにしてくれるなど、導入することで得られるメリットは決して小さくないのですが、書籍「<a href="https://www.amazon.co.jp/exec/obidos/ASIN/4798153273/hatena-blog-22/">Akka 実践バイブル (Akka in Action)</a>」でもほんの少ししか触れられていないなどまだ日本語の情報が十分ではない状況です。</p> <p>簡単な内容ではありますが、本記事が Akka Typed 導入の参考になれば幸いです。</p> <h1 id="Akka-Typed-について">Akka Typed について</h1> <p>Akka Typed は Akka 2.6.0 から導入された新しい Actor の <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> です。それまでの Actor <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> は Classic Actor と呼ばれ区別されています。Akka の公式ドキュメントではすでに Akka Typed での説明がメインになっています。</p> <dl> <dt><b>Akka Documentation</b></dt> <dd><a href="https://doc.akka.io/docs/akka/current/">https://doc.akka.io/docs/akka/current/</a></dd> </dl> <p>Akka Typed を導入するメリットとしては以下のような点が挙げられます。</p> <ul> <li>メッセージの型を宣言できる</li> <li>Immutable なコードで Actor を実装できる</li> </ul> <h2 id="メッセージの型を宣言できる">メッセージの型を宣言できる</h2> <p>Typed Actor では Actor が受信するメッセージの型を明示できます。</p> <p>下記サンプルコードでは <code>Message</code> 型のメッセージを受信する Actor を定義しています。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> akka.actor.typed.scaladsl.{ Behaviors } <span class="synPreProc">import</span> akka.actor.typed.{ ActorRef, ActorSystem, Behavior } <span class="synType">trait</span> Message <span class="synType">case object</span> Print <span class="synType">extends</span> Message <span class="synType">case</span> <span class="synType">class</span> Update(numValue: Int) <span class="synType">extends</span> Message <span class="synType">object</span> MyTypedActor { <span class="synIdentifier"> def</span> apply(numValue: Int): Behavior[Message] = Behaviors.receive[Message] { (ctx, msg) =&gt; msg match { <span class="synType">case</span> Print =&gt; println(numValue) Behaviors.same <span class="synType">case</span> Update(numValue) =&gt; MyTypedActor(numValue) } } } </pre> <p>Actor の記述方法が Classic Actor とはだいぶ違っているので最初は戸惑いますが、やっていることは <code>Message</code> を <code>receive</code> して何らかの処理をするだけなのでその点においては Classic Actor と変わりません。</p> <p>Classic Actor で少し残念な部分の一つはメッセージの型を宣言できないことではないかと思います。パターンマッチでフィルタリングすればいいのですが、実行時に動的に型判定することになります。せっかく <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a> で書いているのだから Actor も Type Safe に静的に書きたくなるものです。Typed Actor はその名の通りまさにその課題を解決してくれます。</p> <p>メッセージを送信するクライアントコードは以下のようになります。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synType">val</span> system: ActorSystem[Message] = ActorSystem(MyTypedActor(<span class="synConstant">123</span>), <span class="synConstant">&quot;my-actor&quot;</span>) system ! Print system ! Update(<span class="synConstant">456</span>) system ! Print system ! Update(<span class="synConstant">789</span>) system ! Print system ! <span class="synConstant">&quot;hello&quot;</span> <span class="synComment">// コンパイルエラー</span> </pre> <p>異なる型のデータを送信しようとすると<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>エラーになります。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>時に型チェックができるのでコーディングミスによる余計なランタイムエラーの削減につながります。</p> <p>Actor 側でも、受け取る型が決まっているので <code>case _ =&gt;</code> のようなフォールバックコードが不要になります。また、網羅性のチェックも <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a> のパターンマッチの仕組みを利用して<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>時に行うことができます。そのように全体的にコードがすっきりして見通しがよくなりかつ安全性も高まります。</p> <h2 id="Immutable-なコードで-Actor-を実装できる">Immutable なコードで Actor を実装できる</h2> <p>Actor モデルはその概念上ステートフルであるため、Actor は基本的に Mutable な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>になります。そのため Actor に何かしら状態を管理させたい場合は Mutable なプロパティを持たせる必要があります。これはそういうものなので、まあ、そういうものなのですという感じなのですが、Akka Typed では関数型的な仕組みを提供していて、関数の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>処理で Actor を記述できるようになっています。</p> <p>先程のサンプルコードを再掲します。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> akka.actor.typed.scaladsl.{ Behaviors } <span class="synPreProc">import</span> akka.actor.typed.{ ActorRef, ActorSystem, Behavior } <span class="synType">trait</span> Message <span class="synType">case object</span> Print <span class="synType">extends</span> Message <span class="synType">case</span> <span class="synType">class</span> Update(numValue: Int) <span class="synType">extends</span> Message <span class="synType">object</span> MyTypedActor { <span class="synIdentifier"> def</span> apply(numValue: Int): Behavior[Message] = <span class="synComment">// (1)</span> Behaviors.receive[Message] { (ctx, msg) =&gt; <span class="synComment">// (2)</span> msg match { <span class="synType">case</span> Print =&gt; <span class="synComment">// (3)</span> println(numValue) Behaviors.same <span class="synType">case</span> Update(newNumValue) =&gt; <span class="synComment">// (4)</span> MyTypedActor(newNumValue) } } } </pre> <p>(1) <code>MyTypedActor</code> は <code>numValue</code> という <code>Int</code> のプロパティを持つ Actor です。しかしコード内には <code>numValue</code> を保持するための<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>変数が存在していません。</p> <p>(2) <code>apply()</code> 関数の中で呼び出されている <code>Behaviors.receive</code> が Actor を生成する関数です。<code>receive</code> の引数には Actor の処理を関数として渡しています。関数が受け取る引数は <code>ActorContext</code> と <code>Message</code> になります。<code>ActorContext</code> は <code>ActorSystem</code> を参照したりロガーを取得したり子 Actor を生成・参照したりなど各種 Actor に関する操作を行うために使用できます。</p> <p>(3) <code>Message</code> が <code>Print</code> だった場合は、最後に <code>Behaviors.same</code> を返しています。これは Actor の状態を変更せずそのままその Actor を再利用するということを意味します。</p> <p>(4) 一方で <code>Message</code> が <code>Update</code> だった場合は <code>Behaviors.same</code> ではなく <code>MyTypedActor</code> を新しい値で新たに生成して返しています。こうすると Actor はその新しいものに差し替えられます。</p> <p>Actor 本体は関数として定義しているためプロパティを持たせることはできません。その代わりに新しいパラメータで関数を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>的に呼び出すことで Actor の状態を更新していくことができるという仕組みになっているのです。このように Actor としては従来通りステートフルでありながらコードの表現上は Immutable に記述することができます。</p> <h1 id="Classic-Actor-から移行する">Classic Actor から移行する</h1> <p>ありがたいことに Typed Actor は Classic Actor と共存できるように作られています。そのため Typed Actor を部分的に導入したり段階的に移行したりといったことが可能です。</p> <p>Classic な ActorSystem から Typed Actor を生成する方法は極めて簡単です。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> akka.{ actor =&gt; classic } <span class="synPreProc">import</span> akka.actor.typed.scaladsl.adapter._ <span class="synType">val</span> system = classic.ActorSystem(<span class="synConstant">&quot;classic-system&quot;</span>) <span class="synType">val</span> actor: ActorRef[Message] = system.spawn(MyTypedActor(<span class="synConstant">123</span>), <span class="synConstant">&quot;my-actor&quot;</span>) actor ! Print </pre> <p><code>akka.actor.typed.scaladsl.adapter._</code> というアダプターを import すると <code>ActorSystem</code> から <code>spawn</code> メソッドで Typed Actor が生成できるようになります。生成した Actor はそのまま普通に Typed Actor として使用できます。</p> <p>これにより、既存の Classic Actor ベースのコードにほとんど手を加えることなく、新規に追加するコードは Typed Actor で記述するといったことが可能になります。Classic Actor と Typed Actor では記述方法がだいぶ異なってくるため、一気に移行するよりもそのようにまずは新規追加のコードから導入して少しずつ移行していくのがよいのではと思います。</p> <p>逆に Typed Actor の環境から Classic Actor を生成して扱うこともできます。このあたりの相互運用については以下の公式ドキュメントで具体的に説明されています。</p> <dl> <dt><b>Coexistence • Akka Documentation</b></dt> <dd><a href="https://doc.akka.io/docs/akka/current/typed/coexisting.html">https://doc.akka.io/docs/akka/current/typed/coexisting.html</a></dd> </dl> <h1 id="さいごに">さいごに</h1> <p>非常に簡単ではありますが Akka Typed への移行について紹介させていただきました。Akka Typed を導入することで、Akka が提供する先進的な非同期分散処理と <a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a> の Type Safe で Immutable なコードと組み合わせて、アプリケーションの機能をより堅牢に簡潔に実現することができます。</p> <p>Classic Actor とは違う点がいくつかあるため注意が必要な部分もありますが、公式にも Typed Actor を推進していく流れのようなので、我々としても引き続き移行を進めていきたいと考えています。</p> <p>以上となります。最後までお読みいただきありがとうございました。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4798153273/hatena-blog-22/" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/51DNj77500L._SL500_.jpg" class="hatena-asin-detail-image" alt="Akka実践バイブル アクターモデルによる並行・分散システムの実現" title="Akka実践バイブル アクターモデルによる並行・分散システムの実現"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4798153273/hatena-blog-22/" target="_blank" rel="noopener">Akka実践バイブル アクターモデルによる並行・分散システムの実現</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/Raymond%20Roestenburg" class="keyword">Raymond Roestenburg</a>,<a href="http://d.hatena.ne.jp/keyword/Rob%20Bakker" class="keyword">Rob Bakker</a>,<a href="http://d.hatena.ne.jp/keyword/Rob%20Williams" class="keyword">Rob Williams</a></li><li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%E6%C6%B1%CB%BC%D2">翔泳社</a></li></ul><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4798153273/hatena-blog-22/" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4844381490/hatena-blog-22/" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/51TxxeeOvOL._SL500_.jpg" class="hatena-asin-detail-image" alt="Scalaスケーラブルプログラミング第3版" title="Scalaスケーラブルプログラミング第3版"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4844381490/hatena-blog-22/" target="_blank" rel="noopener">Scalaスケーラブルプログラミング第3版</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/Martin%20Odersky" class="keyword">Martin Odersky</a>,<a href="http://d.hatena.ne.jp/keyword/Lex%20Spoon" class="keyword">Lex Spoon</a>,<a href="http://d.hatena.ne.jp/keyword/Bill%20Venners" class="keyword">Bill Venners</a></li><li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%D7%A5%EC%A5%B9">インプレス</a></li></ul><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4844381490/hatena-blog-22/" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> o8yamazakiy thisとアロー関数の関係 hatenablog://entry/26006613669887264 2021-01-05T19:38:18+09:00 2021-01-06T14:11:29+09:00 Video BRAINのフロントエンドを担当している松井です。もうすっかり寒くなりました、在宅勤務になってから初めての冬ですが、朝晩の寒い中、外に出て電車に乗らなくていいのはすごい楽ですね。 さて今回の話は、フロントエンド開発をしていて、Componentにコールバック関数を渡すときにアロー関数とFunction関数を使い分ける理由が何となくフワーっとした理解のままで気持ち悪かったので、はっきりさせようと色々調べたり、教わった事をまとめてみました。 まずこの2つのClassを見てください。 どちらもClick Meというボタンを描画するだけのシンプルなClassで、handleClickという… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsuihopen8/20201225/20201225202937.png" alt="f:id:matsuihopen8:20201225202937p:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Video BRAINのフロントエンドを担当している松井です。もうすっかり寒くなりました、在宅勤務になってから初めての冬ですが、朝晩の寒い中、外に出て電車に乗らなくていいのはすごい楽ですね。</p> <p>さて今回の話は、フロントエンド開発をしていて、Componentにコールバック関数を渡すときに<code>アロー関数とFunction関数を使い分ける理由</code>が何となくフワーっとした理解のままで気持ち悪かったので、はっきりさせようと色々調べたり、教わった事をまとめてみました。</p> <p>まずこの2つのClassを見てください。</p> <p>どちらもClick Meというボタンを描画するだけのシンプルなClassで、handleClickという名前のコールバック関数が渡されてます。(*便宜上Constructor等は省いてます)</p> <h5>1つ目はhandleClickがアロー関数</h5> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">class</span> Button <span class="synIdentifier">{</span> handleClick = () =&gt; <span class="synIdentifier">{</span> <span class="synIdentifier">this</span>.setState(<span class="synIdentifier">{</span> hoge: xxxx <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> ( &lt;button onClick=<span class="synIdentifier">{this</span>.handleClick<span class="synIdentifier">}</span>&gt; Click me &lt;/button&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <h5>2つ目はhandleClickがFunction関数</h5> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">class</span> Button <span class="synIdentifier">{</span> handleClick() <span class="synIdentifier">{</span> console.log(<span class="synConstant">'hogehoge'</span>); <span class="synIdentifier">}</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> ( &lt;button onClick=<span class="synIdentifier">{this</span>.handleClick<span class="synIdentifier">}</span>&gt; Click me &lt;/button&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>全く同じ構造のClassでなぜ1つ目はアロー関数で書かないといけなくて、2つ目はFunction関数でいいのか?</p> <h3>その答えの鍵は「this」にあったのです。</h3> <p>thisは奥が深いので完璧に理解することは厳しいと言われてますし、完璧に理解したからコードがすごい書けるようになる訳でもないと言われています。英語で言うところの「the」を完璧に理解したから凄い英語力が上がる訳ではないのと似てる気がしますね。</p> <p>でも今回、アロー関数を理解する上ではthisは重要な鍵を握っていたので改めてthisを見直してみました。</p> <h4>そもそもthisとは?</h4> <p>thisは、読み取り専用の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B0%A5%ED%A1%BC%A5%D0%A5%EB%CA%D1%BF%F4">グローバル変数</a>のようなものでどこからでも参照可能。 基本的にはthisを使うと、参照先はその呼び出し元のオブジェクトになりますが、問題は、thisの参照先(評価結果)が呼び出すタイミングや場所によって変わってしまうという事です。。(変数なので)</p> <p>thisの参照先は主に次の条件によって変化します。</p> <pre class="code" data-lang="" data-unlink>・ 実行コンテキストにおけるthis ・ コンストラクタにおけるthis ・ 関数とメソッドにおけるthis ・ Arrow Functionにおけるthis</pre> <p><a href="https://jsprimer.net/">JavaScript Primer</a>から引用。</p> <p>ちょっと言葉だけではピンとこないですね。サンプルコードを見ていきましょう。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">class</span> App <span class="synIdentifier">{</span> constructor() <span class="synIdentifier">{</span> <span class="synStatement">super</span>(); <span class="synIdentifier">this</span>.state = <span class="synIdentifier">{</span> title: <span class="synConstant">&quot;&quot;</span> <span class="synIdentifier">}</span>; <span class="synIdentifier">}</span> handleChangeTitle(e) <span class="synIdentifier">{</span> <span class="synStatement">const</span> title = e.target.value; <span class="synIdentifier">this</span>.setState(<span class="synIdentifier">{</span> title <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> ( &lt;input type=<span class="synConstant">&quot;text&quot;</span> onChange=<span class="synIdentifier">{this</span>.handleChangeTitle<span class="synIdentifier">}</span> /&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>テキスト入力するinputタグがあり、入力が発生するたびにhandleChangeTitleというコールバックが呼ばれ、ローカルStateに内容を保存するという単純なClassです。</p> <p>一見問題なさそうですが、入力をすると<code>Uncaught TypeError: Cannot read property 'setState' of undefined</code>というエラーが出てしまいます。</p> <h4>なぜこれでエラーになるのか??</h4> <p>まず、inputタグにコールバック関数を渡す際に、Reactでは裏側で以下のコードと同じ事が行われています。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>target.addEventListener(<span class="synConstant">'change'</span>, <span class="synIdentifier">function</span>(e)<span class="synIdentifier">{</span> <span class="synIdentifier">this</span>.handleChangeTitle(e); <span class="synIdentifier">}</span>); </pre> <p>addEventListener関数は、イベント発生時、第2引数の無名関数を呼ぶ時に、<code>その中で使われるthisをイベント発生元の要素(この場合input)に束縛してしまう</code>特性がある為です。</p> <p>(注)consoleで確認しようとしてもReactの仕様上、undefinedとしか出ませんが、以下のようなPlain JSだと確認できます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> inputBox = <span class="synStatement">document</span>.createElement(<span class="synConstant">'input'</span>) inputBox.addEventListener(<span class="synConstant">&quot;change&quot;</span>, <span class="synIdentifier">function</span>()<span class="synIdentifier">{</span> console.log(<span class="synIdentifier">this</span>) <span class="synComment">// 出力結果は &lt;input type=&quot;text&quot; id=&quot;inputBox&quot; /&gt;</span> <span class="synIdentifier">}</span>) </pre> <p>thisが参照しているのはInputである為、InputにはsetStateなんてないのでエラーになってしまうのです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsuihopen8/20201225/20201225202624.png" alt="f:id:matsuihopen8:20201225202624p:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3>こうしたthisの挙動を解決するため、アロー関数が導入された。</h3> <p>以下、<a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions">MDN</a>から引用。</p> <blockquote><p>2つの理由から、アロー関数が導入されました、</p> <p>1つ目の理由は関数を短く書きたいということ、</p> <p>2つ目の理由は this を束縛したくないということです。</p> <p>アロー関数自身は this を持ちません。 レキシカルスコープの this 値を使います。つまり、アロー関数内の this 値は通常の変数検索ルールに従います。 このためスコープに this 値がない場合、その一つ外側のスコープで this 値を探します。</p></blockquote> <p>すなわち、アロー関数は、関数が定義された時の環境のthisで確定させて変更させない、とも解釈できます。</p> <p>ただ関数を短く書けるというだけはなかったのです!</p> <p>先ほどのPlain JSをアロー関数に変えてみると、出力結果はInputではなく、一つ外側のWindowオブジェクトになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> inputBox = <span class="synStatement">document</span>.createElement(<span class="synConstant">'input'</span>) inputBox.addEventListener(<span class="synConstant">&quot;change&quot;</span>, () =&gt; <span class="synIdentifier">{</span> console.log(<span class="synIdentifier">this</span>) <span class="synComment">// 出力結果は、Windowオブジェクト</span> <span class="synIdentifier">}</span>) </pre> <p>ちなみに、アロー関数以前は、bindを当てるという方法がありました。</p> <p>先ほどのPlain JSにbind関数を当てると、こちらもthisの参照は一つ外側のWindowオブジェクトになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> inputBox = <span class="synStatement">document</span>.createElement(<span class="synConstant">'input'</span>) inputBox.addEventListener(<span class="synConstant">&quot;change&quot;</span>, <span class="synIdentifier">function</span>()<span class="synIdentifier">{</span> console.log(<span class="synIdentifier">this</span>) <span class="synComment">// 出力結果は、Windowオブジェクト</span> <span class="synIdentifier">}</span>.bind(<span class="synIdentifier">this</span>)) </pre> <p>という訳で、最初のサンプルコードもアロー関数にすると、thisの参照が呼び出し元のオブジェクトである<code>Appインスタンス</code>になり、handleChangeTitleでsetStateがないぞ、というエラーも出ずに正常に動作します。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsuihopen8/20201225/20201225202642.png" alt="f:id:matsuihopen8:20201225202642p:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4>以上の事を踏まえて、アロー関数とFunction関数を使い分ける理由って何なのか?</h4> <p>大きな理由としては関数の処理の中で、thisを束縛したいかどうか?にあると思います。</p> <p>冒頭でお見せした2つのコードの違い、アロー関数の方はthisを使うので、束縛しないといけない、Fucntion関数の方はそうでない。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink> handleClick = () =&gt; <span class="synIdentifier">{</span> <span class="synIdentifier">this</span>.setState(<span class="synIdentifier">{</span> hoge: xxxx <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span> handleClick() <span class="synIdentifier">{</span> console.log(<span class="synConstant">'hogehoge'</span>); <span class="synIdentifier">}</span> </pre> <h4>パフォーマンスへの影響</h4> <p>コールバック内でアロー関数を使っても問題なく動作はできるのですが、React公式ドキュメントにはこう書かれてます。</p> <p>コールバック内でアロー関数を使ってしまうと、レンダーされるたびに異なるコールバック関数が毎回作成されて、パフォーマンスに影響するので避けるべき。 参考:<a href="https://ja.reactjs.org/docs/handling-events.html">React公式ドキュメント</a></p> <p>つまり、何気なくこんな感じでコールバックを書いてしまいがちだったのですが、、</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">class</span> Button <span class="synIdentifier">{</span> handleClick() <span class="synIdentifier">{</span> <span class="synIdentifier">this</span>.setState(<span class="synIdentifier">{</span> hoge: xxxx <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> ( &lt;button onClick=<span class="synIdentifier">{</span>() =&gt; <span class="synIdentifier">this</span>.handleClick()<span class="synIdentifier">}</span>&gt; Click me &lt;/button&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synComment">//もしくは外に処理を出さず、JSXの中に直接処理を書いたり。</span> <span class="synStatement">class</span> Button <span class="synIdentifier">{</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> ( &lt;button onClick=<span class="synIdentifier">{</span>() =&gt; <span class="synIdentifier">this</span>.setState(<span class="synIdentifier">{</span> hoge: xxxx <span class="synIdentifier">}</span>)<span class="synIdentifier">}</span>&gt; Click me &lt;/button&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>大抵のケースでは問題ないのですが、このコールバックが props の一部として下層の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>に渡される場合、それら下層<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>が余分に再描画されることになります。</p> <p>なので、この様にクラスフィールドを利用して書くのが一番良いパターンと言う事ですね。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">class</span> Button <span class="synIdentifier">{</span> handleClick = () =&gt; <span class="synIdentifier">{</span> <span class="synIdentifier">this</span>.setState(<span class="synIdentifier">{</span> hoge: xxxx <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> ( &lt;button onClick=<span class="synIdentifier">{this</span>.handleClick<span class="synIdentifier">}</span>&gt; Click me &lt;/button&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <h2>まとめ</h2> <p>アロー関数って関数を短くシンプルに書けるだけと思ってましたが、thisにも絡んでいたのは大きな学びになりました。 完璧に理解したとは言い難いですが、苦手意識はなくなりました。</p> <p>また今回みたいにフロント開発で学んだ<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>の小ネタを題材に書いてみようと思います。</p> matsuihopen8 Ruby biz Grand prix 2020特別賞を受賞しました! hatenablog://entry/26006613664900021 2020-12-16T11:30:00+09:00 2020-12-16T11:33:33+09:00 オープンエイト VP of Engineeringの古萱です。この度、弊社オープンエイト提供のVideo BRAINが「Ruby biz Grand prix 2020」にて 、特別賞を受賞させていただきました! Ruby biz Grand prixについては以下のようになっております。 ビジネスの領域においてプログラム言語 Ruby の特徴を活かして、新たなサービスを創造し世界へ発信している企業、団体及び個人を対象としたグランプリです。 一方で、弊社は以下を掲げております。 VISION: 世界を豊かにするコンテンツテクノロジーカンパニーになる。 MISSION: 人の気持ちを動かす、質の… <p>オープンエイト VP of Engineeringの古萱です。この度、弊社オープンエイト提供のVideo BRAINが「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> biz Grand prix 2020」にて 、特別賞を受賞させていただきました!</p> <p> <a href="https://rubybiz.jp/">Ruby biz Grand prix</a>については以下のようになっております。</p> <blockquote><p>ビジネスの領域においてプログラム言語 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> の特徴を活かして、新たなサービスを創造し世界へ発信している企業、団体及び個人を対象としたグランプリです。 <a href="https://rubybiz.jp/images/index/img_top_logo_2020.svg" class="http-image"><img src="https://rubybiz.jp/images/index/img_top_logo_2020.svg" class="http-image" alt="https://rubybiz.jp/images/index/img_top_logo_2020.svg"></a></p></blockquote> <p>一方で、弊社は以下を掲げております。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/VISION">VISION</a>: 世界を豊かにするコンテンツテク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CE%A5%ED">ノロ</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B8%A1%BC">ジー</a>カンパニーになる。</li> <li>MISSION: 人の気持ちを動かす、質の高い情報を体験できる世界を創造する。</li> </ul> <p>ということで、趣旨に共感するところもあり応募させていただいた次第です。</p> <h1>Video BRAINと<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a></h1> <h3>サービスの設計方針</h3> <p>これ!という特別なことは行なってはおりませんが、基本的なアプローチを徹底することでユーザーへの価値提供につなげています。</p> <ul> <li>認証基盤を始め、サービスをマイクロサービス化して分割統治</li> <li>アプリケーション層での対応とは別にWAFその他の導入など多層防御構成</li> <li>DependabotによるGemの継続的バージョンアップ(PullRequest自動生成活用)</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>アライアンス実現でも迅速にベストプ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ティスといえるRailsWayにて実装</li> </ul> <h3>なぜ<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>なのか</h3> <p>認証基盤・動画生成基盤・<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>エンジンの<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rest%20API">Rest API</a>部分を担っています。 マイクロサービス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>を志向した場合やともすればピボットも含めた事業展開に対応する場合においては、迅速な開発が行える<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のエコシステムは親和性が高いと考えています。</p> <p>正直なところ、奇をてらったような使い方や非常に革新的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の使い方・・・はしておりません。が、スタートアップの人材獲得においては優位性があると考えています(実際、新卒採用でお会いするスタートアップ志向が強い学生からも「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>に関する知見は必須」という言葉もありました)。</p> <h3>技術上の理由</h3> <p>マイクロサービスでの設計を行い、各サービスの大小がわかれる中、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Sinatra">Sinatra</a>・<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a>など成熟した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>がサービスの大小ごとで存在することもあり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>を選択しています。</p> <p>これにより、今後の拡張性を踏まえたマイクロサービスの設計に基づきながらもスモールピボットを繰り返し、半年で4回ほどのリニューアルを経て、MVP作成・<a class="keyword" href="http://d.hatena.ne.jp/keyword/PMF">PMF</a>を達成することができたものと考えています。</p> <h3>ビジネス上の理由</h3> <p>自社サービスとして“新しい考え方での動画生成”においては、独自の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>エンジンまでも作成しなくてはなりませんでした。</p> <p>立ち上げ時点では不確実性が非常に高く、ややモノリシックに設計したほうがMVPの作成及び<a class="keyword" href="http://d.hatena.ne.jp/keyword/PMF">PMF</a>までの速度が早いと判断したものの、今後の拡張性や事業のグロースを考えた時にマイクロサービスへの移行も視野に入れておく必要がありました。</p> <p>迅速に開発を進めつつ、マイクロサービスへの進化を考えた場合、エンジニアの採用や業務委託を考えると豊富な経験をもったエンジニアが期待でき、さらに若い世代のエンジニアにも受け入れられている。さらにエコシステムやコミュニティが確立されている<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>での開発はベストプ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ティスと考えて採用にいたりました。</p> <p>なお、結果立ち上げ時の内部サービスとしては認証基盤・動画生成基盤・<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>エンジンの複数サービスを開発する結果となりました。</p> <h1>Video BRAINとは</h1> <p>AIによるサポートにより、ブラウザを使って簡単に動画を制作するという着想に基づいて展開しているサービスです。</p> <div style="text-align: center;"> <iframe width="480" height="270" src="https://www.youtube.com/embed/ca382QdrB6g?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=ca382QdrB6g">www.youtube.com</a></cite> </div> <p>「動画を活用したい意向は高いが、実際には十分に実行できていない・・・という企業が多くを占めている」というファクトに基づき、「スキル」・「コスト」・「時間」という課題を解決し、さらにはその先の多用途での動画利活用にたどり着くまでを提供価値と位置付けてサービスを展開しています。</p> <p>以下のような点をサービスの特徴として有しており、一般に認識されているインストール型の動画制作ソリューションとは一線を画しています。</p> <ul> <li>AIのサポートにより、ブザウザを使って誰でも簡単に動画制作が可能</li> <li>手持ちのテキストや写真、動画をアップロードするだけで初稿が完成する簡単さ</li> <li>パワーポイント作成かのような手軽さで本格的な動画作成</li> <li>オーディオ編集を可能とするMA(Multi Audio)機能</li> <li>最大60分までの長尺動画編集に対応するなど幅広い用途に対応</li> <li>用途に適した豊富なテンプレートを活用した簡単な動画制作も可能</li> <li>カスタマーサクセスによる動画活用知見注入で動画活用の拡大・深化をサポート</li> </ul> <p>5Gの商用化など、様々な情勢を追い風に2020年には3,000億規模といわれている動画広告を始め、<a class="keyword" href="http://d.hatena.ne.jp/keyword/SNS">SNS</a>やオウンドメディアでの動画利用、オンラインサービスの拡大に合わせたマニュアルの動画化、コロナ禍における動画でのストック型、非同期の情報発信など様々な用途で活用していただける可能性が広がっていると考えています。</p> <p>プロダクトの開発としても、参考になる他プロダクトや事例がない中でも我々自身で技術検証も含めて進めていく必要があるため難易度も非常に高いですが、その分エンジニアとしての腕の見せ所がたくさんあるとも言えます。</p> <p>サービスの開発に興味を持っていただいたエンジニアのみなさん、ぜひご連絡ください!</p> <p><a href="https://open8.com/wpdir/wp-content/uploads/2020/11/fc6927a4cd7fc6f068de9eb5d3ae4aff.png" class="http-image"><img src="https://open8.com/wpdir/wp-content/uploads/2020/11/fc6927a4cd7fc6f068de9eb5d3ae4aff.png" class="http-image" alt="https://open8.com/wpdir/wp-content/uploads/2020/11/fc6927a4cd7fc6f068de9eb5d3ae4aff.png"></a></p> furukayat Startup4社 AWS勉強会を共催しました! hatenablog://entry/26006613640274797 2020-10-23T12:00:00+09:00 2020-10-26T14:42:47+09:00 本日(2020年10月某日)のランチにマンチズバーガー シャックさんのハンバーガーを食べてご満悦だった、VP of Engineeringの古萱です。おいしいランチは一日を充実させる要素になりますね。 本日の幸せの素、ハンバーガー(※本文とは一切関係ありません) 勉強会開催報告 去る9/24にFinatext、Hacobu、ユニファと弊社オープンエイトの4社共催、かつAWSからソリューションアーキテクトの針原さんをゲストとしてお招きして『Startup テックリード勉強会 -AWS編-』というイベントを開催させていただきました。(ご報告まで時間が空いてしまいました、、) connpass.co… <p>本日(2020年10月某日)のランチにマンチズバーガー シャックさんの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CF%A5%F3%A5%D0%A1%BC%A5%AC">ハンバーガ</a>ーを食べてご満悦だった、VP of Engineeringの古萱です。おいしいランチは一日を充実させる要素になりますね。</p> <p><figure class="figure-image figure-image-fotolife" title="本日の幸せの素、ハンバーガー(※本文とは一切関係ありません)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/furukayat/20201013/20201013191641.jpg" alt="f:id:furukayat:20201013191641j:plain" title="f:id:furukayat:20201013191641j:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>本日の幸せの素、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CF%A5%F3%A5%D0%A1%BC%A5%AC">ハンバーガ</a>ー(※本文とは一切関係ありません)</figcaption></figure></p> <h2>勉強会開催報告</h2> <p>去る9/24にFinatext、Hacobu、ユニファと弊社オープンエイトの4社共催、かつ<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>からソリューションアーキテクトの針原さんをゲストとしてお招きして『Startup テッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A1%BC%A5%C9">クリード</a>勉強会 -<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>編-』というイベントを開催させていただきました。(ご報告まで時間が空いてしまいました、、)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fconnpass.com%2Fevent%2F187972%2F" title="[増枠]Startup テックリード勉強会 -AWS編- (2020/09/24 19:00〜)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://connpass.com/event/187972/">connpass.com</a></cite></p> <p>弊社からは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%F3%A5%AC%A5%DD%A1%BC%A5%EB">シンガポール</a>チームでアプリケーションエンジニア兼SREとして活躍してくれている<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C8%C4%C3%AB">板谷</a>が「OPEN8 Singaporeの<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>(コンテナ技術)活用 (EKS, App Mesh)」ということで登壇させていただきました。</p> <p>VideoBRAINでさくっとこれを表現するとこんな感じでしょうか・・</p> <p><iframe width="480" height="270" src="https://www.youtube.com/embed/Qpu7FhxXM10?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://youtu.be/Qpu7FhxXM10">youtu.be</a></cite></p> <p>各社からそれぞれ興味深い発信をさせていただきました。</p> <blockquote><p>"Startupと銘打った勉強会なのに、みんな最新の<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>の機能を使い過ぎではw "</p></blockquote> <p>という<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tweet">Tweet</a>をいただけるなど、ご参加いただいた皆さんに一定の情報提供が行えたのではないかと考えております。一点、参加者へのアンケートでご要望いただいていた ”各社の<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>費用” については差し控えさせていただいたのはご容赦くださいw</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Special%20thanks%20to">Special thanks to</a>:</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> 針原さん(<a href="https://twitter.com/_hariby">@_hariby</a>)</li> <li>Finatext 石橋さん(<a href="https://twitter.com/bashi0501">@bashi0501</a>)</li> <li>Hacobu 戸井田さん(<a href="https://twitter.com/yuki_toida">@yuki_toida</a>)</li> <li>ユニファ <a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%D6%BE%C2">赤沼</a>さん(<a href="https://twitter.com/akanuma">@akanuma</a>)</li> </ul> <h2>なぜ勉強会を開催するのか</h2> <p>テックブログや社内勉強会などの活動とも通じるものがありますが、弊社としては以下のようなことを考えて開催しています。</p> <h4><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D6%A5%E9%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">ブランディング</a></h4> <p>弊社のサービスそのもの、およびサービス説明では伝わらないプロダクトの内側を一定の社内事情などを含めてお話しさせていただくことで、会社としての技術的な現在位置や方向性などを社内外の方に知っていただき、共感・信頼を得ることを目的としています。</p> <p>また、社外との関わりの中で自社・自身を客観的に見ることができ、強み・弱みを改めて知るのもその後の成長に大きく寄与しさらなる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D6%A5%E9%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">ブランディング</a>強化につなげられると信じています</p> <h4>インプット&アウトプット</h4> <p>開催する組織として、登壇するエンジニアとして、大きなインプット&アウトプットの場になります。登壇についてはアウトプットと捉える方が多いような気もしますが、新たなインプットあるいはこれまでに得ていたインプットの再整理なしに発信することは難しく、相応の時間を使って準備をするわけですが、ここで新たな成長の機会を得ることができます。</p> <p>開催にあたっては、オフラインの場ではともすればトマホークが飛んでくるかもしれないという(?)、ほどほどのプレッシャー下で、オンラインではほとんど反応が見えない中でマイクに向かって喋るという違和感の中で、発表するという経験を積むことができるのも大きいですね!</p> <h4>リクルーティング</h4> <p>募集要項や面談の場などで言語や<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>、働く環境などの情報は伝えるように心がけてはいるものの、それとは別に実際に業務に当たっているエンジニアからの生の声が聞こえる場を提供させていただくことで、良いご縁に繋がる、ご縁があった時の相互理解が深まることを期待しています。</p> <p>実際、ブログや勉強会その他の活動について何らかの形で言及いただけたときにはやっててよかったと実感しています。</p> <h4>エンジニア個人の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D6%A5%E9%A5%F3%A5%C7%A5%A3%A5%F3%A5%B0">ブランディング</a>・キャリア形成</h4> <p>オープンな発信のもつ力は言わずもがな、かと思います。</p> <p>その一方で、これは意外と実行するまでは気づいていないエンジニアも多いように感じるのですが、オープンなイベントに登壇することは社内に対しても大きな力を発揮すると考えており、VPoEとしては強い興味を持って見ています。</p> <p>今まさに実プロダクトの開発に使っている技術に関してであれば興味深いのはもちろんなのですが、もしそれが現時点では実プロダクトに使っていないような内容であった場合も、登壇者のその技術に対する熱意や現在位置、それに対する外部からの反応は今後のプロダクト開発の方向性に大きく影響してくるものではないでしょうか。</p> <h2>なぜこの記事を書いたのか</h2> <p>コロナ禍下で情報発信に苦慮しているエンジニア組織も多いと思います。我々としても試行錯誤で取り組んでいる面が大きく、ぜひ情報交換、協力させていただきながら進んでおります。弊社内の人間から初めて弊社を知っていただいた方まで、何かお声がけいただけるきっかけになるかも?と考えて投稿させていただきました。</p> <h2>最後にお約束</h2> <p>弊社オープンエイトでは、ブラウザを使って簡単に動画を作れる<a href="https://video-b.com/">VideoBRAIN</a>を提供しています。</p> <p>今まさに成長フェーズで We are hiring! ということで興味を持っていただいた方は弊社CTOの石橋(<a href="https://twitter.com/hisatake">@hisatake</a>)や古萱(<a href="https://twitter.com/emfurupon777">@emfurupon777</a>)までDMよろしくお願いします!</p> <p>あ。VideoBRAIN導入のご相談ももちろん歓迎いたします!!</p> <p>上にあげた動画はさくっと作ったものではありますが、もちろんもっと凝った動画に仕上げることも可能です。多用途の動画を簡単に作成できるので良いですよ!はい。宣伝ですw</p> furukayat Reactでコンポーネントのふるまいだけ使い回したい hatenablog://entry/26006613598426603 2020-07-14T19:36:08+09:00 2020-07-14T19:36:08+09:00 はじめに こんにちは。VIDEO BRAINでフロントエンドを担当している田村です。いつの間にか新卒2年目になっていました。 最近全く外に出られないのでアニメを見始めました。「かぐや様は告らせたい」めっちゃ面白いですね。速攻で漫画全巻買いました。 で、早速仕事の話なのですが、最近PRのレビューしてるとコンポーネントのふるまい再利用できそうだなぁと思うことがちょくちょくありました。 よく使われる手法として、Render PropsとHigh Order Componentという手法があるので紹介しようと思います。 Render Props 例えば、レンダリング時の月を表示するコンポーネントがある… <h2>はじめに</h2> <p>こんにちは。VIDEO BRAINでフロントエンドを担当している田村です。いつの間にか新卒2年目になっていました。 最近全く外に出られないのでアニメを見始めました。「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AB%A4%B0%A4%E4%CD%CD%A4%CF%B9%F0%A4%E9%A4%BB%A4%BF%A4%A4">かぐや様は告らせたい</a>」めっちゃ面白いですね。速攻で漫画全巻買いました。</p> <p>で、早速仕事の話なのですが、最近PRのレビューしてると<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>のふるまい再利用できそうだなぁと思うことがちょくちょくありました。 よく使われる手法として、Render PropsとHigh Order Componentという手法があるので紹介しようと思います。</p> <h2>Render Props</h2> <p>例えば、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>時の月を表示する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>があるとします。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">class</span> Month <span class="synStatement">extends</span> React.Component <span class="synIdentifier">{</span> constructor() <span class="synIdentifier">{</span> <span class="synStatement">super</span>(); <span class="synIdentifier">this</span>.state = <span class="synIdentifier">{</span> time: <span class="synStatement">new</span> <span class="synType">Date</span>() <span class="synIdentifier">}</span>; <span class="synIdentifier">}</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> &lt;h1&gt;今は<span class="synIdentifier">{this</span>.state.time.getMonth() + 1<span class="synIdentifier">}</span>月です&lt;/h1&gt;; <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synComment">// =&gt; 今は7月です</span> </pre> <p>しかし、別のページに年/月/日を表示したいという要望が出てきました。 できれば上の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>のstateをそのまま使いたいので、Monthを拡張してみました。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">class</span> Month <span class="synStatement">extends</span> React.Component <span class="synIdentifier">{</span> constructor() <span class="synIdentifier">{</span> <span class="synStatement">super</span>(); <span class="synIdentifier">this</span>.state = <span class="synIdentifier">{</span> time: <span class="synStatement">new</span> <span class="synType">Date</span>() <span class="synIdentifier">}</span>; <span class="synIdentifier">}</span> render() <span class="synIdentifier">{</span> <span class="synStatement">const</span> <span class="synIdentifier">{</span> pageName <span class="synIdentifier">}</span> = <span class="synIdentifier">this</span>.props; <span class="synStatement">return</span> pageName === <span class="synConstant">'month'</span> ? ( &lt;h1&gt;今は<span class="synIdentifier">{this</span>.state.time.getMonth() + 1<span class="synIdentifier">}</span>月です&lt;/h1&gt; ) : ( &lt;h1&gt; 今は <span class="synIdentifier">{</span>`$<span class="synIdentifier">{this</span>.state.time.getFullYear()<span class="synIdentifier">}</span> / $<span class="synIdentifier">{this</span>.state.time.getMonth() + 1<span class="synIdentifier">}</span> / $<span class="synIdentifier">{this</span>.state.time.getDate()<span class="synIdentifier">}</span>`<span class="synIdentifier">}</span> です &lt;/h1&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synComment">// =&gt; 今は7月です</span> <span class="synComment">// =&gt; 今は2020/7/8です</span> </pre> <p>香ばしいコードの完成です。日だけ表示したい。時/分/秒だけ表示したいという要望が増えるたび条件分岐も増えていく未来が見えます。 できればstateの振る舞いは今のままで、表示する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>だけ変えたいですね。 こういった場合に使えるのがRender Propsです。</p> <p>Render Propsは、propsにrender関数を渡すことによって、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>自信が持つrenderを変化させる手法です。 振る舞いをそのままに、表示する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>だけ変えることができるので、今回の例にはうってつけです。 コードで書くとこんな感じ。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">class</span> DateRender <span class="synStatement">extends</span> React.Component <span class="synIdentifier">{</span> constructor() <span class="synIdentifier">{</span> <span class="synStatement">super</span>(); <span class="synIdentifier">this</span>.state = <span class="synIdentifier">{</span> time: <span class="synStatement">new</span> <span class="synType">Date</span>() <span class="synIdentifier">}</span>; <span class="synIdentifier">}</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> ( &lt;h1&gt; 今は <span class="synIdentifier">{this</span>.props.render(<span class="synIdentifier">this</span>.state.time)<span class="synIdentifier">}</span> です &lt;/h1&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">class</span> MonthRender <span class="synStatement">extends</span> React.Component <span class="synIdentifier">{</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> ( &lt;DateRender render=<span class="synIdentifier">{</span>time =&gt; <span class="synIdentifier">{</span> <span class="synStatement">return</span> &lt;h2&gt;<span class="synIdentifier">{</span>`$<span class="synIdentifier">{</span>time.getMonth() + 1<span class="synIdentifier">}</span>月`<span class="synIdentifier">}</span>&lt;/h2&gt;; <span class="synIdentifier">}}</span> /&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synComment">// =&gt; 今は7月です</span> </pre> <p>こうすれば、表示する日時のパターンがどれだけ増えても、振る舞いを再利用して<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を作ることができます。 また、props.renderとして渡さずに、childrenとして渡すこともできます。 childrenとして渡す場合には</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>&lt;DateRender children=<span class="synIdentifier">{</span>time =&gt; ( &lt;h2&gt;<span class="synIdentifier">{</span>`$<span class="synIdentifier">{</span>time.getMonth() + 1<span class="synIdentifier">}</span>月`<span class="synIdentifier">}</span>&lt;/h2&gt; )<span class="synIdentifier">}</span>/&gt; &lt;DateRender&gt; <span class="synIdentifier">{</span>time =&gt; ( &lt;h2&gt;<span class="synIdentifier">{</span>`$<span class="synIdentifier">{</span>time.getMonth() + 1<span class="synIdentifier">}</span>月`<span class="synIdentifier">}</span>&lt;/h2&gt; )<span class="synIdentifier">}</span> &lt;/DateRender&gt; </pre> <p>こんな風に書くこともできます。</p> <p>また、同じことを実現する手法として、High Order Componentがあります。</p> <h2>High Order Component ( HOC )</h2> <p>所謂<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%E2%B3%AC%B4%D8%BF%F4">高階関数</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>です。 これもRender Propsと同じようにふるまいを再利用するためによく使われる手法です。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%E2%B3%AC%B4%D8%BF%F4">高階関数</a>のReact版みたいなものです。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> date = (dateType) =&gt; (number) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">return</span> `今は$<span class="synIdentifier">{</span>number<span class="synIdentifier">}</span>$<span class="synIdentifier">{</span>dateType<span class="synIdentifier">}</span>です` <span class="synIdentifier">}</span> date(<span class="synConstant">'月'</span>)(2); <span class="synComment">// =&gt; 今は2月です</span> date(<span class="synConstant">'日'</span>)(1); <span class="synComment">// =&gt; 今は1日です</span> </pre> <p>↑の引数と返り値が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>になったのがHOCです。</p> <p>HOCでふるまいを使い回す場合は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%E2%B3%AC%B4%D8%BF%F4">高階関数</a>のとしての役割を持つラッパーを作って実装していきます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> dateComponent(WrappedComponent, currentDate) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">class</span> <span class="synStatement">extends</span> React.Component <span class="synIdentifier">{</span> constructor(props) <span class="synIdentifier">{</span> <span class="synStatement">super</span>(props); <span class="synIdentifier">this</span>.state = <span class="synIdentifier">{</span> date: currentDate(<span class="synStatement">new</span> <span class="synType">Date</span>()); <span class="synIdentifier">}</span>; <span class="synIdentifier">}</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> &lt;WrappedComponent date=<span class="synIdentifier">{this</span>.state.date<span class="synIdentifier">}</span> <span class="synIdentifier">{</span>...<span class="synIdentifier">this</span>.props<span class="synIdentifier">}</span> /&gt;; <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>; <span class="synIdentifier">}</span> <span class="synStatement">class</span> Month <span class="synStatement">extends</span> React.Component <span class="synIdentifier">{</span> render() <span class="synIdentifier">{</span> <span class="synStatement">const</span> <span class="synIdentifier">{</span> date <span class="synIdentifier">}</span> = <span class="synIdentifier">this</span>.props; <span class="synStatement">return</span> ( &lt;h1&gt;今は<span class="synIdentifier">{</span>date<span class="synIdentifier">}</span>月です&lt;/h1&gt; ) <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">class</span> <span class="synType">Date</span> <span class="synStatement">extends</span> React.Component <span class="synIdentifier">{</span> render() <span class="synIdentifier">{</span> <span class="synStatement">const</span> <span class="synIdentifier">{</span> date <span class="synIdentifier">}</span> = <span class="synIdentifier">this</span>.props; <span class="synStatement">return</span> ( &lt;h1&gt;今は<span class="synIdentifier">{</span>date<span class="synIdentifier">}</span>日です&lt;/h1&gt; ) <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">const</span> MonthComponent = dateComponent( Month, time =&gt; (time.getMonth() + 1) ); <span class="synComment">// =&gt; 今は7月です</span> <span class="synStatement">const</span> DateComponent = dateComponent( <span class="synType">Date</span>, time =&gt; time.getDate() ); <span class="synComment">// =&gt; 今は8日です</span> </pre> <h2>どっちを使うのか</h2> <p>以上の2つを比べた時、私個人としてはHOCよりもRender Propsのほうが好きです。 Reactにおいてベストなのは、最もシンプルな形であるpropsを使った純関数の形です。あくまでstateは2の次。 それをわかりやすい形で保てるのがRender Propsだと思っています。 それ以外の理由としては</p> <ul> <li>render部分をpropsとして渡していることが直感的にわかる</li> <li>親と子の間に挟む関数がないので、予期しない実装(prototypeで関数上書きするとか)が入る余地がない</li> </ul> <p>が上げられます。</p> <h2>終わりに</h2> <p>Reactクラスは普通のJsClassのように、継承することを良しとしていません。 Reactを触り始めたばかりだと癖があるように感じるかもしれませんが、Render PropsやHigh Order Componentを使えば、むやみに同じロジックを増やさずに済みます。</p> <p>はじめから未来の仕様変更を予想して<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を作るのは難しいですが、なるべく小さな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>で、減らせる分岐は減らしながら書いてくのが大切だなぁと思うこの頃です。 あと、ここ3ヶ月くらいReact書いてないからそろそろ書きたい。</p> tamuraropen8 The journey to the finish line hatenablog://entry/26006613592789135 2020-07-08T12:50:59+09:00 2020-07-08T15:42:00+09:00 What is AWS DeepRacer? It is a miniature racing car that is 1/18th of a real car in term of scale*1. Furthermore, it comes with different type of cameras and sensors that enable autonomous driving. Following are the available sensors (image courtesy of Amazon Web Service): Image Name Description Sin… <h4>What is <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> DeepRacer?</h4> <p>It is a miniature racing car that is 1/18th of a real car in term of scale<a href="#f-ea5baccc" name="fn-ea5baccc" title="Amazon. (2020). Retrieved from https://aws.amazon.com/deepracer/">*1</a>. Furthermore, it comes with different type of cameras and sensors that enable autonomous driving. Following are the available sensors (image courtesy of <a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a> Web Service):</p> <table class="tg"> <thead> <tr> <th class="tg-0pky">Image</th> <th class="tg-0pky">Name</th> <th class="tg-0pky">Description</th> </tr> </thead> <tbody> <tr> <td class="tg-0pky"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702185019.png" alt="f:id:tanhungp:20200702185019p:plain" title="f:id:tanhungp:20200702185019p:plain" class="hatena-fotolife" itemprop="image"></span></td> <td class="tg-0pky">Single camera</td> <td class="tg-0pky">Single-lens 120-degree <a class="keyword" href="http://d.hatena.ne.jp/keyword/field%20of%20view">field of view</a> camera capturing at 15fps. The images are converted into greyscale before being <a class="keyword" href="http://d.hatena.ne.jp/keyword/fed">fed</a> to the neural network.</td> </tr> <tr> <td class="tg-0pky"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702185102.png" alt="f:id:tanhungp:20200702185102p:plain" title="f:id:tanhungp:20200702185102p:plain" class="hatena-fotolife" itemprop="image"></span></td> <td class="tg-0pky">Stereo camera</td> <td class="tg-0pky">Composed of two single-lens cameras, stereo camera can generate depth information of the objects in front of the agent and thus be used to detect and avoid obstacles on the track. The cameras capture images with the same resolution and frequency. Images from both cameras are converted into grey scale, stacked and then <a class="keyword" href="http://d.hatena.ne.jp/keyword/fed">fed</a> into the neural network.</td> </tr> <tr> <td class="tg-0pky"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702185117.png" alt="f:id:tanhungp:20200702185117p:plain" title="f:id:tanhungp:20200702185117p:plain" class="hatena-fotolife" itemprop="image"></span></td> <td class="tg-0pky">Lidar sensor</td> <td class="tg-0pky">LIDAR is a light detection and ranging sensor. It scans its environment and provides inputs to the model to determine when to overtake another vehicle and beat it to the finish line. It provides continuous visibility of its surroundings and can see in all directions and always know its distances from objects or other vehicles on the track.</td> </tr> </tbody> </table> <p>The sensors sample rate is 15 Hz (15 samples per second).</p> <p>Beside the sensors, the car itself can run up to 4 m/s with maximum steering angle of 30 degrees.</p> <h4>What is <a class="keyword" href="http://d.hatena.ne.jp/keyword/reinforcement%20learning">reinforcement learning</a>?</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Reinforcement%20learning">Reinforcement learning</a> is a type of machine learning that is capable of planning and decision making. It is one of the big field of research in AI, if you want to know more about it from the ground up, I recommend the book called <a class="keyword" href="http://d.hatena.ne.jp/keyword/Reinforcement%20Learning">Reinforcement Learning</a>: An Introduction<a href="#f-adb36258" name="fn-adb36258" title="Sutton, R. S., &amp;amp; Barto, A. G. (2011). Reinforcement learning: An introduction. Retrieved from https://web.stanford.edu/class/psych209/Readings/SuttonBartoIPRLBook2ndEd.pdf">*2</a>. Below is a diagram showing a general <a class="keyword" href="http://d.hatena.ne.jp/keyword/reinforcement%20learning">reinforcement learning</a> schema:</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702185636.png" alt="f:id:tanhungp:20200702185636p:plain" title="f:id:tanhungp:20200702185636p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>Our agent here is the DeepRacer car. Environment is the actual racing track. Our agent percepts or sees the environment through its sensors, this perception becomes the state of the agent. Then it will perform actions based on its internal set of rules (or model).</p> <p>The motivation for the model to try to perform better is based on the reward. The model can be a <a class="keyword" href="http://d.hatena.ne.jp/keyword/deep%20learning">deep learning</a> neural network, trying to approximate the best policy. A policy is a function that map the state and action with highest possible rewards. For example, the rewards can simply be finishing the track, then the policy (agent/model) that manage to finish the track will be awarded higher rewards.</p> <p>For our current problem, the actions are discrete and separated into 2 groups: steering angle and speed. Steering angle range is 1 - 30 degrees with granularity options 3, 5 or 7. Speed range is 0.1 - 4 ms with granularity options 1, 2, 3. Number of actions is based on granularity only. For example with steering granularity of 3 and speed granularity of 2 will results in total of 3 * 2 = 6 actions.</p> <p>To actually approximate the best policy, <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> uses PPO (Proximal Policy Optimization)<a href="#f-27946842" name="fn-27946842" title="Schulman, J., Wolski, F., Dhariwal, P., Radford, A., &amp;amp; Klimov, O. (2017). Proximal policy optimization algorithms. arXiv preprint arXiv:1707.06347. Retrieved from https://arxiv.org/abs/1707.06347">*3</a><a href="#f-8b8a4abd" name="fn-8b8a4abd" title="Schulman, J., Wolski, F., Dhariwal, P., Radford, A., &amp;amp; Klimov, O. (2017a). Proximal Policy Optimization. Retrieved from https://openai.com/blog/openai-baselines-ppo/#ppo">*4</a> method to train the DeepRacer. The underlying neural network is a simple n-layer convolution neural network (CNN). n here can be 3 or 5 in our case.</p> <p>There are a few parameters we received during training that can be used to calculate the reward functions:</p> <table class="tg"> <thead> <tr> <th class="tg-0pky">Environment</th> <th class="tg-0pky">Car</th> <th class="tg-0pky">Progress</th> </tr> </thead> <tbody> <tr> <td class="tg-0pky">closest_waypoints, closest_objects, distance_from center, track_length, track_width, waypoints</td> <td class="tg-0pky">is_crashed, is_left_of_center, is_offtrack, is_reversed, heading, speed, steering_angle, x, y</td> <td class="tg-0pky">steps, progress</td> </tr> </tbody> </table> <h4>The social distancing F1 race</h4> <p>To the future generation that is not familiar with the word ‘social distancing', this race happened amidst the CoVid-19 virus outbreak. To prevent its spread, we need to social distancing ourselves. This race is done entirely online in the virtual circuit. This is our race track, called Circuit de Barcelona-Catalunya, which is the reproduction of the <a class="keyword" href="http://d.hatena.ne.jp/keyword/official">official</a> F1 Spanish Grand Prix track:</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702190416.png" alt="f:id:tanhungp:20200702190416p:plain" title="f:id:tanhungp:20200702190416p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>We will refer to this track as the F1 track.</p> <p>We also do a time-trial focus on this track, called The 2019 DeepRacer Championship Cup:</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702190655.png" alt="f:id:tanhungp:20200702190655p:plain" title="f:id:tanhungp:20200702190655p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>We will refer to this track as the 2019 track.</p> <p>So a time-trial is one of the 3 types of race available:</p> <table class="tg"> <thead> <tr> <th class="tg-0pky">Race</th> <th class="tg-0pky">Recommended sensors</th> <th class="tg-0pky">Description</th> </tr> </thead> <tbody> <tr> <td class="tg-0pky">Time trial</td> <td class="tg-0pky">Single camera</td> <td class="tg-0pky">No obstacle or other cars, just you and the road. Every time the car is out of the track, you get 5 seconds penalty time</td> </tr> <tr> <td class="tg-0pky">Object avoidance</td> <td class="tg-0pky">Stereo camera</td> <td class="tg-0pky">There are obstacles littered around the track. When you hit the obstacle or out of the track, you get 5 seconds penalty time.</td> </tr> <tr> <td class="tg-0pky">Head-to-head</td> <td class="tg-0pky">Lidar, stereo camera</td> <td class="tg-0pky">The bread and butter of racing. You face off against other cars. If you hit them you get 5 seconds penalty, same goes for other cars.</td> </tr> </tbody> </table> <h4>The ‘rewarding’ journey to the finish line</h4> <p>We had options to customize our agent itself or our reward function and we found out that they are equally important.</p> <h5>The first trial</h5> <p>Firstly we just wanted to make a functional agent to get a feel of how things work. Our first agent was a single camera, 3 layer CNN, max speed 1 m/s granularity 3, max steering angle granularity 3. Our reward function was focusing on stay in the track and avoid steering too much, hence the parameters we used in the function were: all_wheels_<a class="keyword" href="http://d.hatena.ne.jp/keyword/on_">on_</a>track, track_width, distance_from_center, steering_angle. And voila! It worked albeit the car was very slow.</p> <h5>The trial and errors</h5> <p>We then tried to play around with the reward function, testing from negative rewards to super simple rewards like just speed and progress.</p> <p>Some trials worked and some did not. Some never learned:</p> <table class="tg"> <tbody> <tr> <td class="tg-0pky"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702191939.png" alt="f:id:tanhungp:20200702191939p:plain" title="f:id:tanhungp:20200702191939p:plain" class="hatena-fotolife" itemprop="image"></span></td> <td class="tg-0pky"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702191953.png" alt="f:id:tanhungp:20200702191953p:plain" title="f:id:tanhungp:20200702191953p:plain" class="hatena-fotolife" itemprop="image"></span></td> </tr> </tbody> </table> <p>We started to diverge between the F1 track and the 2019 track. We wanted to try all 3 types of race for the F1 while focus on time trial for the 2019.</p> <p>The overall trend for both tracks was adding speed multiplicative rewards. But for the F1 track, we actually tried to simplified both the rewards and the agent action space while for the 2019 track we tried to overfit the agent with the optimal path.<a href="#f-a91da2f4" name="fn-a91da2f4" title="Lecchi, S. (2009). Artificial intelligence in racing games. Paper presented at the 2009 IEEE Symposium on Computational Intelligence and Games">*5</a></p> <p>Tips: you can pre-train a model on easier tracks for it to learn the basic, then clone that model to train on harder tracks.</p> <h5>The realization</h5> <p>Up til now, we had not touch the agent much beside trying to increase its speed and increase the granularity to maximum as we thought it would help the agent to be more flexible and better. However, with great power (number of actions) comes great responsibility. Responsibility, our model had none. It was drunk driving.</p> <p>The biggest jump in the leader boards for the F1 track was went we crank the speed to maximum and reduce the action space as well as the maximum steering angle. This is our best model action space:</p> <table class="tg"> <thead> <tr> <th class="tg-0pky">Action number</th> <th class="tg-0pky">Steering</th> <th class="tg-0pky">Speed</th> </tr> </thead> <tbody> <tr> <td class="tg-0pky">0</td> <td class="tg-0pky">-15 degrees</td> <td class="tg-0pky">1.33m/s</td> </tr> <tr> <td class="tg-0pky">1</td> <td class="tg-0pky">-15 degrees</td> <td class="tg-0pky">2.67m/s</td> </tr> <tr> <td class="tg-0pky">2</td> <td class="tg-0pky">-15 degrees</td> <td class="tg-0pky">4m/s</td> </tr> <tr> <td class="tg-0pky">3</td> <td class="tg-0pky">0 degrees</td> <td class="tg-0pky">1.33m/s</td> </tr> <tr> <td class="tg-0pky">4</td> <td class="tg-0pky">0 degrees</td> <td class="tg-0pky">2.67m/s</td> </tr> <tr> <td class="tg-0pky">5</td> <td class="tg-0pky">0 degrees</td> <td class="tg-0pky">4m/s</td> </tr> <tr> <td class="tg-0pky">6</td> <td class="tg-0pky">15 degrees</td> <td class="tg-0pky">1.33m/s</td> </tr> <tr> <td class="tg-0pky">7</td> <td class="tg-0pky">15 degrees</td> <td class="tg-0pky">2.67m/s</td> </tr> <tr> <td class="tg-0pky">8</td> <td class="tg-0pky">15 degrees</td> <td class="tg-0pky">4m/s</td> </tr> </tbody> </table> <p>Beside the task specific reward (try to avoid obstacles and other cars), our reward function utilized a speed multiplicative coefficient. So whatever our reward was, it would be multiplied by the current speed. We also gave the agent a big fat reward once it reached the finish line.</p> <p>The 2019 track also saw many increments. We obtained the optimum path<a href="#f-15832a61" name="fn-15832a61" title="Xiong, Y. (2010). Racing line optimization. Massachusetts Institute of Technology, ">*6</a><a href="#f-ea3390ff" name="fn-ea3390ff" title="Vesel, R. (2015). Racing line optimization@ race optimal. ACM SIGEVOlution, 7(2-3), 12-20. ">*7</a> by assigning each waypoint a neuron and the sum of distance between each neuron as the loss function to let them converge (minimize the loss). Below was the optimum path for this track after 20K epochs, which was also the shortest path ever possible(the blue line). Now the agent only needed to learn to drive closer and closer to the optimum while remain fast. There were some other algorithms that calculated optimum racing line differently, but here we used the shortest path as our optimum. Below was our calculated optimum path (blue dots) for the 2019 track:</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702192940.png" alt="f:id:tanhungp:20200702192940p:plain" title="f:id:tanhungp:20200702192940p:plain" class="hatena-fotolife" itemprop="image"></span></p> <h5>The finish line</h5> <p>Here we are, at the end of the road. The final result of the F1 race was this:</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tanhungp/20200702/20200702193049.png" alt="f:id:tanhungp:20200702193049p:plain" title="f:id:tanhungp:20200702193049p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>Not too shabby for our first time entering <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> DeepRacer. We are even in top 9 for object avoidance, and hence here is the video for that:</p> <iframe width="560" height="315" frameborder="0" allowfullscreen="" src="//www.youtube.com/embed/ZAPJhx9W40Q"></iframe> <p><br><a href="https://youtube.com/watch?v=ZAPJhx9W40Q">Best obstacle avoidance F1</a></p> <p>Our 2019 track also sees promising result, we are 67/341 for the time trial, here is the video:</p> <iframe width="560" height="315" frameborder="0" allowfullscreen="" src="//www.youtube.com/embed/DI0L9itkqQw"></iframe> <p><br><a href="https://youtube.com/watch?v=DI0L9itkqQw">Optimal path 2019 track</a></p> <h4>Our lesson</h4> <p>Training an artificial intelligent agent is not much different from training a baby, you need to be careful and watchful. Give it rewards correctly or else it will have a wrong motivation. Designing the agent and its action space is also equally important!</p> <p>Authors: Pham Tan Hung (F1 race), Yun Yi Ke (2019 race)</p> <div class="footnote"> <p class="footnote"><a href="#fn-ea5baccc" name="f-ea5baccc" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a>. (2020). Retrieved from <a href="https://aws.amazon.com/deepracer/">https://aws.amazon.com/deepracer/</a></span></p> <p class="footnote"><a href="#fn-adb36258" name="f-adb36258" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Sutton, R. S., &amp; Barto, A. G. (2011). <a class="keyword" href="http://d.hatena.ne.jp/keyword/Reinforcement%20learning">Reinforcement learning</a>: An introduction. Retrieved from <a href="https://web.stanford.edu/class/psych209/Readings/SuttonBartoIPRLBook2ndEd.pdf">https://web.stanford.edu/class/psych209/Readings/SuttonBartoIPRLBook2ndEd.pdf</a></span></p> <p class="footnote"><a href="#fn-27946842" name="f-27946842" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">Schulman, J., Wolski, F., Dhariwal, P., Radford, A., &amp; Klimov, O. (2017). Proximal policy optimization algorithms. <a class="keyword" href="http://d.hatena.ne.jp/keyword/arXiv">arXiv</a> preprint <a class="keyword" href="http://d.hatena.ne.jp/keyword/arXiv">arXiv</a>:1707.06347. Retrieved from <a href="https://arxiv.org/abs/1707.06347">https://arxiv.org/abs/1707.06347</a></span></p> <p class="footnote"><a href="#fn-8b8a4abd" name="f-8b8a4abd" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">Schulman, J., Wolski, F., Dhariwal, P., Radford, A., &amp; Klimov, O. (2017a). Proximal Policy Optimization. Retrieved from <a href="https://openai.com/blog/openai-baselines-ppo/#ppo">https://openai.com/blog/openai-baselines-ppo/#ppo</a></span></p> <p class="footnote"><a href="#fn-a91da2f4" name="f-a91da2f4" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">Lecchi, S. (2009). Artificial intelligence in racing games. Paper presented at the 2009 <a class="keyword" href="http://d.hatena.ne.jp/keyword/IEEE">IEEE</a> Symposium on Computational Intelligence and Games</span></p> <p class="footnote"><a href="#fn-15832a61" name="f-15832a61" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">Xiong, Y. (2010). Racing line optimization. Massachusetts Institute of Technology, </span></p> <p class="footnote"><a href="#fn-ea3390ff" name="f-ea3390ff" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text">Vesel, R. (2015). Racing line optimization@ race optimal. <a class="keyword" href="http://d.hatena.ne.jp/keyword/ACM">ACM</a> SIGEVOlution, 7(2-3), 12-20. </span></p> </div> tanhungp TS+styled-componentsでマージンの取り方を工夫してみた hatenablog://entry/26006613593145699 2020-07-03T16:30:57+09:00 2020-07-03T16:30:57+09:00 はじめに オープンエイトの大津です。 在宅勤務が始まり、かなり日にちが経ちましたね。僕は在宅勤務開始時にPCデスクや椅子がなくコタツ机で仕事を行なっていたのですが、最終的にデスク・椅子ともに買い揃えました。 デスクと椅子がほぼ同時期に届いたため、部屋がダンボール等の荷物で埋め尽くされることになりました! 僕は現在、VBA(VIDEO BRAIN Analytics)のフロントの開発を年明け頃からしています。 このVBAの開発で印象に残っているものとして、マージンに対する取り組みをご紹介したいと思います。 open8.com VBAは、SPA(React + TypeScript)で開発されてい… <h2>はじめに</h2> <p>オープンエイトの大津です。 在宅勤務が始まり、かなり日にちが経ちましたね。僕は在宅勤務開始時にPCデスクや椅子がなくコ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BF%A5%C4">タツ</a>机で仕事を行なっていたのですが、最終的にデスク・椅子ともに買い揃えました。</p> <p>デスクと椅子がほぼ同時期に届いたため、部屋が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C0%A5%F3%A5%DC%A1%BC">ダンボー</a>ル等の荷物で埋め尽くされることになりました!</p> <p>僕は現在、<a class="keyword" href="http://d.hatena.ne.jp/keyword/VBA">VBA</a>(VIDEO BRAIN Analytics)のフロントの開発を年明け頃<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AB%A4%E9%A4%B7">からし</a>ています。 この<a class="keyword" href="http://d.hatena.ne.jp/keyword/VBA">VBA</a>の開発で印象に残っているものとして、マージンに対する取り組みをご紹介したいと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen8.com%2Fnews%2Fpressrelease%2F5457%2F" title="リモートワークを支援するインハウスAI動画編集クラウド「VIDEO BRAIN」 SNS分析・投稿サービス「VIDEO BRAIN Analytics」β版を提供開始コロナ禍において急増するSNSでの企業からの情報発信をサポート | 株式会社オープンエイト" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://open8.com/news/pressrelease/5457/">open8.com</a></cite></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/VBA">VBA</a>は、SPA(React + TypeScript)で開発されていて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>によるスタイリングにはstyled-componentsを利用しています。 今回の話は、styled-componentsを活用してマージンの付け方をルール化したという話になります。そのため、TyepScriptの話はほぼ触れることはないです。すみません。</p> <p>最後にサンプルコードを載せているので、ぜひ最後までご覧ください!!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hodaka08/20200703/20200703160146.png" alt="f:id:hodaka08:20200703160146p:plain" title="f:id:hodaka08:20200703160146p:plain" class="hatena-fotolife" itemprop="image"></span></p> <h2>マージンの取り組み</h2> <p>マージンに対する取り組みで行なったことは、styled-componentsを利用して<code>propsからmargin・paddingを指定できる様にした</code>ことです。</p> <p>styled-componentsについて調べる中で、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>について書かれいるこちらの記事がすごく参考になりました。 この中にある「レイアウトの組み方一例」を自分風に作り上げてみました。</p> <p><a href="https://gist.github.com/kenmori/60bf7b67819061f41ce960617c035955">styled-components&#x306E;&#x4F7F;&#x3044;&#x65B9;(&#x30D1;&#x30C3;&#x3068;&#x308F;&#x304B;&#x308A;&#x3084;&#x3059;&#x304F;&#x3001;&#x8272;&#x3005;&#x306A;&#x30D1;&#x30BF;&#x30FC;&#x30F3;&#x3092;&#x8AAC;&#x660E;&#x3059;&#x308B;&#x3053;&#x3068;&#x3092;&#x76EE;&#x6307;&#x3057;&#x3066;&#x3044;&#x307E;&#x3059;) &middot; GitHub</a></p> <p>簡単にJSXを記述すると次のようになります。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">&lt;</span>LayoutBox margin<span class="synStatement">=</span><span class="synIdentifier">{{</span> <span class="synConstant">top</span>: <span class="synConstant">16</span> <span class="synIdentifier">}}</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span>...<span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/LayoutBox<span class="synStatement">&gt;</span> </pre> <p>LayoutBoxはstyled-componentsで作成したカスタム<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>で、margin・paddingというpropsを受け取るようにしています。 margin・paddingともに <code>{ top?: number, right?: number, bottom?: number, left?: number }</code> の値を想定しており、受け取ったpropsの数値をもとに各方向へmargin・paddingを取る仕組みとなっています。</p> <p>このようにすることで、次のメリットが得られるのではないかと考えています。</p> <ul> <li>JSXの並びをみることでJSX間のマージンが確認することができる</li> <li>buttonやpなど特定のJSXにも同様の<a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>を付与して使用できる</li> <li>拡張の際に意図せずにmargin・paddingが引き継がれない</li> </ul> <h3>JSXの並びをみることでJSX間のマージンが確認することができる</h3> <p>こちらが最初にして、一番大きな理由です。</p> <p>次のようにstyled-componentsで<a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>を付与すると、ファイルの中身が多くなるにつれて作った<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>とJSXが離れてしまいmargin・paddingが分かりづらくなってしまいます。 marign・paddingの確認をするたびに画面をスクロールさせるのは大変ですし、目的の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を見失うことが多くなりそうだと思います。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">const</span> XComponent <span class="synStatement">=</span> styled.div<span class="synConstant">`</span> <span class="synConstant"> padding: 8px;</span> <span class="synConstant"> // any css</span> <span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">const</span> YComponent <span class="synStatement">=</span> styled.div<span class="synConstant">`</span> <span class="synConstant"> padding: 0px 8px;</span> <span class="synConstant"> // any css</span> <span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">const</span> ZComponent <span class="synStatement">=</span> styled.div<span class="synConstant">`</span> <span class="synConstant"> margin-top: 16px;</span> <span class="synConstant"> // any css</span> <span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">&lt;&gt;</span> <span class="synStatement">&lt;</span>XComponent<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span>...<span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/Xcomponent<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>YComponent /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ZComponent<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/<span class="synStatement">&gt;</span> </pre> <p>これらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>をLayoutBoxから拡張して、propsでマージンを取ることでJSXの並びを見ればファイルの中身が多くなろうとも各JSX間で取られているmargin・paddingが分かりやすくなるかと思います。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">const</span> XComponent <span class="synStatement">=</span> styled<span class="synStatement">(</span>LayoutBox<span class="synStatement">)</span><span class="synConstant">`</span> <span class="synConstant"> // any css</span> <span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">const</span> YComponent <span class="synStatement">=</span> styled<span class="synStatement">(</span>LayoutBox<span class="synStatement">)</span><span class="synConstant">`</span> <span class="synConstant"> // any css</span> <span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">const</span> ZComponent <span class="synStatement">=</span> styled<span class="synStatement">(</span>LayoutBox<span class="synStatement">)</span><span class="synConstant">`</span> <span class="synConstant"> // any css</span> <span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">&lt;&gt;</span> <span class="synStatement">&lt;</span>XComponent padding<span class="synStatement">=</span><span class="synIdentifier">{{</span> <span class="synConstant">top</span>: <span class="synConstant">8</span><span class="synStatement">,</span> right: <span class="synConstant">8</span><span class="synStatement">,</span> bottom: <span class="synConstant">8</span><span class="synStatement">,</span> left: <span class="synConstant">8</span> <span class="synIdentifier">}}</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span>...<span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/Xcomponent<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>YComponent padding<span class="synStatement">=</span><span class="synIdentifier">{{</span> left: <span class="synConstant">8</span><span class="synStatement">,</span> right: <span class="synConstant">8</span> <span class="synIdentifier">}}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ZComponent margin<span class="synStatement">=</span><span class="synIdentifier">{{</span> <span class="synConstant">top</span>: <span class="synConstant">8</span> <span class="synIdentifier">}}</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/<span class="synStatement">&gt;</span> </pre> <p>このように各JSX間で取られているmargin・paddingが一目で分かることが僕は好きです。 修正するときは、JSXの並びやmargin・paddingを確認し、その場で数値を変更するだけで良いので楽に対応できると思います。</p> <h3>buttonやpなど特定のJSXにも同様の<a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>付与して使用できる</h3> <p>こちらはstyled-componentsの仕様を活かしたメリットです。 LayoutBoxをそのまま使い続けるとどうしてもネストが深くなりがちなので、buttonなど他のJSXには次のように付与することができます。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">const</span> BaseButton <span class="synStatement">=</span> styled<span class="synStatement">(</span>LayoutBox.withComponent<span class="synStatement">(</span>button<span class="synStatement">))</span><span class="synConstant">`</span> <span class="synConstant"> // any css</span> <span class="synConstant">`</span><span class="synStatement">;</span> <span class="synStatement">&lt;</span>BsaseButton margin<span class="synStatement">=</span><span class="synIdentifier">{{</span> <span class="synConstant">top</span>: <span class="synConstant">8</span><span class="synStatement">,</span> bottom: <span class="synConstant">8</span> <span class="synIdentifier">}}</span> /<span class="synStatement">&gt;</span> </pre> <p>styled-componentsで作成した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>はwithComponentメソッドを利用することで、他のJSXに自身の持つ<a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>を与えることができます。 また、styled-componentsで作成した<a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>は<code>className</code>で付与されるので次のように<code>className</code>をpropsで受け取るようにしていれば自作した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>にも利用できます。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> Props <span class="synStatement">=</span> <span class="synIdentifier">{</span> className?: <span class="synType">string</span><span class="synStatement">;</span> <span class="synIdentifier">}</span> <span class="synStatement">const</span> CustomComponent: React.FC<span class="synStatement">&lt;</span>Props<span class="synStatement">&gt;</span> <span class="synStatement">=</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> className <span class="synIdentifier">}</span><span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>div className<span class="synStatement">=</span><span class="synIdentifier">{</span>className<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span>...<span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/div<span class="synStatement">&gt;</span> <span class="synStatement">)</span> <span class="synIdentifier">}</span> <span class="synStatement">export</span> <span class="synStatement">default</span> LayoutBox.withComponent<span class="synStatement">(</span>CustomComponent<span class="synStatement">);</span> </pre> <p>こちらの記法は便利だと思っていて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/VBA">VBA</a>ではテキストやボタンに関する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>をはじめとしたmargin・paddingが必要であろう<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>によく使われています。</p> <h3>拡張の際に意図せずにmargin・paddingが引き継がれない</h3> <p>styled-componentsの拡張は、記述した<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の内容を引き継げるため、DRYに則ることができます。 この時、意図していない<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>まで引き継いでしまうことは避けたいことだと考えています。</p> <p>皆気をつけるようにしているとは思いますが、この問題は全く起こらない問題ではないかなと思います。誰かが作った<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を拡張し、その<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>に共通項目として新たな<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を追加する人もいるかもしれません。<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を追加した結果、自分が対応する箇所とは別の箇所に意図せず影響を与える可能性があります。</p> <p>数値が指定されているmargin・paddingを継承すると、レイアウトを大きく崩す恐れがあるため、継承を避けられるのは素直に嬉しいです。</p> <h2>便利なレイアウト用メソッド</h2> <p>LayoutBoxのpropsであるmargin・paddingは<code>{ top?: number, right?: number, bottom?: number, left?: number }</code>を受け取ります。 記述する時に <code>{ left: 8, right: 8 }</code> など同じ数値を毎回書くのは面倒だという考えのもと次のようなレイアウト用のメソッドを用意しました。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">export</span> <span class="synStatement">const</span> lo <span class="synStatement">=</span> <span class="synIdentifier">{</span> overall: <span class="synStatement">(</span>num: <span class="synType">number</span><span class="synStatement">)</span>: BoxLayout <span class="synStatement">=&gt;</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> <span class="synConstant">top</span>: num<span class="synStatement">,</span> right: num<span class="synStatement">,</span> bottom: num<span class="synStatement">,</span> left: num <span class="synIdentifier">}</span><span class="synStatement">),</span> horizontal: <span class="synStatement">(</span>num: <span class="synType">number</span><span class="synStatement">)</span>: Partial<span class="synStatement">&lt;</span>BoxLayout<span class="synStatement">&gt;</span> <span class="synStatement">=&gt;</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> right: num<span class="synStatement">,</span> left: num <span class="synIdentifier">}</span><span class="synStatement">),</span> vertical: <span class="synStatement">(</span>num: <span class="synType">number</span><span class="synStatement">)</span>: Partial<span class="synStatement">&lt;</span>BoxLayout<span class="synStatement">&gt;</span> <span class="synStatement">=&gt;</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> <span class="synConstant">top</span>: num<span class="synStatement">,</span> bottom: num <span class="synIdentifier">}</span><span class="synStatement">)</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> </pre> <p>left, rightに同一の値を入れるならば、<code>lo.horizontal(8)</code>のように利用することで目的のオブジェクトを生成できるようにしました。 この辺りは、好みもあると思いますのでチームで相談して利用すればいいのかなと思います。</p> <h2>おわりに</h2> <p>今回は、margin・paddingをprops指定した話をさせていただきました。 このmargin・paddingの指定方法は個人的にかなり気に入っています。ですが、プロジェクトの最初期に作成したものであるため、まだまだ改良したい部分もあったりします。</p> <p>例えば、margin・paddingを別々のpropsとしていたが、同一のpropsから指定できるようにして、margin・paddingを指定するために専用のレイアウト用メソッドを作成するなど考えてみたりはしました。けれど、他の開発者からLayoutBox自体のコードが読みづらくなるかなと思いました。</p> <p>その他、marginを指定しない場合は<code>margin: 0px 0px 0px 0px</code>のように全て0として<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>が付与されてしまっているので、propsで数値を指定しない場合は<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>が付与されないようにしたいですね!</p> <p>このLayoutBoxではTypeScriptの型に関する使いかたもかなり練習できたので良かったです。</p> <h2>付録</h2> <p>本文中では特に触れられませんでしたが、margin・paddingのpropsに<code>{ top?: number, right?: number, bottom?: number, left?: number }</code>の配列を渡しても良いように設計しているため、Object.prototype.toStringを用いた型判定のメソッドを自作しています。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">type</span> LayoutBoxProps <span class="synStatement">=</span> <span class="synIdentifier">{</span> <span class="synConstant">top</span>: <span class="synType">number</span><span class="synStatement">;</span> right: <span class="synType">number</span><span class="synStatement">;</span> bottom: <span class="synType">number</span><span class="synStatement">;</span> left: <span class="synType">number</span><span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synStatement">type</span> Margin <span class="synStatement">=</span> <span class="synIdentifier">{</span> margin?: Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span> | Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span><span class="synIdentifier">[]</span><span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synStatement">type</span> Padding <span class="synStatement">=</span> <span class="synIdentifier">{</span> padding?: Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span> | Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span><span class="synIdentifier">[]</span><span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synStatement">const</span> DEFAULT_VALUE: LayoutBoxProps <span class="synStatement">=</span> <span class="synIdentifier">{</span> <span class="synConstant">top</span>: <span class="synConstant">0</span><span class="synStatement">,</span> right: <span class="synConstant">0</span><span class="synStatement">,</span> bottom: <span class="synConstant">0</span><span class="synStatement">,</span> left: <span class="synConstant">0</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synComment">// オブジェクトであるか判定するメソッド</span> <span class="synIdentifier">function</span> isLayoutBoxObj<span class="synStatement">(</span> layout: Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span> | Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span><span class="synIdentifier">[]</span> <span class="synStatement">)</span>: layout is Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> isObject<span class="synStatement">(</span>layout<span class="synStatement">);</span> <span class="synIdentifier">}</span> <span class="synComment">// 配列であるか判定するメソッド</span> <span class="synIdentifier">function</span> isLayoutBoxArray<span class="synStatement">(</span> layout: Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span> | Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span><span class="synIdentifier">[]</span> <span class="synStatement">)</span>: layout is Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span><span class="synIdentifier">[]</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> isArray<span class="synStatement">(</span>layout<span class="synStatement">);</span> <span class="synIdentifier">}</span> <span class="synIdentifier">function</span> createLayoutBox<span class="synStatement">(</span> layoutBox: Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span> | Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span><span class="synIdentifier">[]</span> | <span class="synType">undefined</span> <span class="synStatement">)</span>: LayoutBoxProps <span class="synIdentifier">{</span> <span class="synStatement">if</span> <span class="synStatement">(</span>!layoutBox<span class="synStatement">)</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> DEFAULT_VALUE<span class="synStatement">;</span> <span class="synIdentifier">}</span> <span class="synStatement">if</span> <span class="synStatement">(</span>isLayoutBoxObj<span class="synStatement">(</span>layoutBox<span class="synStatement">))</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synIdentifier">{</span> ...DEFAULT_VALUE<span class="synStatement">,</span> ...layoutBox <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synIdentifier">}</span> <span class="synStatement">if</span> <span class="synStatement">(</span>isLayoutBoxArray<span class="synStatement">(</span>layoutBox<span class="synStatement">))</span> <span class="synIdentifier">{</span> <span class="synIdentifier">let</span> resultLayout <span class="synStatement">=</span> <span class="synIdentifier">{</span> ...DEFAULT_VALUE <span class="synIdentifier">}</span><span class="synStatement">;</span> layoutBox.forEach<span class="synStatement">((</span>layoutItem: Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> resultLayout <span class="synStatement">=</span> <span class="synIdentifier">{</span> ...resultLayout<span class="synStatement">,</span> ...layoutItem <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">);</span> <span class="synStatement">return</span> resultLayout<span class="synStatement">;</span> <span class="synIdentifier">}</span> <span class="synStatement">return</span> DEFAULT_VALUE<span class="synStatement">;</span> <span class="synIdentifier">}</span> <span class="synStatement">const</span> LayoutBox <span class="synStatement">=</span> styled.div<span class="synStatement">&lt;</span>Margin &amp; Padding<span class="synStatement">&gt;</span><span class="synConstant">`</span> <span class="synConstant"> margin: ${({ margin }): string =&gt; {</span> <span class="synConstant"> const { top, right, bottom, left } = createLayoutBox(margin);</span> <span class="synConstant"> return `</span>$<span class="synIdentifier">{</span><span class="synConstant">top</span><span class="synIdentifier">}</span>px $<span class="synIdentifier">{</span>right<span class="synIdentifier">}</span>px $<span class="synIdentifier">{</span>bottom<span class="synIdentifier">}</span>px $<span class="synIdentifier">{</span>left<span class="synIdentifier">}</span>px<span class="synConstant">`;</span> <span class="synConstant"> }};</span> <span class="synConstant"> padding: ${({ padding }): string =&gt; {</span> <span class="synConstant"> const { top, right, bottom, left } = createLayoutBox(padding);</span> <span class="synConstant"> return `</span>$<span class="synIdentifier">{</span><span class="synConstant">top</span><span class="synIdentifier">}</span>px $<span class="synIdentifier">{</span>right<span class="synIdentifier">}</span>px $<span class="synIdentifier">{</span>bottom<span class="synIdentifier">}</span>px $<span class="synIdentifier">{</span>left<span class="synIdentifier">}</span>px<span class="synConstant">`;</span> <span class="synConstant"> }};</span> <span class="synConstant">`</span><span class="synStatement">;</span> <span class="synComment">//レイアウト用のUtils</span> <span class="synStatement">export</span> <span class="synStatement">const</span> lo <span class="synStatement">=</span> <span class="synIdentifier">{</span> overall: <span class="synStatement">(</span>num: <span class="synType">number</span><span class="synStatement">)</span>: LayoutBoxProps <span class="synStatement">=&gt;</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> <span class="synConstant">top</span>: num<span class="synStatement">,</span> right: num<span class="synStatement">,</span> bottom: num<span class="synStatement">,</span> left: num <span class="synIdentifier">}</span><span class="synStatement">),</span> horizontal: <span class="synStatement">(</span>num: <span class="synType">number</span><span class="synStatement">)</span>: Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span> <span class="synStatement">=&gt;</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> right: num<span class="synStatement">,</span> left: num <span class="synIdentifier">}</span><span class="synStatement">),</span> vertical: <span class="synStatement">(</span>num: <span class="synType">number</span><span class="synStatement">)</span>: Partial<span class="synStatement">&lt;</span>LayoutBoxProps<span class="synStatement">&gt;</span> <span class="synStatement">=&gt;</span> <span class="synStatement">(</span><span class="synIdentifier">{</span> <span class="synConstant">top</span>: num<span class="synStatement">,</span> bottom: num <span class="synIdentifier">}</span><span class="synStatement">)</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synStatement">export</span> <span class="synStatement">default</span> LayoutBox<span class="synStatement">;</span> </pre> hodaka08 フロントエンド開発で学んだ命名の重要性 hatenablog://entry/26006613589382308 2020-06-30T18:16:50+09:00 2020-07-23T12:10:06+09:00 VIDEO BRAINのフロントエンドを担当している松井です。入社して早8ヶ月が経ち、毎月進化していくスピード感にようやく慣れつつあります。前回はVIDEO BRAINのフロントエンドの全体アーキテクチャについて書きましたが、今回はもっとコードレベルの事を書こうと思います。 言わずと知れた名著リーダブルコード。 この本はコードは他の人が最短時間で理解できるように書くという考えの基に具体的なTipsを色々と紹介しています。 今回はこの考えの重要性をVIDEO BRAINの開発を通じて体験できたので、実例を交えながら書いていこうと思います。 コードは他の人が最短時間で理解できるように書く よく言わ… <p>VIDEO BRAINのフロントエンドを担当している松井です。入社して早8ヶ月が経ち、毎月進化していくスピード感にようやく慣れつつあります。<a href="https://open8tech.hatenablog.com/entry/2019/12/26/111353">前回</a>はVIDEO BRAINのフロントエンドの全体<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>について書きましたが、今回はもっとコードレベルの事を書こうと思います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20200624/20200624200531.png" alt="f:id:open8tech:20200624200531p:plain" title="f:id:open8tech:20200624200531p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>言わずと知れた名著リーダブルコード。</p> <p>この本は<code>コードは他の人が最短時間で理解できるように書く</code>という考えの基に具体的なTipsを色々と紹介しています。</p> <p>今回はこの考えの重要性をVIDEO BRAINの開発を通じて体験できたので、実例を交えながら書いていこうと思います。</p> <h5>コードは他の人が最短時間で理解できるように書く</h5> <p>よく言われている事ですが、開発時は書く時間より読む時間の方が長いです。</p> <p>今回のテーマで海外記事も読みましたが、このフレーズを頻繁に目にしました。</p> <blockquote><p>Write readable code because it’s read more than it’s written.</p></blockquote> <p>複雑な処理などをしばらく経ってから読み返すと自分で書いたコードでも把握するのに時間がかかる事がよくあります。</p> <p>プログラミングでありがちな<code>数週間前の自分は他人</code>現象です。</p> <p>その原因の多くは<span style="color: #dd830c">関数が何をしてるのか?</span>、<span style="color: #dd830c">変数に入ってる値は何なのか?</span>を理解するのに時間がかかる事だと思います。だからこそ適切な名前をつけてラベリングしておけば、どこに何が入ってるのか、何をしてるのかが追いやすくなり、ラベルを見るだけで大体のイメージを掴む事ができます。</p> <p>それでは開発中に体験した悪い例、いい例を紹介していこうと思います。</p> <h5>悪い例1:スコープの大きい値に抽象的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a></h5> <p>子Componentに渡す様なスコープの大きい値に<code>value</code>という抽象的な名前で渡してしまっていた。</p> <p>その<a class="keyword" href="http://d.hatena.ne.jp/keyword/value">value</a>の値が具体的に何なのか?は開発中は分かっていても、後から自分がそのコードを読んだ時に、「何を入れたっけ?」となり、わざわざ親Componentで確認しないとイメージがつかめなかった。書いた本人でもそうなのだから他のチームメンバーも同じ思いをしたはずです。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/value">value</a>のような抽象的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>はスコープの小さいものや、ローカル変数など必要な情報が近くにある時に留めるべきで、<span style="color: #dd830c">Propsの様な、ファイルをまたいで渡される値には必ず具体的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>をすべき</span>だと反省しました。</p> <h5>悪い例2:誤解を招いてしまう<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a></h5> <pre class="code lang-javascript" data-lang="javascript" data-unlink>isInvalid = <span class="synConstant">true</span>/<span class="synConstant">false</span> </pre> <p>上記の様なBooleanの変数を作って、<code>isInvalid</code>の場合にアラートを表示するという使い方をして問題なく動作していたのですが、チームメンバーから<code>無効</code>という名前なのに<code>中身がtrue</code>だと不自然で、誤解を生みやすいと指摘された事がありました。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>isValid = <span class="synConstant">true</span>/<span class="synConstant">false</span> </pre> <p>にして <code>!isValid</code>の場合にアラートを出すという処理の方が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%C1%B3%B8%C0%B8%EC">自然言語</a>的でいいという事でした。</p> <p><span style="color: #dd830c">変数名</span>だけではなく、<span style="color: #dd830c">その中に入れる値</span>、<span style="color: #dd830c">その使い方</span>も考慮した上でコードを書かないといけないという学びになりました。</p> <h5>いい例1:UIがイメージできる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a></h5> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>は変数や関数だけではありません。</p> <p>VIDEO BRAINはstylingにstyled-componentsを導入していて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>ができます。</p> <p>以下のstyled-componentsを見ると、これがどんなUIなのかイメージがつくと思います。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>&lt;SeekBar&gt; &lt;SeekBarPlay&gt; <span class="synIdentifier">{</span>isPlaying ? &lt;PlayerPause /&gt; : &lt;PlayerPlay /&gt;<span class="synIdentifier">}</span> &lt;/SeekBarPlay&gt; &lt;SeekBarLine&gt; &lt;SeekBarLineProgress /&gt; &lt;SeekBarLineCircle /&gt; &lt;SeekBarLinePointingProgress /&gt; &lt;/SeekBarLine&gt; &lt;SeekBarLinePointingTime /&gt; &lt;SeekBarSecond&gt; &lt;SeekBarSecondCurrent&gt;&lt;/SeekBarSecondCurrent&gt; &lt;SeekBarSecondDuration&gt;&lt;/SeekBarSecondDuration&gt; &lt;/SeekBarSecond&gt; &lt;SeekBarSound&gt; <span class="synIdentifier">{</span>isSoundOn ? &lt;SoundOn /&gt; : &lt;SoundOff /&gt;<span class="synIdentifier">}</span> &lt;/SeekBarSound&gt; &lt;/SeekBar&gt; </pre> <p>正解は動画のプレビュー画面の下にある再生ボタンやシークバーなどのUIです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20200624/20200624200600.png" alt="f:id:open8tech:20200624200600p:plain" title="f:id:open8tech:20200624200600p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>構成もいいですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>が簡潔で一貫性があり、これを見ただけでどんなUIかイメージが湧きました。逆にこれをいい加減にしてしまうとどれが何のUIなのかいちいちビルドして確認しないといけなくなります。</p> <p>こういう細かい気遣いが、他のチームメンバー、もしくは数週間後の自分が読むストレスを軽減するので、とても大事だと思いました。</p> <h5>いい例2:</h5> <pre class="code lang-javascript" data-lang="javascript" data-unlink>durationToTimeFormat(duration) </pre> <p>この関数名を見た時に秒数を時計表示に変えていると一目で予想がつきました。 VIDEO BRAINのユーザーが作る動画の再生時間はバックエンド側では全て秒数で管理されていて、それを時計(00:00:00)表示に変換しているといういい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>だと思いました。</p> <p>参考までに、durationToTimeFormatは実際こんな処理をしてます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>durationToTimeFormat = (time: number) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> h = (<span class="synConstant">'00'</span> + parseInt((parseInt(time) % 86400) / 1440)).slice(-2); <span class="synStatement">const</span> m = (<span class="synConstant">'00'</span> + parseInt((parseInt(time) % 3600) / 60)).slice(-2); <span class="synStatement">const</span> s = (<span class="synConstant">'00'</span> + parseInt(parseInt(time) % 60)).slice(-2); <span class="synStatement">return</span> `$<span class="synIdentifier">{</span>h<span class="synIdentifier">}</span>:$<span class="synIdentifier">{</span>m<span class="synIdentifier">}</span>:$<span class="synIdentifier">{</span>s<span class="synIdentifier">}</span>`; <span class="synIdentifier">}</span>; </pre> <p>個人的にこの関数名が、<code>durationToTime()</code>だったらそこまでピンとこなかったかもしれません。durationもTimeも同じ意味といえば同じなので、どんな変換をしたんだ?と思ってしまったかもしれません。</p> <p><code>TimeFormat</code>とした事で時刻フォーマットに変換したんだなというイメージが湧きました。</p> <h5>いい例3:</h5> <pre class="code lang-javascript" data-lang="javascript" data-unlink>checkHasAudioTrack(video) </pre> <p>読んで字のごとく、動画に音があるのかどうかチェックする関数です。</p> <p>単純に<code>checkAudioTrack()</code>だったらAudioTrackの何をチェックしてるか分かりませんが、<code>has</code>とつくとAudioTrackの<code>有無</code>をチェックしてると予想がつきます。</p> <h5>いい例4:</h5> <p>ページUIを改修した時の例です。 もともと最大5ページを表示していたのですが、新たに最大9ページ表示に変更してほしいという指示でした。</p> <p>表示は大きく分けて10ページ未満か以上かの2パターン</p> <p>10ページ未満の場合は全ページを表示(最大9ページ) <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsuihopen8/20200723/20200723120004.png" alt="f:id:matsuihopen8:20200723120004p:plain" title="f:id:matsuihopen8:20200723120004p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>10ページ以上の場合、以下3種類の表示条件で分ける</p> <p>・最初の5ページは9ページ固定で表示 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsuihopen8/20200723/20200723120021.png" alt="f:id:matsuihopen8:20200723120021p:plain" title="f:id:matsuihopen8:20200723120021p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>・6ページ目以降は現在のページを中心に前後に4ページ、合計9ページを表示 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsuihopen8/20200723/20200723120032.png" alt="f:id:matsuihopen8:20200723120032p:plain" title="f:id:matsuihopen8:20200723120032p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>・最後の5ページは9ページ固定で表示 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/matsuihopen8/20200723/20200723120041.png" alt="f:id:matsuihopen8:20200723120041p:plain" title="f:id:matsuihopen8:20200723120041p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>その条件式を自分は最初にこのように書き、PRを出しました。 pageNum は合計のページ数 page は現在のページ pages という配列に表示するページの番号をPushするという処理です。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> pages = <span class="synIdentifier">[]</span>; <span class="synStatement">if</span> (pageNum &lt; 10) <span class="synIdentifier">{</span> <span class="synStatement">for</span> (<span class="synIdentifier">let</span> i = 1; i &lt;= pageNum; i++) <span class="synIdentifier">{</span> pages.push(i); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synIdentifier">{</span> <span class="synStatement">if</span> (page &lt; 6) <span class="synIdentifier">{</span> <span class="synStatement">for</span> (<span class="synIdentifier">let</span> i = 1; i &lt;= Math.min(9, pageNum); i++) <span class="synIdentifier">{</span> pages.push(i); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synStatement">if</span> (pageNum - page &lt; 5) <span class="synIdentifier">{</span> <span class="synStatement">for</span> (<span class="synIdentifier">let</span> i = pageNum - 8; i &lt;= pageNum; i++) <span class="synIdentifier">{</span> pages.push(i); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synIdentifier">{</span> <span class="synStatement">for</span> (<span class="synIdentifier">let</span> i = Math.max(page - 4, 1); i &lt;= Math.min(page + 4, pageNum); i++) <span class="synIdentifier">{</span> pages.push(i); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>Reviewerから条件式内の<code>数字に何の意味があるのか</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>してくれないとわからない。 配列にPushする処理は共<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C4%CC%B2%BD">通化</a>した方がいいという指摘を受けて、以下のように修正しました。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> previous4Pages = page - 4; <span class="synStatement">const</span> next4Pages = page + 4; <span class="synStatement">const</span> last8Pages = pageNum - 8; <span class="synStatement">const</span> showPageMax = 9; <span class="synStatement">const</span> isFirst5Pages = page &lt; 6; <span class="synStatement">const</span> isLast5Pages = pageNum - page &lt; 5; <span class="synIdentifier">let</span> iterateStartNum = Math.max(previous4Pages, 1); <span class="synIdentifier">let</span> iterateLimitNum = Math.min(next4Pages, pageNum); <span class="synStatement">if</span> (isFirst5Pages || pageNum &lt; 10) <span class="synIdentifier">{</span> iterateStartNum = 1; iterateLimitNum = Math.min(showPageMax, pageNum); <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synStatement">if</span> (isLast5Pages) <span class="synIdentifier">{</span> iterateStartNum = last8Pages; iterateLimitNum = pageNum; <span class="synIdentifier">}</span> <span class="synStatement">for</span> (<span class="synIdentifier">let</span> i = iterateStartNum; i &lt;= iterateLimitNum; i++) <span class="synIdentifier">{</span> pages.push(i); <span class="synIdentifier">}</span> </pre> <p>条件式に使う各数字に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>して意味を持たせました。さらにループして配列へのPush処理を共<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C4%CC%B2%BD">通化</a>させた事で可読性が増し、Reviewの負担が減ったと思いますし、自分自身も後から読んだ時に困る事がなくなりました。</p> <p>いざ自分が変数、関数に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>するとなると簡単にできる時もあれば、なかなかいい言葉のチョイスが思いつかない時もあって大変ですが、これらのいい例を参考に、多少長くなっても読み手にとってストレスのない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>をする事が大事だと思いました。</p> <h5>コードの質の向上→生産性の向上→事業の成長</h5> <p>ソフトウェア開発において、一度作ったものをそのまま何ヶ月も使い続けることはありえない事で、ましてやVIDEO BRAINは毎月進化を遂げています。</p> <p>「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>」は処理を書くロジカルな作業とは異なるクリエイティブな作業とも言えますが、コードの質を上げる為に疎かにできない作業ですし、この作業に少し余計に時間を使う価値はあると言われています。</p> <p>コードの質が上がれば生産性が上がり、生産性が上がれば事業の成長スピードにも影響を与える。</p> <p>競合サービスを出し抜いていくためには、開発時のこういう地道で小さな心使いの積み重ねが大きな差を生んでいくかもしれません。</p> open8tech 弊社の現在位置とカイゼン活動 hatenablog://entry/26006613573571928 2020-06-02T14:56:25+09:00 2020-06-02T14:56:25+09:00 ブログへの登場は初めてとなります、VP of Engineering(以下VPoE)の古萱(@emfurupon777)です。 2020年2月にオープンエイトに参画し、VPoEとして活動させていただいております。 COVID-19環境下において、これまで盛んに行われてきたエンジニアの交流イベントが一旦なくってきたものの、 オンライン開催のイベントも徐々に増えつつあり、次にはどんな世界が待っているのかと不安な中にもワクワクも感じ始めている今日この頃です。 今回は改めて弊社の置かれている状況を改めて確認しておくべく、筆をとり・・・もといキーボードをうっています。 弊社の現在位置について 弊社略歴 … <p>ブログへの登場は初めてとなります、VP of Engineering(以下VPoE)の古萱(<a href="https://twitter.com/emfurupon777">@emfurupon777</a>)です。 2020年2月にオープンエイトに参画し、VPoEとして活動させていただいております。</p> <p>COVID-19環境下において、これまで盛んに行われてきたエンジニアの交流イベントが一旦なくってきたものの、 オンライン開催のイベントも徐々に増えつつあり、次にはどんな世界が待っているのかと不安な中にもワクワクも感じ始めている今日この頃です。</p> <p>今回は改めて弊社の置かれている状況を改めて確認しておくべく、筆をとり・・・もといキーボードをうっています。</p> <h2>弊社の現在位置について</h2> <h3>弊社略歴</h3> <p>創業期からCEO高松を中心に大人な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC">ベンチャー</a>(!)として、Ad Platformや<a href="https://letronc-m.com/">女性向けメディアLeTRONC</a>などを運営するとともに、CTOの石橋(<a href="https://twitter.com/hisatake">@hisatake</a>)を迎えながら、一部属人的になることも覚悟しつつ様々な開発を行ってきました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fletronc-m.com%2F" title="おでかけ動画メディア | ルトロン" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://letronc-m.com/">letronc-m.com</a></cite></p> <p>これらのサービス提供で磨いてきたAIエンジンなどを中心に、AI動画編集<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a><a href="https://video-b.com/">VIDEO BRAIN(https://video-b.com/)</a>や<a class="keyword" href="http://d.hatena.ne.jp/keyword/SNS">SNS</a>分析・投稿サービス「VIDEO BRAIN Analytics」β版などを提供し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/PMF">PMF</a>(Product Market Fit)を確認し、さらなる成長に挑むフェーズにまで至っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvideo-b.com%2F" title="【公式】VIDEO BRAIN | AI 自動動画編集ツール" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://video-b.com/">video-b.com</a></cite></p> <p>技術的観点では、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a>で実現されていたAd PlatformからReact/ReduxによるSPA、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>などからなるVIDEO BRAIN、というようにプロダクトに合わせたものを選定しながら開発しています。</p> <h3>今どのフェーズにいるのか</h3> <p>色々な分類があるとは思いますが、直近では、Findyの石川さん(<a href="https://twitter.com/HRBizDev1">@HRBizDev1</a>)がまとめられているこの記事が非常にわかりみが深いなと感じています。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.findy.us%2Fengineer-organization-interview%2F" title="【CTO・エンジニアマネージャーに聞いた】企業成長フェーズ5段階別に発生するエンジニア組織の課題と取り組み事例まとめ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://blog.findy.us/engineer-organization-interview/">blog.findy.us</a></cite></p> <p>このフェーズわけによれば、弊社オープンエイトは急成長期から安定成長期に入ろうとしているところに位置しています。 実際、直近のエンジニアリング組織の動きとして、エンジニアが楽しみながら<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A5%EB%A5%A2%A5%C3%A5%D7">スキルアップ</a>し、事業に貢献できる組織への飛躍を期して様々な取り組みに取り掛かっています。</p> <ul> <li>プロジェクト・チームでの振り返り(<a class="keyword" href="http://d.hatena.ne.jp/keyword/KPT">KPT</a>)実施</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%AB%C8%AF%A5%D7%A5%ED%A5%BB%A5%B9">開発プロセス</a>の効率化</li> <li>ドキュメントの充実</li> <li>1on1や目標設定などを洗練させることによる成長サポート強化</li> <li>社内LT会のオンライン開催トライアル(本来は寿司でもつまみながらやりたいところなのですが、、(〃▽〃))</li> <li>本テックブログの再始動</li> <li>etc.</li> </ul> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A5%A4%A5%BC%A5%F3">カイゼン</a>について</h2> <h3>AS IS</h3> <p>オープンエイトで様々な取り組みを開始してみて実感するのは、やはりいずれもトライアル&エラーの繰り返しになるということです。 良書として認識されているような書籍や、他社の成功事例をそのままなぞっただけでは定着せず、 やはり自社としてのアレンジというか、フレーバーをつけるというか、は必要となってきます。</p> <p>どのような状況下においても、より良いエンジニアリング・企業活動を志向していく以上、<b><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A5%A4%A5%BC%A5%F3">カイゼン</a></b>に対する意識は必要だと考えています。弊社でも様々な取り組みを続けてきていますが、正直これまではエンジニアの個の力によって切り拓いてきたという状況で、継続できずに停止状態になってしまった施策も多々・・・という状況でした。</p> <h3>TO BE</h3> <p>そんな中、<b>現在欠かさずやろうと呼びかけているのは「振り返り」</b>です。</p> <p>なぜなら、様々な活動をするのにあたって、あるべき姿(ありたい姿)と自らのギャップを測ること、見立てたギャップの”確<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AB%A4%E9%A4%B7">からし</a>さ”を実際に行動した結果を持って確認し、実現の確度をあげることが非常に重要だからです。</p> <p>さらに言えば、振り返りをする過程で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B8%AB%A4%A8%A4%EB%B2%BD">見える化</a>なども実現したくなってきます。 この欲求を掘り起こしてくれるという意味でも振り返りに効果を期待し、しいてはいわゆる<a class="keyword" href="http://d.hatena.ne.jp/keyword/PDCA%A5%B5%A5%A4%A5%AF%A5%EB">PDCAサイクル</a>が円滑に回っていくところまでつなげていきたい・・というのが狙いです</p> <p>正直なところ、HOWでいけばもちろん色々とやりたいことはあるわけですが、「やりたいこといっぱいあるな~・・・」と某CMのように言っていても前に進むことはできません。まずはやってみる、やりつづけてみるという状況にあり、正直まだまだこれからというステージにいます。</p> <p>このブログで弊社エンジニアリング組織の成長や開発面の取り組みについて、うまくいった取り組み、うまくいかなかった取り組み含めてご報告していきます!</p> <h3>参考書籍について</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AB%A5%A4%A5%BC%A5%F3">カイゼン</a>という文脈でいけば、こちらの書籍が非常にわかりやすく社内でも推薦しています。ストーリー仕立てになっているため、段階をおって知見を深めていくことができますし、経験者が多く無い環境下での<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>導入にありがちな「いきなりやりすぎ問題」を避ける参考にもなります。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4798153346/hatena-blog-22/"><img src="https://m.media-amazon.com/images/I/413zYBVOo2L._SL160_.jpg" class="hatena-asin-detail-image" alt="カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで" title="カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/4798153346/hatena-blog-22/">カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%BB%D4%C3%AB%20%C1%EF%B7%BC" class="keyword">市谷 聡啓</a>,<a href="http://d.hatena.ne.jp/keyword/%BF%B7%B0%E6%20%B9%E4" class="keyword">新井 剛</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2018/02/07</li><li><span class="hatena-asin-detail-label">メディア:</span> 単行本(ソフトカバー)</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <h2>オープンエイト補足情報</h2> <h3>行動指針</h3> <p>弊社では以下の3つを行動指針として掲げています。</p> <ul> <li>Keep Moving</li> <li>Break the Border</li> <li>Play for Team</li> </ul> <p>いずれもエンジニアの行動としてはごく自然にやっている・・・気になってしまうのですが、実はこれらを徹底させるのは難しいです。 会社の仲間たちとの相互作用の中でこれらを継続的に意識し続けることでのみ、これらを体現できる組織を作り上げていけるものと考えています。</p> <h3>We Are Hiring!</h3> <p>弊社オープンエイトでは、サーバーサイド、フロントエンド、エンジニア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%F3%A5%B0%A5%DE">リングマ</a>ネージャー、SREとあらゆるポジションで一緒に働く仲間を募集しています。</p> <p>経験ある経営者の下で安定した事業運営、ユニークなプロダクトの開発に興味ある方はぜひぜひご応募ください! <a href="https://www.wantedly.com/companies/open8">https://www.wantedly.com/companies/open8</a><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/open8">www.wantedly.com</a></cite></p> open8tech algoliasearch-railsを用いた検索機能の実装について hatenablog://entry/26006613504649145 2020-01-30T15:08:25+09:00 2020-01-30T15:08:25+09:00 こんにちは、オープンエイトのエンジニアの中野です! 今月の1/15にメドピア株式会社さんとエンジニアイベント「あなたの知ってるRubyGemsTips」を共同開催しました。 今回はRubyのGemに関するTipsついて、いろいろと情報を交換するというテーマでLTを行いました。主催枠として、私もLTを行ったので、簡単に発表内容を紹介します。 イベントの概要は以下のURLから確認出来ます。 connpass.com 発表内容 LTのテーマは「algoliasearch-railsを用いて検索機能を実装してみた」 登壇資料は以下になります。 speakerdeck.com Algoliaとは Alg… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20200130/20200130140625.png" alt="f:id:open8tech:20200130140625p:plain" title="f:id:open8tech:20200130140625p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、オープンエイトのエンジニアの中野です!<br /> 今月の1/15にメドピア株式会社さんとエンジニアイベント「あなたの知ってるRubyGemsTips」を共同開催しました。 今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のGemに関するTipsついて、いろいろと情報を交換するというテーマでLTを行いました。主催枠として、私もLTを行ったので、簡単に発表内容を紹介します。</p> <p>イベントの概要は以下のURLから確認出来ます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fconnpass.com%2Fevent%2F159601%2F" title="【増枠!】あなたの知ってるRubyGemsTips (2020/01/15 19:30〜)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://connpass.com/event/159601/">connpass.com</a></cite></p> <h2>発表内容</h2> <p>LTのテーマは「algoliasearch-<a class="keyword" href="http://d.hatena.ne.jp/keyword/rails">rails</a>を用いて検索機能を実装してみた」 登壇資料は以下になります。</p> <p><iframe id="talk_frame_592347" src="//speakerdeck.com/player/a9d6259f174845358477c051da4e53a1" width="710" height="399" style="border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/tsubasa1122/algoliasearch-railswoyong-itejian-suo-ji-neng-woshi-zhuang-sitemita">speakerdeck.com</a></cite></p> <h3>Algoliaとは</h3> <p>Algoliaはモバイルアプリや<a class="keyword" href="http://d.hatena.ne.jp/keyword/Web%A5%B5%A1%BC%A5%D3%A5%B9">Webサービス</a>に導入することで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%B4%CA%B8%B8%A1%BA%F7%A5%A8%A5%F3%A5%B8%A5%F3">全文検索エンジン</a>が利用できる<a class="keyword" href="http://d.hatena.ne.jp/keyword/SaaS">SaaS</a>です。2019年5月には日本法人を設立されて<a class="keyword" href="http://d.hatena.ne.jp/keyword/Cookpad">Cookpad</a>などが導入していたり、急成長中のサービスになります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.algolia.com%2F" title="Modern site search for companies of all sizes" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.algolia.com/">www.algolia.com</a></cite></p> <p>Algoliaの良さとして、下記のような部分が大きく上がっています。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/GUI">GUI</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>のどちらでも検索ロジックを柔軟に設定出来る。</li> <li>様々な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>向けに<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を提供している。</li> <li>高速な検索レスポンスを提供してくれる。 <br /> →React.jsのドキュメントにも使用されているため、参考にリンクを載せておきます。</li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Freactjs.org%2Fdocs%2Fgetting-started.html" title="Getting Started – React" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://reactjs.org/docs/getting-started.html">reactjs.org</a></cite></p> <h3>Algoliaを触ったきっかけ</h3> <p>弊社では<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%B4%CA%B8%B8%A1%BA%F7">全文検索</a>にElasticsearchを使用しているのですが、運用にいくつか辛い部分があったため、Algoliaを試してみることにしました。 Algoliaは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>で作られたアプリケーション用に<code>algoliasearch-rails</code>というgemを提供してくれています。 ドキュメントが丁寧に書かれているため、こちらを見れば簡単に導入することが出来ます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Falgolia%2Falgoliasearch-rails" title="algolia/algoliasearch-rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/algolia/algoliasearch-rails">github.com</a></cite></p> <h3>触ってみて良かった所</h3> <ul> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/GUI">GUI</a>上で検索結果を確認出来る。<br /> 画像のようにサーチバーに文字列を入力すると検索結果を確認することが出来、検索クエリに対するレスポンスデータや文字のマッチ率なども確認することが出来ます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20200130/20200130133627.png" alt="f:id:open8tech:20200130133627p:plain" title="f:id:open8tech:20200130133627p:plain" class="hatena-fotolife" itemprop="image"></span></p></li> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/GUI">GUI</a>上で簡単に検索をカスタマイズ出来る。<br /> 画像の画面でカラムの重み付けや表記ゆれの修正も簡単に出来たりします。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20200130/20200130133659.png" alt="f:id:open8tech:20200130133659p:plain" title="f:id:open8tech:20200130133659p:plain" class="hatena-fotolife" itemprop="image"></span></p></li> </ul> <h3>まとめ</h3> <p>検索体験向上にAlgoliaを使用してみるのはありだと思いました。検索機能の運用に悩んでいる方はぜひ一度Algoliaを触ってみて欲しいです。 また、今回初めて外部勉強会でLTをしてみて、外部の方々を交えてアウトプットを出す楽しさも経験出来たため、今後も定期的にLTに挑戦していきます。</p> open8tech VIDEO BRAINのRedux設計 hatenablog://entry/26006613486282196 2019-12-26T11:13:53+09:00 2019-12-26T11:13:53+09:00 はじめまして、10月にフロントエンドエンジニアとして入社した松井と申します。 最近通勤時間にAmazon Audibleで読書ならぬ聴書をする様になりインプット量が大幅に増えました。歩きながらでも本が読める(聞ける)のでお勧めです。 さて弊社の事業の一つにVIDEO BRAINというAI自動動画編集クラウドがあります。 video-b.com フロントエンドはReact+ReduxのSPAで開発されており、私はOPEN8入社前はNuxt.jsを使っていて、Reactはこれが初でした。Nuxt.jsと共通点も多いですが、異なる点も多く、特にディレクトリ構成がいまいち理解できていませんでした。 し… <p>はじめまして、10月にフロントエンドエンジニアとして入社した松井と申します。 最近通勤時間に<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a> Audibleで読書ならぬ聴書をする様になりインプット量が大幅に増えました。歩きながらでも本が読める(聞ける)のでお勧めです。</p> <p>さて弊社の事業の一つにVIDEO BRAINというAI自動動画編集<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>があります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvideo-b.com%2F" title="【公式】VIDEO BRAIN | AI 自動動画編集ツール" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://video-b.com/">video-b.com</a></cite></p> <p>フロントエンドはReact+ReduxのSPAで開発されており、私はOPEN8入社前はNuxt.jsを使っていて、Reactはこれが初でした。Nuxt.jsと共通点も多いですが、異なる点も多く、特に<strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成</strong>がいまいち理解できていませんでした。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20191221/20191221130754.jpg" alt="f:id:open8tech:20191221130754j:plain" title="f:id:open8tech:20191221130754j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>しかし、先日フォント一括変更機能を実装した時に、主要となる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リのComponent, Container, Moduleのファイルをゼロから作成、連携させたおかげでその理解が一気に深まりました。</p> <p>今回はその振り返りも兼ねて記事にまとめようと思います。</p> <h2>はじめに</h2> <p>一般的に<a href="https://redux.js.org/">Redux</a>を用いたアプリケーションの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成には、<code>Redux-Way</code> <code>Ducks</code> <code>Re-ducks</code>の3種類がありますので、まずはそれぞれを簡単に解説したいと思います。</p> <h3>1.Redux-way</h3> <p>Reduxの登場人物(Components, Containers, Actions, Reducers, Types)ごとに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを分け、その中ではComponentごとにファイルを分類するパターン。</p> <pre class="code" data-lang="" data-unlink>src/ ├ components/ | └ componentName.js ├ containers/ | └ componentName.js ├ actions/ | └ componentName.js ├ reducers/ | └ componentName.js └ types/ └ componentName.js</pre> <p>このパターンの欠点はStateを書き換える為に必要なActionType、Actions、Reducerが分散されていて、それぞれを呼ぶ為にimportしないといけなかったので、閲覧性が悪く管理がしにくいという点です。</p> <p>その悩みを解決する為に生まれたのがDucksパターン。</p> <h3>2.Ducks</h3> <p>ActionType、Actions、Reducerは密結合してるもの同士なので、1つのファイルで構成し、Moduleとして定義するパターン。</p> <p>これにより簡潔で見通しが良くなります。<a href="https://github.com/erikras">Erik Rasmussen氏</a>が考案しました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20191221/20191221131806.jpg" alt="f:id:open8tech:20191221131806j:plain" title="f:id:open8tech:20191221131806j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>小規模開発においてはDucksパターンのシンプルな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成がマッチすると思われますが、中・大規模開発の場合ActionType、Actions、Reducerを1つのファイルにまとめると、ファイルが肥大していき、閲覧性が悪くなってメンテナンスもしにくくなります。そこで生まれたのがRe-ducksパターン。</p> <h3>3.Re-ducks</h3> <p>DucksパターンにおけるActionType、Actions、Reducerを<code>1つのファイル</code>にまとめるのではなく、Component単位の<code>1つのフォルダ</code>にまとめるというパターン。 <a href="https://github.com/alexnm">Alex Moldovan氏</a>がDucksの拡張版として考案しました。</p> <pre class="code" data-lang="" data-unlink>src/ ├ components/ | └ componentName.js ├ containers/ | └ componentName.js └ duck/ └ componentName/ ├ index.js ├ recucers.js ├ actions.js ├ types.js ├ tests.js ├ operations.js └ selectors.js</pre> <h2>VIDEO BRAINで採用している<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成</h2> <p>さて、肝心のVIDEO BRAINではどのパターンを採用してるかと言いますと、DucksとRe-ducksそれぞれのいい部分を組み合わせています。</p> <ul> <li>ActionType、Actions、Reducerを1つのファイルにまとめModuleとして定義する<code>Ducksパターン</code></li> <li>Component単位の1フォルダにまとめ、Operationsという<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ内に<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>通信の処理を切り出す<code>Re-ducksパターン</code></li> </ul> <pre class="code" data-lang="" data-unlink>src/ ├ components/ | └ componentName/ | └ index.js ├ containers/ | └ componentNameContainer/ | └ index.js └ modules/ └ componentName/ ├ index.js └ operations/ └ index.js</pre> <p>この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成のおかげで、処理は分散され過ぎず、まとまり過ぎず良いバランスを維持できていると思いました。</p> <p>今回、新機能実装の際に、一番最初にした作業は、<code>このディレクトリ構成の理解</code>で、それから詳細な実装プランを立てコードを書いていきました。</p> <p>その処理の流れを図にまとめるとこんな感じです。(*<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを含めるパターンです。)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20191224/20191224110717.png" alt="f:id:open8tech:20191224110717p:plain" title="f:id:open8tech:20191224110717p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>たかだか<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成と思いますが、Re-ducks考案者のAlex Moldovan氏は自身の記事でその重要性を語っています。</p> <blockquote><p>Someone once said that naming things is one of the hardest jobs in computer science. I couldn’t agree more. But structuring folders and organizing files is a close second.</p></blockquote> <p>訳)<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE%B5%AC%C2%A7">命名規則</a>がコンピューターサイエンスで最も大変な仕事の一つだと昔誰かが言っていました。 全くもって賛成ですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リの構成やファイルの整理もそれに匹敵すると思います。</p> <p>引用元:<a href="https://www.freecodecamp.org/news/scaling-your-redux-app-with-ducks-6115955638be/">Scaling your Redux App with ducks</a> Alex Moldovan</p> <p>中・大規模開発では膨大な量の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リとファイルが存在していて、それを<code>どう構成するか</code>が管理、拡張のしやすさを向上する為に重要だし、<code>その構成の意図を理解する事</code>はコードを書く事と同じくらい重要だと思います。</p> <p>アプリケーションは最初にリリースした時から日々拡張をしていくものです。それを容易に安全にできるような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成で設計する事の重要性をAlex Moldovan氏も繰り返し語っていました。</p> <h2>なぜDuck(ア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%EB">ヒル</a>)なのか??</h2> <p>最後に余談ですが、なぜDuck(ア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%EB">ヒル</a>)と呼ぶのか?と疑問に思われた方も多いと思います。 あくまでも個人的な見解ですが、英語で<strong>get one’s ducks in a row(整理する、準備を整える)</strong>という表現があります。 ア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%EB">ヒル</a>の子供達は親の後ろを綺麗に一列になってついていく習性が語源なのですが、これが由来かなと思います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20191221/20191221131844.jpg" alt="f:id:open8tech:20191221131844j:plain" title="f:id:open8tech:20191221131844j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>React+Reduxの膨大なファイル群をア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%EB">ヒル</a>の子供達の様に一列に並べて整理するというコンセプトで、更にReduxの<strong>dux</strong>ともかけて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>したのかもしれませんね。</p> <h2>おわりに</h2> <p>これからもどんどん新しい機能を実装していきVIDEO BRAINがお客様に喜んで頂けるプロダクトへ成長させていく一端を担う者として頑張っていきたいと思います。</p> <p>また今後更にReactとReduxに触れていく中で、得られた知見を共有させて頂けたらと思います。</p> open8tech 新卒がde:code2019に行ってみた hatenablog://entry/26006613380668624 2019-08-02T11:26:12+09:00 2019-08-02T11:26:12+09:00 de:code振り返り はじめまして。今年の4月に入社&上京した新卒の田村です。今年の夏はサーフィンを始めようと考えているのですが、身体がだらしなさすぎるので先日ジムに入会しました。 東京のご飯が美味しすぎるのが悪いですね。東京のせい。 さて、先日Microsoft主催のde:codeというカンファレンスが催されました。 幸運なことにそれに参加する権利をいただけたため、簡単ではありますが、面白かったセッションの振り返りをしたいと思います。 HoloLens2 今回のde:codeで最も心が揺さぶられたのが、HoloLens関連のセッションです。 僕はde:codeの会場で初めてHoloLens… <h2>de:code振り返り</h2> <p>はじめまして。今年の4月に入社&上京した新卒の田村です。今年の夏はサーフィンを始めようと考えているのですが、身体がだらしなさすぎるので先日ジムに入会しました。 東京のご飯が美味しすぎるのが悪いですね。東京のせい。</p> <p>さて、先日<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a>主催のde:codeというカンファレンスが催されました。 幸運なことにそれに参加する権利をいただけたため、簡単ではありますが、面白かったセッションの振り返りをしたいと思います。</p> <h2>HoloLens2</h2> <p>今回のde:codeで最も心が揺さぶられたのが、HoloLens関連のセッションです。 僕はde:codeの会場で初めてHoloLensという存在を知り、HoloLensが見せてくれる未来感に興奮しました。 「これあればトニー・スタークになれるやん」と心から思い、映画の中にしか無かった未来がもうそこまで来ていることを肌で実感しました。</p> <p>HoloLens2がどんなにすごいのかはこの映像を見てもらうのが一番てっとり速いかもしれません。</p> <p><iframe width="480" height="270" src="https://www.youtube.com/embed/eqFqtAJMtYE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://youtu.be/eqFqtAJMtYE">youtu.be</a></cite></p> <p><iframe width="480" height="270" src="https://www.youtube.com/embed/J-C6GE2gFYw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><cite class="hatena-citation"><a href="https://youtu.be/J-C6GE2gFYw">youtu.be</a></cite></p> <p>どうでしょう。すごくないですか。僕はワクワクしっぱなしでした。 HoloLens2に関するセッションにも参加したのですが、HoloLens2はホログラムを自在に操ることができるようです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190730/20190730134903.jpg" alt="f:id:open8tech:20190730134903j:plain" title="f:id:open8tech:20190730134903j:plain" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190730/20190730134944.jpg" alt="f:id:open8tech:20190730134944j:plain" title="f:id:open8tech:20190730134944j:plain" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190730/20190730135005.jpg" alt="f:id:open8tech:20190730135005j:plain" title="f:id:open8tech:20190730135005j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>以上のように、本当に物体がその場にあるかのように操作することができるのです。 ハードをいじったことのない僕からすると理解<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C9%D4%C7%BD">不能</a>です。なんでこんなことができるんだろうって感じでした。工学部は偉大。</p> <p>これをHoloLensは主にファーストラインワーカーへの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%D1%A5%AF">インパク</a>トを期待しているため、弊社のような企業で導入するのはなかなか難しいかもしれません。しかし、弊社のルトロンは「体験」をキーとしているメディアであるため、イベントなどでHoloLensを使い、コスメや服などの仮想展覧回などができるかもしれません。1台39万円程度するため、個人で購入することが難しいので、会社で購入してもらって開発したいなぁと思う日々です。</p> <h2>HoloLens体験</h2> <p>会場では実際にHoloLensを体験できるブースがありました。 最新のHoloLens2の体験は完全に抽選になっており、だいたい5〜10%程度の当選率になっていました。 残念ながら僕はハズれてしまいましたが、現行版のHoloLensを体験することができました。</p> <p>いくつかある体験ブースの中で最も人気だったのが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%E8%A5%BF">トヨタ</a>のブースでした。 会場には<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EA%A5%A6%A5%B9">プリウス</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%C2%BC%D6">実車</a>が運び込まれており、車体をHoloLens越しにみると<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EA%A5%A6%A5%B9">プリウス</a>の内部構造を見ることができるという体験でした。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EA%A5%A6%A5%B9">プリウス</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CF%A5%A4%A5%D6%A5%EA%A5%C3%A5%C9%BC%AB%C6%B0%BC%D6">ハイブリッド自動車</a>なので、車内に蓄電装置が搭載されています。本来、その蓄電装置を一般の人が見る機会は殆どないのですが、HoloLensを使うと、その蓄電池がホログラムとなって<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EA%A5%A6%A5%B9">プリウス</a>の中に現れます。 このホログラムは本物の物質のようにそこに存在します。自分が移動してもホログラムは常にその場所にあり、前、横、後ろと様々な確度から観察することができました。 驚愕の体験でした。そもそもどうやって蓄電装置の位置を決定しているのか疑問に思い、質問してみたところ、車のバンパーを基準にして人力で位置を調整しているようでした。 さすがにそこはまだ手動なのですね。</p> <p>しかし、この技術を使えば整備士の仕事の効率が格段に上がるのではないでしょうか。 車の情報がインプットされたHoloLensを使えば、設計書がその場に無くても、視覚的に、もっと言うなら体験的に車の構造を認知することができます。 更に構造を見ている間も両手は空いているため、かなり作業効率が上がるのではないでしょうか。実際に今年からHoloLensが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%E8%A5%BF">トヨタ</a>に導入されるとのアナウンスもありました。HoloLensがあればファーストラインワーカーの仕事に革命が起こるのではないでしょうか。</p> <h2>サーバーレスな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A1%BC%A5%C8%A5%B9%A5%D4%A1%BC%A5%AB%A1%BC">スマートスピーカー</a></h2> <p>初めてちょまどさんを生で見ました。de:code参加直前にちょまどさんの存在を知り、ちょうどサーバレスに興味があったので、このセッションに行きました。参加者も多く人気のセッションでしたね。文系出身→<a class="keyword" href="http://d.hatena.ne.jp/keyword/SIer">SIer</a>3ヶ月で退職→<a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/IOS">IOS</a>エンジニア→<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a>という経歴の持ち主であり、文系出身&<a class="keyword" href="http://d.hatena.ne.jp/keyword/SIer">SIer</a>内定もらっていた身としては親近感を覚えました。</p> <p>さて、肝心のセッションの中身なのですが、主に、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A1%BC%A5%C8%A5%B9%A5%D4%A1%BC%A5%AB%A1%BC">スマートスピーカー</a>のバックエンドとしてAzure Functionsを使うという内容になっていました。 一問一答のために用意された回答を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A1%BC%A5%C8%A5%B9%A5%D4%A1%BC%A5%AB%A1%BC">スマートスピーカー</a>に登録するのは簡単です。しかし、そんなのは全くスマートではありません。 もっとスマートに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A1%BC%A5%C8%A5%B9%A5%D4%A1%BC%A5%AB%A1%BC">スマートスピーカー</a>を使うために、バックエンドを作り、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a>製品であるOffice365と連携してビジネスに活用していこう、というのがセッションの主な内用でした。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190730/20190730135045.png" alt="f:id:open8tech:20190730135045p:plain" title="f:id:open8tech:20190730135045p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>Azure Functionsは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a>が提供しているFaas(Function as a Service)です。いわゆる「サーバーレス」というものですね。Azure Functionsを使う上で、使用者が考えるべきことは眼前の問題を解決する関数(コード)だけです。慣れていない人<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AB%A4%E9%A4%B7">からし</a>たら小難しく思えるインフラ周りのことはほとんど気にする必要がありません。さらに料金形態もシンプルで、コードが実行された時間に対して料金を払えばいいだけです。Paas(Platform as a Service)のようにRequestを待機するためにサーバを起動しつづけて、その待機時間分の料金が取られるということがありません。似たようなサービスとして<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a>が提供している<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Lambda(以下Lambda)があります。</p> <p>以前から「サーバーレス」という言葉を耳にしたことはあったのですが、その実態をこのセッションで始めて知りました。ただ、注意したいのは本当にサーバーが"存在しない"わけではないという点です。サーバーのインフラ周りを"考慮する必要が無い"という意味でサーバーレスと呼ばれているようです。勘違いしないようにしなくてはなりません。</p> <p>構成図のAzure Functionsの右側に配置されているアイコン、GraphAPIは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a>のサービスリソースへのアクセスを可能にするRESTful Web <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>です。そのためGraphAPIを使えば容易にOffice365のデータにアクセスすることができるようになります。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A1%BC%A5%C8%A5%B9%A5%D4%A1%BC%A5%AB%A1%BC">スマートスピーカー</a>をよりスマートにするために、Azure FunctionsとGraphAPIを活用してOffice365と連携しようという内容だったのですが、実装する際には本当に関数だけに集中すればいいので、割と簡単に実装できるようです。</p> <p>帰ってすぐに実践したいと考えたのですが、僕は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A1%BC%A5%C8%A5%B9%A5%D4%A1%BC%A5%AB%A1%BC">スマートスピーカー</a>もなく、Azure関連のサービスを使ったことがありませんでした。しかし、僕にはSlackと<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>のアカウントがありました。先にもちらっと紹介しましたが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a>もLambdaというFaasを提供しています。Slackにはアプリ機能があり、自分でSlackを拡張することができます。これだと思い、僕はLambdaを使ってSlackを拡張することにしました。</p> <p>弊社では日報を<a class="keyword" href="http://d.hatena.ne.jp/keyword/DocBase">DocBase</a>で提出することになっています。そこでSlackから日報を提出する機能を作ろうと考え、実装しました。LambdaはSlackと<a class="keyword" href="http://d.hatena.ne.jp/keyword/DocBase">DocBase</a>の橋渡し的な存在で、SlackからのRequestをそのまま<a class="keyword" href="http://d.hatena.ne.jp/keyword/DocBase">DocBase</a>に渡し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/DocBase">DocBase</a>からのresponseを成形してSlackに返すというシンプルな構造です。</p> <p>Lambda自体触るのが初めてだったのですが、設定が終われば後はコードに集中すればいいので割と簡単に実装することができました。インフラ周りを考えなくていいのは本当に楽ですね。しかし、まだエンジニア歴3ヶ月程度の僕がこれに慣れてしまうのもいかがなものか、という気も少ししました。まぁ今回みたいな簡単な処理を行いたいだけだったり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C1%BD%E8%CD%FD">バッチ処理</a>を流すために<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を叩かせるとか、ちょっとした機能がほしいけどわざわざサーバ立てるのも馬鹿らしいという場合にはちょうどいいのではないでしょうか。</p> <h2>エンジニアのキャリア</h2> <p>澤 円さんの公演でした。de:codeのこのセッションで初めて存在を知ったのですが、とても面白い(interest)方でした。まさかの澤さんも文系出身。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%D0%A5%F3%A5%B8%A5%A7%A5%EA%A5%B9%A5%C8">エバンジェリスト</a>は文系出身の方が多いのでしょうか。このセッションは最終日の最後のセッションということもあり、かなり多くの人が参加しており、立ち見の人の数も凄まじかったです。</p> <p>さて、肝心のセッションの内容についてです。澤さんの社会人としてのストーリーを語りつつ、自分の身を置く場所についての話になりました。適材適所に通ずる話ですね。 最も印象的だったのが、この表です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190730/20190730135118.png" alt="f:id:open8tech:20190730135118p:plain" title="f:id:open8tech:20190730135118p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>社会人であれば、会社から何らかの役割や仕事を与えられ、その仕事に従事するはずです。仕事をこなしていくうちに、嫌でも自分ができるとか、自分はできないとかいう感情を抱くことになります。その状態を表にしたものが、上の画像です。</p> <p>言うまでもなく最も良いのは右上の「"賢い"かつ"速い"」です。ここにいる人間は、個人として会社に評価されるだけではなく、「組織全体を引き上げる上昇気流」を作り出します。周りの社員の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%ED%A1%BC%A5%EB%A5%E2%A5%C7%A5%EB">ロールモデル</a>になることで下が育っていくわけです。 では、右上以外の箇所にいる人間はどうなるのでしょうか。その人はダメな人間なのでしょうか。</p> <p>澤さんはそれを否定しました。仕事ができないの主語は「あなた」ではなく「今ここで仕事をしている状態」だと。de:codeという舞台でセッションを開く澤さんでも、向いていなかったエンジニアの仕事をさせれば「"愚か"で"遅い"」になってしまいます。これはキャリアを積む上で非常に大切な考え方で、自分の居場所やあり方を選べば、誰でも「"賢い"かつ"速い"」になることができるのです。 キャリアで悩む人はたいてい主語を自分にしてしまって苦しむパターンが多いので、そうならないように注意する必要があるとも話されていました。</p> <p>では、どうすれば「"賢い"かつ"速い"」になれる居場所を見つけることができるのでしょうか。 そのためには自分がどうありたいのかを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%B2%BD">言語化</a>していくことが大切だと話していました。誰しもこうなりたいという願望はあると思いますが、それを上手く<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%B2%BD">言語化</a>できてる人は少ないでしょう。 ちなみに、「"愚か"で"遅い"」になる簡単な方法も紹介していました。それが睡眠不足です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190730/20190730135201.png" alt="f:id:open8tech:20190730135201p:plain" title="f:id:open8tech:20190730135201p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>誰もがパフォーマンスを上げるために今できる改善が睡眠だそうです。 話を戻しますが、自分を知るためには行動が大切です。ここでは2つのやり方が紹介されました。</p> <p><b>・レジュメを書く</b><br/> 日本語で言う履歴書です。転職するしないにかかわらず、全員書くべきだとおっしゃっていました。 レジュメを書くことで、今の自分の経験値を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%B2%BD">言語化</a>することができます。1回書いて満足するのではなく、定期的にレジュメを更新すべきだそうです。</p> <p><b>・リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>と会う</b><br/> 転職するしないにかかわらずリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>に会うべきだとおっしゃていました。リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>に会えばIT業界の人材の流れや、自分の市場価値を知ることができます。 先のレジュメ作成と合わせて、行うのが良いそうで、レジュメ作成=問診票、リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>と会う=健康診断、だそうです。人間の身体と同じで、キャリアも定期的に健康診断を受けましょうということですね。</p> <p>以上の話が、澤さんのセッションで印象深かった箇所です。転職するしないにかかわらず、レジュメを更新してリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>と会うっていう発想は全くありませんでした。主語を自分じゃなくて、状態にするという考えも良いなと感じました。今後「仕事全然できないなぁ。ダメだなぁ。」と思うこともきっとあるでしょうが、主語を自分にしないように注意します。「自分ダメだなぁ」と思ってしまうと精神的にもキツくて、何やってもダメみたいな気持ちになってしまいそうですし。十年一昔ではなく、一年一昔というIT業界で長く活躍できるためにも、定期的に健康診断を受けていきたいと思いました。数年後にはアラサーなので身体の方の健康診断も必ず受けるようにします。</p> <h2>最後に</h2> <p>入社して2ヶ月でde:codeという大きなイベントに参加することができて本当に幸運だと感じています。今回紹介したセッション以外にも2日間びっちりセッションを聞いて回ったのですが、この経験はエンジニアとしての技術も未熟な僕にとって、知見を広めるとても良い機会でした。何が一番心に残ったかと言われると、僕の脳みそは単純なので「HoloLensの体験!」思ってしまうのですが、ITに携わるものとして、大きな発表を知ったり、最新技術に触れたりするなどして、ワクワク感、やる気を得られたのは自分にとって大きな収穫だったと思います。</p> open8tech de:code 2019に行ってきました hatenablog://entry/17680117127211597535 2019-07-04T15:19:07+09:00 2019-07-04T16:31:55+09:00 こんにちは、オープンエイトでインフラ全般を担当している武田です。オープンエイトではサービス全般をクラウドサービスで実現していることから、AmazonのAWS・マイクロソフトのAzure・GoogleのGCPといたメジャーなクラウドサービス、akamaiのCDNサービスといった特化型のサービスなど数多くのサービスなど扱っています。インフラ担当とは、それらクラウドサービスの設計・構築・運用(つまり全て)を行っています。その関係から、各企業とはお付き合いがあり、この度日本マイクロソフトよりde:code2019へのお誘いがありましたので、参加してまいりました。 今回のテックブログでは少し遅くなってし… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701110924.jpg" alt="f:id:open8tech:20190701110924j:plain" title="f:id:open8tech:20190701110924j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、オープンエイトでインフラ全般を担当している武田です。オープンエイトではサービス全般を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>サービスで実現していることから、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>・<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>のAzure・<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/GCP">GCP</a>といたメジャーな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>サービス、<a class="keyword" href="http://d.hatena.ne.jp/keyword/akamai">akamai</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/CDN">CDN</a>サービスといった特化型のサービスなど数多くのサービスなど扱っています。インフラ担当とは、それら<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>サービスの設計・構築・運用(つまり全て)を行っています。その関係から、各企業とはお付き合いがあり、この度日本<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>よりde:code2019へのお誘いがありましたので、参加してまいりました。</p> <p>今回のテックブログでは少し遅くなってしまいましたが、de:code2019の基調講演の様子をご紹介したいと思います。当日行くことが出来なかった方も、会場の雰囲気を感じていただければと思います。</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>のde:codeとは</h2> <p>de:codeとは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>が開催するITに携わるすべてのエンジニアを対象としたテクニカルカンファレスとなり、2014年より毎年開催されています。de:codeでは、先んじて行われた米国サンフランシスコで開催される開発者向けカンファレンス「Build」の内容を凝縮したカンファレンスから、日本向けのオリジナルコンテンツを盛り込んだセッションなどを2日間に渡って行われます。今年は5月29日から30日の2日間にわたり開催され、5月29日の午前中に上級役員による基調講演、午後から30日にかけては180を超えるセッションが会場の複数の会議室で行われました。また、Expoエリアでは協賛企業による製品の展示から、体験型のイベントなども行われていました。</p> <p>会場となるのはザ・プリンス パークタワー東京です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111016.jpg" alt="f:id:open8tech:20190701111016j:plain" title="f:id:open8tech:20190701111016j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>当日は雨が降る中でしたが、会場は参加した方でいっぱいでした。特に基調講演については、開始が9時半からであるにも関わらず、9時の段階で満席となってしまいました。私自身も9時前には会場に着きましたが、すでに会場の中央部は満席で会場の端になってしまいました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111108.jpg" alt="f:id:open8tech:20190701111108j:plain" title="f:id:open8tech:20190701111108j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>そこからも、注目度がうかがえます。</p> <p>de:code2019の基調講演では、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>が注力していることや未来について、また最新のサービスの動向などについて以下の方が講演を行いました。</p> <ul> <li>日本<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>株式会社 平野 拓也</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a> コーポレーション ジャレッド スパタロウ</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a> コーポレーション ジュリア ホワイト</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a> コーポレーション アレックス キップマン</li> </ul> <p>見ていただいてわかるかと思いますが、日本<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>株式会社の平野 拓也氏以外はすべて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>の上級役員です。講演は平野 拓也氏以外はすべて英語で行われるため、会場では同時通訳を聞ける機械が配られていました。また、会場に設置された大型のスクリーンには同時に字幕も流れていました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111157.jpg" alt="f:id:open8tech:20190701111157j:plain" title="f:id:open8tech:20190701111157j:plain" class="hatena-fotolife" itemprop="image"></span></p> <h2>平野 拓也氏の講演</h2> <p>平野 拓也氏は日本<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>株式会社の現<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%E5%C9%BD%BC%E8%C4%F9%CC%F2">代表取締役</a>社長となります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111214.jpg" alt="f:id:open8tech:20190701111214j:plain" title="f:id:open8tech:20190701111214j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>座った席が遠くからなので、本人は小さいですね。</p> <p>平野 拓也氏は2015年7月より日本<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%E5%C9%BD%BC%E8%C4%F9%CC%F2">代表取締役</a>社長<a href="#f-203a9691" name="fn-203a9691" title="2019年7月3日にて、8月31日付けで日本マイクロソフト株式会社の代表取締役社長を退任し、9月1日付けで米本社であるマイクロソフト コープレーションのバイスプレジデントに就任することが発表されました。">*1</a>に就任し、現在日本<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>の舵取りを行っている方になります。</p> <p>平野 拓也氏からは現在の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>が向かっている方向性について紹介がありました。</p> <p>説明の冒頭では、AzureのAIを利用した具体的な事例として、阿修羅像の年齢推定の取り組みについてのお話を聞けました。世界ではAIの開発が盛んに行われており、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>としてもAI基盤は注力していることのアピールとして、講演の冒頭に印象深い話を持ってきたのではないでしょうか。AIについては、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%B3%D8%BD%AC">機械学習</a>が主流であり、いかに学習を積み重ねるかが、精度に影響します。どの企業も学習の素材について苦労している現状であるなか、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>の取り組みは大きな企業ならではと感じました。</p> <p>続いて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>としては、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> Azure・<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> 365・<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> Dynamics 365 & PowerPlatform・<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> Gamingの4つの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>プラットフォームに注力していくこと、また各企業との提携についてお話がありました。</p> <p>馴染みの深い内容としては、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Sony">Sony</a>との戦略的提携が一つあげられます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111249.jpg" alt="f:id:open8tech:20190701111249j:plain" title="f:id:open8tech:20190701111249j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>が今後注力していくプラットフォームに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> Gamingがあり、その延長線上の取り組みの一つと考えられますが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/Sony">Sony</a>は現在もコンソールゲームの分野では<a class="keyword" href="http://d.hatena.ne.jp/keyword/Xbox360">Xbox360</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/PlayStation4">PlayStation4</a>で競う仲ではあります。ただし、現在のコンソールゲームについては、インターネットを経由したサービスは必須であり、またこれから先の未来においてもゲームの主戦場がコンソールであり続ける保証はなく、モバイルもしくは新たなプラットフォームに移る可能性もあります。くしくも先日<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a>が新ゲームサービスである「Stadia」を発表したこともあり、ゲーム業界は再び群雄割拠の時代に突入する予感があります。現在コンソールゲーム業界の巨人2社が提携するに至ったのは、これからの未来を鑑みると必然であるのかもしれません。</p> <p>他にはIoTを利用した企業の発表などが続き、平野 拓也氏の講演は終わりました。</p> <h2>ジャレッド スパタロウ氏の講演</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111327.jpg" alt="f:id:open8tech:20190701111327j:plain" title="f:id:open8tech:20190701111327j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>続きまして、ジャレッド スパタロウ氏の講演では<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> 365・<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> Graph・<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a> Subsystem for <a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a> 2(WLLS2)の紹介がありました。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>と言うと少し前まではOSの会社、更に言えばクライアントサイドで頂点にまで上り詰めたといったイメージが強いですが、ジャレッド スパタロウ氏が冒頭で、今後のITは人を中心としたあり方に変わっていく、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>のサービスはそれらを変えていけると力強く述べていました。実際、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> 365のデモでは、今まで<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a>などが先行していたと思われる共同作業やサービス連携などが、違和感なく実現できていると感じました。さらに、今までクライアントサイドとして強みを発揮していたOfficeの操作性・表現力も兼ね備えたサービスとなっているのは、今後の可能性を感じるものでした。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111341.jpg" alt="f:id:open8tech:20190701111341j:plain" title="f:id:open8tech:20190701111341j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>ただ、私自身として印象深かったのは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a> Terminalと<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a> Subsystem for <a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a> 2(WLLS2)の紹介でした。現在において、サービスの基盤は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>の割合が多く、長らく<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>での開発ではサーバを立ち上げ、Tera Termなどのターミナルソフトを入れて開発・構築を行うことが常でした。WindowsOSの成り立ち上その形態は致し方ないと感じていましたが、今後は大きく変わっていくものと思います。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a> Terminalのデモで表示された絵文字を使ったロゴを見て、Windows3.1を思い出してしまった事に自身の歳を感じる事と、現在<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>の一員とはいえ、オープソースコニュニティから提供を行うこと自体が、時代の流れを感じさせます。</p> <h2>ジュリア ホワイト氏の講演</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111404.jpg" alt="f:id:open8tech:20190701111404j:plain" title="f:id:open8tech:20190701111404j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>ジュリア ホワイト氏の講演では、主に<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a> Azureの最新トピックが紹介されました。現在においても日々新しいサービスが追加されていくAzureですが、その中でもAzure DevOpsの機能強化・各サービスとの連携に重点をおいた紹介がありました。開発側としては新たに発表されたブラウザベースの開発支援ツール<a class="keyword" href="http://d.hatena.ne.jp/keyword/Visual%20Studio">Visual Studio</a> OnlineやVisual Studio2019・<a class="keyword" href="http://d.hatena.ne.jp/keyword/VS%20Code">VS Code</a>などとの連携も強化されることは注目すべきところだと思います。また、注目のトピックとしてはデータベース周りについて、「Azure <a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a> Database Hyperscale」、「Azure Database for <a class="keyword" href="http://d.hatena.ne.jp/keyword/PostgreSQL">PostgreSQL</a> Hyperscale」、「Azure <a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a> Database サーバーレス」、「Azure <a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a> Database Edge」の紹介がありました。今まで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>と言えばMSsqlserverのイメージが強かった(当たり前ですが)のですが、PostgerSQLをサービスのラインナップとして入れること自体驚きがあります。</p> <h2>アレックス キップマン氏の講演</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111430.jpg" alt="f:id:open8tech:20190701111430j:plain" title="f:id:open8tech:20190701111430j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>アレックス キップマン氏の講演では、最新のHoloLens 2についてデモを交えて紹介されました。HoloLensについて興味のある方であればご存知の方も多いと思いますが、アレックス キップマン氏はHoloLensの生みの親であります。そのため、思い入れも強くあると思われ、講演には非常に熱がこもっていることが、聞いている側からもわかりました。講演の前半ではHoloLens 2については基本的な機能の強化に加えて、装着性の快適さを向上するための工夫を凝らした事を強調されていました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190701/20190701111444.jpg" alt="f:id:open8tech:20190701111444j:plain" title="f:id:open8tech:20190701111444j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>注目すべきは内容としては、講演の後半に行われたHoloLens 2のでデモです。アレックス キップマン氏自身がHoloLens 2を装着して、MRにて自身の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%D0%A5%BF%A1%BC">アバター</a>を映し出して戦わせるなど、見る側としても非常に楽しいものでした。しかし、私としてはアレックス キップマン氏が装着したHoloLens 2以外のカメラで会場に向けにMRの映像を同時に異なった角度から見えるデモから、単体としての機能より、HoloLens 2を装着した複数の者が同じものを見て共同作業ができる事を強くイメージできました。</p> <h2>最後に</h2> <p>基調講演は予定の時間を大幅に超えて3時間にもなりました。今回はそのほんの一部の紹介となりますが、内容としては非常に密度が濃く、興味深い内容に溢れていました。また、基調講演の他にも180のセッションが行われており、その中には企業での実例に基づいた発表であったり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>の方が最新サービスの詳細を説明していただけるセッション、さらに最新のサービスを体験できるワークショップなど、多種多様なセッションが開かれていました。また、無料でも参加できるEXPOでも各企業の展示を見ることができます。その中にはHoloLens 2を利用した業務効率化の事例を体験できるなど、楽しめる内容がたくさんありました。次回は来年の開催となりますが、興味があれば参加して見てはいかがでしょうか。</p> <p>また、基調講演については現在も<a class="keyword" href="http://d.hatena.ne.jp/keyword/YouTube">YouTube</a>で公開されています。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>のサイトにリンクがありますので、興味のある方は一度ご覧になってください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.microsoft.com%2Fja-jp%2Fevents%2Fdecode%2F2019%2Fdefault.aspx" title="de:code 2019 | 開発者をはじめとする、IT に携わるすべてのエンジニアのための年に一度のテクニカルカンファレンス。5月29日-30日開催。https://aka.ms/decode19 #decode19" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.microsoft.com/ja-jp/events/decode/2019/default.aspx">www.microsoft.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-203a9691" name="f-203a9691" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">2019年7月3日にて、8月31日付けで日本<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a>株式会社の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%E5%C9%BD%BC%E8%C4%F9%CC%F2">代表取締役</a>社長を退任し、9月1日付けで米本社である<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%AF%A5%ED%A5%BD%A5%D5%A5%C8">マイクロソフト</a> コープレーションの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9%A5%D7%A5%EC%A5%B8%A5%C7%A5%F3%A5%C8">バイスプレジデント</a>に就任することが発表されました。</span></p> </div> open8tech Nginxを用いたDocker内で使えるロードバランサ設計 hatenablog://entry/17680117127129079471 2019-05-31T14:39:02+09:00 2019-05-31T14:39:02+09:00 新卒2年目の梅川です。部屋の中が暑すぎて、例年より大分前倒ししてクーラーを解禁しました。皆様も熱中症にはお気をつけください。 さて、今回は弊社の広告事業で用いている管理画面を様々な事情で新しく作り直すことになったため、その時の開発環境用のDockerとNginxを用いたロードバランサ(以下LB)設計についてお話ししようと思います。 全体の方針 作り直すことになった管理画面ですが、全てのページを一度に移行するわけではなく、完成したページから徐々に移行する方針をとりました。 そして、移行過程で必要になってくる適切なパスの振り分けを、LBで管理することにしました。 ステージング環境や本番環境では、A… <p>新卒2年目の梅川です。部屋の中が暑すぎて、例年より大分前倒ししてクーラーを解禁しました。皆様も<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C7%AE%C3%E6%BE%C9">熱中症</a>にはお気をつけください。</p> <p>さて、今回は弊社の広告事業で用いている管理画面を様々な事情で新しく作り直すことになったため、その時の開発環境用のDockerとNginxを用いたロードバランサ(以下LB)設計についてお話ししようと思います。</p> <h2>全体の方針</h2> <p>作り直すことになった管理画面ですが、全てのページを一度に移行するわけではなく、完成したページから徐々に移行する方針をとりました。 そして、移行過程で必要になってくる適切なパスの振り分けを、LBで管理することにしました。</p> <p>ステージング環境や本番環境では、<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>のALBでパスの振り分けを行うことにしました。しかし、開発環境ではそうもいかないため、Nginxを用いて振り分けを行うことにしました。 また、新管理画面、旧管理画面、両管理画面共通で扱うデータベースおよびLBをそれぞれDockerのコンテナとして定義し、Docker Composeでまとめることで開発環境をセットアップしやすいようにしました。</p> <h2>構成</h2> <p>改めて今回作成したい環境について詳しく見ていきます。 作成したい環境は以下の図のようになります。 <figure class="figure-image figure-image-fotolife" title="構成図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190520/20190520172118.png" alt="f:id:open8tech:20190520172118p:plain" title="f:id:open8tech:20190520172118p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>構成図</figcaption></figure></p> <p>管理画面は新旧どちらも<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>で作成します。今回の開発ではLBを介さずにアクセスしたいという要望が存在したため、80番ポートからLBにアクセスできる他、開発者が53000番・63000番ポートから、各管理画面へLBを介さず直接アクセスできるようにします。 では、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DB%A1%BC%A5%E0%A5%D5%A5%A9%A5%EB%A5%C0">ホームフォルダ</a>上に<code>cmc_project</code>というフォルダを作成し、そのフォルダ上で環境構築を行います(今回は説明に必要なファイルのみ記載)。 仮に古い管理画面の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>名を<code>old_cmc_repo</code>、新しい管理画面の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>名を<code>new_cmc_repo</code>、<code>docker-compose.yml</code>等を置く<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>名を<code>cmc_docker</code>とした時下記のようなフォルダ構成をとります。</p> <pre class="code" data-lang="" data-unlink>cmc_project ├─ cmc_docker │ ├─ docker-compose.yml │ └─ load_balancer │ ├─ Dockerfile │ └─ nginx.conf ├─ old_cmc_repo │ └─ Dockerfile └─ new_cmc_repo └─ Dockerfile</pre> <p>データベースは作成済みのイメージを使いますが、それ以外は<code>Dockerfile</code>を使って環境構築を行います。</p> <p>それでは、セットアップ用の<code>cmc_docker/docker-compose.yml</code>を見ていきましょう。</p> <pre class="code docker" data-lang="docker" data-unlink>version: &#39;3&#39; services: load_balancer: build: ./load_balancer/ ports: - &#39;80:80&#39; old_cmc: build: ../old_cmc_repo/ ports: - &#39;53000:80&#39; volumes: - ../old_cmc_repo/:/root/projects/old_cmc_repo - /root/bundle - /root/projects/old_cmc_repo/node_modules new_cmc: build: ../new_cmc_repo/ ports: - &#39;63000:80&#39; volumes: - ../new_prod_repo/:/root/projects/new_cmc_repo - /root/bundle db: image: mysql:5.6.34 command: &#34;mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci&#34; working_dir: /root/dump environment: MYSQL_ROOT_PASSWORD: &#39;root&#39; TZ: Asia/Tokyo ports: - &#39;3306&#39; volumes: - mysql-data:/var/lib/mysql volumes: mysql-data: driver: local</pre> <p>load_balancerの<a class="keyword" href="http://d.hatena.ne.jp/keyword/ports">ports</a>の部分を見ていただくと<code>80:80</code>という記述があります。これにより、ユーザーはブラウザ等から<code>localhost:80</code>にアクセスすることで、load_balancerコンテナの<code>localhost:80</code>にアクセスできるようになります。old_cmcコンテナ・new_cmcコンテナも同様にユーザーは<code>localhost:53000</code>・<code>localhost:63000</code>にアクセスすることで各コンテナ内の<code>localhost:80</code>にアクセスできます。</p> <h2>LB用コンテナの構成</h2> <p>Docker Composeの準備が整ったところでload_balancerコンテナについて見ていきます。 まずは、<code>Dockerfile</code>から見ていきましょう。</p> <pre class="code docker" data-lang="docker" data-unlink>#cmc_docker/load_balancer/Dockerfile #最新の安定板を取得する FROM nginx:1.15.12 COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 CMD [&#34;nginx&#34;, &#34;-g&#34;, &#34;daemon off;&#34;]</pre> <p>Nginxのイメージを取ってきて、<code>/etc/nginx/</code>へ<code>nginx.conf</code>を置いた後、Nginxを起動しているのみの簡素な<code>Dockerfile</code>です。 次に、<code>nginx.conf</code>でどのようにパスを振り分けているのかを見ていきます(<code>nginx.conf</code>の全容は最後に付録として載せています)。 今回は以下の図のように、<code>/path_to_a</code>と<code>/path_to_b</code>のみnew_cmcコンテナへ振り分ける事とした場合です。</p> <p><figure class="figure-image figure-image-fotolife" title="振り分け図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190520/20190520172109.png" alt="f:id:open8tech:20190520172109p:plain" title="f:id:open8tech:20190520172109p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>振り分け図</figcaption></figure></p> <p>まず下記の部分で、<code>docker-compose.yml</code>で定義したコンテナ名をサーバーとして指定をしています。</p> <pre class="code lang-nginx" data-lang="nginx" data-unlink> <span class="synStatement">upstream</span> old_cmc_stream { <span class="synType">server</span> old_cmc; } <span class="synStatement">upstream</span> new_cmc_stream { <span class="synType">server</span> new_cmc; } </pre> <p>コンテナ間は<code>http://(コンテナ名):(ポート番号)</code>で相互にアクセスできるようになっているため、このような書き方になります。 あとは完成したページはnew_cmcコンテナへ、そうでないものはold_cmcコンテナへ振り分けていきます。 単純な振り分けであれば、下記のようにlocationで指定することで行うことができます。</p> <pre class="code lang-nginx" data-lang="nginx" data-unlink> <span class="synStatement">location</span> /path_to_a { <span class="synType">proxy_pass</span> http://new_cmc_stream/path_to_a; } <span class="synStatement">location</span> /path_to_b { <span class="synType">proxy_pass</span> http://new_cmc_stream/path_to_b; } <span class="synStatement">location</span> / { <span class="synType">proxy_pass</span> http://old_cmc_stream; } </pre> <p>しかし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>アプリケーションであれば<code>/assets</code>も適切に振り分ける必要があります。 様々な解決法はありますが、お手軽にできそうだったため<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%E9">リファラ</a>を使った振り分けを今回は採用しました。 下記の記述を加えることで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%E9">リファラ</a>が<code>/path_to_a</code>か<code>/path_to_b</code>であればvalid_referersとしてnew_cmcコンテナへ、それ以外はold_cmcコンテナへ振り分けるようになります。</p> <pre class="code lang-nginx" data-lang="nginx" data-unlink> <span class="synStatement">location</span> /assets { <span class="synIdentifier">valid_referers</span> ~localhost/path_to_a*; <span class="synIdentifier">valid_referers</span> ~localhost/path_to_b*; <span class="synStatement">if</span> (<span class="synIdentifier">$invalid_referer</span> != <span class="synConstant">&quot;1&quot;</span>) { <span class="synType">proxy_pass</span> http://new_cmc_stream; } <span class="synStatement">if</span> (<span class="synIdentifier">$invalid_referer</span> = <span class="synConstant">&quot;1&quot;</span>) { <span class="synType">proxy_pass</span> http://old_cmc_stream; } } </pre> <p>これでLBは完成です。他のページも移行したい時には設定を<code>nginx.conf</code>に加筆していくことで、スムーズな切り替えができます。</p> <h2>まとめ</h2> <p>今回はWebシステムを移行したい時の、DockerとNginxを用いた環境構築の一例を紹介させていただきました。 Nginxをきちんと触ったのは初めてで、実は構築にかなり苦戦したのですが、時間をかけて取り組んだおかげで理解がとても進みました。 また、Docker Composeで環境構築のために必要なものを全てひとまとめにしたおかげで、環境構築がとても楽になりました。 今回の記事が何らかの参考になれば幸いです。</p> <h2>付録</h2> <pre class="code lang-nginx" data-lang="nginx" data-unlink><span class="synComment">#cmc_docker/load_balancer/nginx.conf</span> <span class="synIdentifier">user</span> www-data; <span class="synIdentifier">worker_processes</span> 4; <span class="synIdentifier">pid</span> /run/nginx.pid; <span class="synStatement">events</span> { <span class="synIdentifier">worker_connections</span> 768; <span class="synComment"> # multi_accept on;</span> } <span class="synStatement">http</span> { <span class="synIdentifier">proxy_redirect</span> <span class="synConstant">off</span>; <span class="synIdentifier">proxy_set_header</span> Host <span class="synIdentifier">$host</span>; <span class="synIdentifier">proxy_set_header</span> X-Forwarded-For <span class="synIdentifier">$proxy_add_x_forwarded_for</span>; <span class="synIdentifier">proxy_set_header</span> X-Forwarded-Host <span class="synIdentifier">$host</span>; <span class="synIdentifier">proxy_set_header</span> X-Forwarded-Server <span class="synIdentifier">$host</span>; <span class="synIdentifier">proxy_set_header</span> X-Real-IP <span class="synIdentifier">$remote_addr</span>; <span class="synIdentifier">sendfile</span> <span class="synConstant">on</span>; <span class="synIdentifier">tcp_nopush</span> <span class="synConstant">on</span>; <span class="synIdentifier">tcp_nodelay</span> <span class="synConstant">on</span>; <span class="synIdentifier">keepalive_timeout</span> 65; <span class="synIdentifier">types_hash_max_size</span> 2048; <span class="synIdentifier">default_type</span> application/octet-stream; <span class="synIdentifier">access_log</span> /var/log/nginx/access.log; <span class="synIdentifier">error_log</span> /var/log/nginx/error.log; <span class="synIdentifier">gzip</span> <span class="synConstant">on</span>; <span class="synIdentifier">gzip_disable</span> <span class="synConstant">&quot;msie6&quot;</span>; <span class="synIdentifier">proxy_cache_path</span> /var/cache/nginx levels=1:2 keys_zone=default:8m max_size=1000m inactive=24h; <span class="synIdentifier">proxy_temp_path</span> /var/cache/nginx/tmp; <span class="synIdentifier">log_format</span> main <span class="synConstant">'</span><span class="synPreProc">$remote_addr</span><span class="synConstant"> - </span><span class="synPreProc">$remote_user</span><span class="synConstant"> [</span><span class="synPreProc">$time_local</span><span class="synConstant">] &quot;</span><span class="synPreProc">$request</span><span class="synConstant">&quot; '</span> <span class="synConstant">'</span><span class="synPreProc">$status</span><span class="synConstant"> </span><span class="synPreProc">$body_bytes_sent</span><span class="synConstant"> &quot;</span><span class="synPreProc">$http_referer</span><span class="synConstant">&quot; '</span> <span class="synConstant">'&quot;</span><span class="synPreProc">$http_user_agent</span><span class="synConstant">&quot; &quot;</span><span class="synPreProc">$invalid_referer</span><span class="synConstant">&quot;'</span>; <span class="synStatement">upstream</span> old_cmc_stream { <span class="synType">server</span> old_cmc; } <span class="synStatement">upstream</span> new_cmc_stream { <span class="synType">server</span> new_cmc; } <span class="synStatement">server</span> { <span class="synType">listen</span> 80; <span class="synType">server_name</span> localhost; <span class="synIdentifier">proxy_set_header</span> Host <span class="synIdentifier">$http_host</span>; <span class="synIdentifier">proxy_redirect</span> <span class="synConstant">off</span>; <span class="synStatement">location</span> /path_to_a { <span class="synType">proxy_pass</span> http://new_cmc_stream/path_to_a; } <span class="synStatement">location</span> /path_to_b { <span class="synType">proxy_pass</span> http://new_cmc_stream/path_to_b; } <span class="synStatement">location</span> /assets { <span class="synIdentifier">valid_referers</span> ~localhost/path_to_a*; <span class="synIdentifier">valid_referers</span> ~localhost/path_to_b*; <span class="synStatement">if</span> (<span class="synIdentifier">$invalid_referer</span> != <span class="synConstant">&quot;1&quot;</span>) { <span class="synType">proxy_pass</span> http://new_cmc_stream; } <span class="synStatement">if</span> (<span class="synIdentifier">$invalid_referer</span> = <span class="synConstant">&quot;1&quot;</span>) { <span class="synType">proxy_pass</span> http://old_cmc_stream; } } <span class="synStatement">location</span> / { <span class="synType">proxy_pass</span> http://old_cmc_stream; } } } </pre> open8tech 入門!RxSwift hatenablog://entry/17680117126999837801 2019-04-03T11:11:00+09:00 2019-04-03T11:11:00+09:00 こんにちは、オープンエイトでiOSアプリエンジニアをしている常盤です。 通勤時間に小説を読むようにしたらQOLが爆上がりしました。 さて、私は約半年前にオープンエイトにジョインし、その時初めてSwiftに触れました。 弊社のサービスである女性向けおでかけ動画メディア、Letronc。iOSアプリではRxSwiftが使われています。 初めて触るSwiftという言語に加え、Rxという概念を理解するまでは艱難辛苦の日々でした。。。 そこでRx初心者に向けて、どうやって概念を理解してコードを書けるようになったかポイントを伝授しようと思います。 Rx is 何 Rx(Reactive X)とは、「オブザ… <p>こんにちは、オープンエイトで<a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリエンジニアをしている常盤です。 通勤時間に小説を読むようにしたら<a class="keyword" href="http://d.hatena.ne.jp/keyword/QOL">QOL</a>が爆上がりしました。</p> <p>さて、私は約半年前にオープンエイトにジョインし、その時初めてSwiftに触れました。 弊社のサービスである女性向けおでかけ動画メディア、Letronc。<a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリではRxSwiftが使われています。 初めて触るSwiftという言語に加え、Rxという概念を理解するまでは艱難辛苦の日々でした。。。 そこでRx初心者に向けて、どうやって概念を理解してコードを書けるようになったかポイントを伝授しようと思います。</p> <h2>Rx is 何</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190328/20190328180006.png" alt="f:id:open8tech:20190328180006p:plain" title="f:id:open8tech:20190328180006p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>Rx(<a href="http://reactivex.io">Reactive X</a>)とは、「オブザーバパターン」「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%C6%A5%EC%A1%BC%A5%BF">イテレータ</a>パターン」「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%D8%BF%F4%B7%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">関数型プログラミング</a>」の概念を実装している拡張ライブラリです。</p> <p>Rxを導入するメリットは、「値の変化を検知できる」「非同期の処理を簡潔に書ける」ということに尽きると思います。 値の変化というのは変数値の変化やUIの変化も含まれます。 例えばボタンをタッチする、という動作もボタンのステータスが変わったと捉えることができRxを使って記述することができます。</p> <h2>Rxの考え方</h2> <p>私がRxで躓いたポイントは3点あると考えています。</p> <p>1.Rx特有のキーワードがそれぞれどのような働きをしているか理解できていない<br/> 2.メソッドチェーンのような書き方で値の流れが理解できていない<br/> 3.値の変化に応じて発火するイベントが理解できていない</p> <p>...そう...何も理解できていないからである!<br/> しかし理解できていない対象が分かっていれば対処は可能です。</p> <p>1.に関しては基本のキーワードを押さえていき、 2.3.は実装例から紐解いて行こうと思います。</p> <h2>基本のキーワード</h2> <h3>SubjectとRelay</h3> <p>すごく簡単に言うと、イベントの検知に加えイベントの発生もできる便利なラッパークラスです。 代表的としてよく使われる4種類は以下です。<br/> ・PublishSubject<br/> ・BehaviorSubject<br/> ・PublishRelay<br/> ・BehaviorRelay<br/> それぞれ特徴が異なるのですが割愛します。本記事のコードでは<code>BehaviorRelay</code>を使用します。</p> <h3>ObservableとObserver</h3> <p><code>Observable</code>は監視可能なものを表すクラスで通知を行うことができます。 変数xが<code>Observable</code>でラップされているならば、変数xの値が変化する毎に<code>Observer</code>へ通知されます。 Rxではストリーム(stream)とも表現されています。</p> <p><code>Observer</code>は監視を行う側です。<code>Observer</code>は<code>Observable</code>から通知されるあらゆる値に反応することができます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190327/20190327113910.png" alt="f:id:open8tech:20190327113910p:plain" title="f:id:open8tech:20190327113910p:plain" class="hatena-fotolife" itemprop="image"></span></p> <h3>subscribe()</h3> <p>subscribe()はメソッドで、監視する側の働きを持ちます。 <code>Observable</code>から通知を受け取ったらどのような振る舞いをするかを定義します。これをRxでは購読と表現します。 引数である<code>onNext</code>がとる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%ED%A1%BC%A5%B8%A5%E3">クロージャ</a>ー内でやりたい処理を記述します。</p> <h3>DisposeBag</h3> <p>一度購読をし始めるといつまで購読し続けるのか?メモリに残ったままにならないのか?そんな疑問が湧くかと思います。 DisposeBagは購読の破棄をうまいことやってくれて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%E2%A5%EA%A5%EA%A1%BC%A5%AF">メモリリーク</a>を回避するためのクラスです。 イメージとしては、購読を定義した際に返ってくる破棄可能な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>をDisposeBag<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>に入れることで、そのDisposeBag<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>自身が破棄されると同時に購読も破棄してくれます。 購読はビューが破棄(deinit)されるタイミングで一緒に破棄したいことが多いので、ビュー、またはビューコントローラーの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>変数として定義することが多いです。</p> <h2>実際に使ってみた</h2> <p>最低限の実装でも良いのでコードを書いてその働きを追うことが理解への第一歩かと思います。 実装例として数字カウントアプリを考えてみましょう。<br/> ボタンを押すと、ラベルに表示される数字が+1されるというアプリです。これをRxを使って実装していきます。 ここでRxが適用できる処理はどの部分でしょうか?以下の2つの処理はRxを用いて記述することができます。</p> <p>1.カウントボタンを押したら、変数の値を変更する<br/> 2.変数の値が変化したら、変化後の値をビューに表示する</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリでよく見る形のMVVM<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>に当てはめると、実装のイメージは下記図の通りです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190327/20190327113912.png" alt="f:id:open8tech:20190327113912p:plain" title="f:id:open8tech:20190327113912p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>それではコードに落とし込んでいきましょう。 今回MVVM<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>は主題から外れる、かつコードは非常にシンプルなので、処理はすべてビューに集めています。</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// SomeViewController.swift</span> <span class="synPreProc">import</span> UIKit <span class="synPreProc">import</span> RxSwift <span class="synPreProc">import</span> RxCocoa <span class="synPreProc">class</span> <span class="synType">SomeViewController</span><span class="synSpecial">:</span> <span class="synType">UIViewController</span> { <span class="synType">@IBOutlet</span> weak <span class="synPreProc">var</span> <span class="synIdentifier">button</span><span class="synSpecial">:</span> <span class="synType">UIButton</span><span class="synIdentifier">!</span> <span class="synType">@IBOutlet</span> weak <span class="synPreProc">var</span> <span class="synIdentifier">label</span><span class="synSpecial">:</span> <span class="synType">UILabel</span><span class="synIdentifier">!</span> <span class="synPreProc">private</span> <span class="synPreProc">let</span> <span class="synIdentifier">count</span><span class="synSpecial">:</span> <span class="synType">BehaviorRelay</span><span class="synSpecial">&lt;Int&gt;</span> <span class="synIdentifier">=</span> BehaviorRelay(value<span class="synSpecial">:</span> <span class="synConstant">0</span>) <span class="synPreProc">private</span> <span class="synPreProc">let</span> <span class="synIdentifier">disposeBag</span><span class="synSpecial">:</span> <span class="synType">DisposeBag</span> <span class="synIdentifier">=</span> DisposeBag() <span class="synStatement">override</span> <span class="synPreProc">func</span> <span class="synIdentifier">viewDidLoad</span>() { <span class="synIdentifier">super</span>.viewDidLoad() bindButtonToValue() bindCountToText() } <span class="synComment">// 1.の処理</span> <span class="synPreProc">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">bindButtonToValue</span>() { button.rx.tap .subscribe(onNext<span class="synSpecial">:</span> { [weak <span class="synIdentifier">self</span>] _ <span class="synStatement">in</span> <span class="synIdentifier">self</span>?.increment()}) .disposed(by<span class="synSpecial">:</span> <span class="synType">disposeBag</span>) } <span class="synPreProc">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">increment</span>() { count.accept(count.value <span class="synIdentifier">+</span> <span class="synConstant">1</span>) } <span class="synComment">// 2.の処理</span> <span class="synPreProc">private</span> <span class="synPreProc">func</span> <span class="synIdentifier">bindCountToText</span>() { count.asObservable() .subscribe(onNext<span class="synSpecial">:</span> { [weak <span class="synIdentifier">self</span>] count <span class="synStatement">in</span> <span class="synIdentifier">self</span>?.label.text <span class="synIdentifier">=</span> String(count) }) .disposed(by<span class="synSpecial">:</span> <span class="synType">disposeBag</span>) } } </pre> <p>実際の動きは以下のようになります。これ以上ないくらいシンプルです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190327/20190327192116.gif" alt="f:id:open8tech:20190327192116g:plain" title="f:id:open8tech:20190327192116g:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>上の実装例からRxの書き方を紐解いていきます。</p> <h3>メソッドチェーンのような直感的な書き方</h3> <p>Rxはメソッドチェーンのように、メソッドの結果に対して更にメソッドを呼ぶことができます。 この形式はRx特有の特徴ではなく<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%D8%BF%F4%B7%BF%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">関数型プログラミング</a>ではよく見られます。 意識して頂きたいのは各メソッドの返り値です。返り値の型によってチェーンできるメソッドが異なります。</p> <p>bindCountToText()を見てみましょう。これは典型的な変数購読の書き方です。 返り値を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%E1%A5%F3%A5%C8%A5%A2%A5%A6%A5%C8">コメントアウト</a>で表してみます。</p> <pre class="code lang-swift" data-lang="swift" data-unlink> count.asObservable() <span class="synComment">// Observable&lt;Int&gt;</span> .subscribe(onNext<span class="synSpecial">:</span> { [weak <span class="synIdentifier">self</span>] count <span class="synStatement">in</span> <span class="synIdentifier">self</span>?.label.text <span class="synIdentifier">=</span> String(count) }) <span class="synComment">// Disposable</span> .disposed(by<span class="synSpecial">:</span> <span class="synType">disposeBag</span>) <span class="synComment">// 返り値なし</span> </pre> <p>asObservable()メソッドは、返り値として<code>Observable</code>を得ることができます。 基礎キーワードで出てきた<code>Observable</code>、繰り返しになりますがこれは監視可能なクラスです。つまり変数countの値の変化を監視している状態になります。 監視しているだけじゃ何もおきないので、購読をします。値が変化したらやりたい処理をsubscribe()メソッドで定義します。ここでやりたいのはラベルに変化した値をいれることです。<br/> subscribeメソッドの返り値は<code>Disposable</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>。なにか見覚えがありますね?ここで、基礎キーワードに出てきたDisposeBagによる購読の破棄を行います。 disposed(by:)メソッドの引数に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>変数として定義したdisposeBagを与えることで、SomeViewControllerが破棄(deinit)されるとdisposeBag自身も含め購読も破棄してくれます。disposed(by:)メソッドを呼び出したらすぐに購読が破棄されることはありません。</p> <h3>値の変化でイベントが発生</h3> <p>画面上ではボタンを押すと表示される数字がカウントアップするシンプルなものなのでRxを使う旨味がないように思われるかもしれません。 しかし実際のアプリでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>に当てはめたりと複雑性は増します。 上記の実装1、2のように分けて考えることができれば、<code>値の変化を検知しイベントが発生する</code>ことが捉えやすくなるかと思います。 そのイベントに応じて、やりたい処理を簡単に記述できるのがRxの便利なところです。 特に非同期処理(例えば、ネットワークを介した<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>処理)において、従来ではコールバックとして定義していた箇所がRxを適用することによりコールバック地獄から解放され、スッキリとしたコードに収めることができます。</p> <h3>1ステップ先へ</h3> <p>bindCountToText()は以下のようにも書き換えることができます。</p> <pre class="code lang-swift" data-lang="swift" data-unlink> count.asObservable() .map{ String(<span class="synIdentifier">$0</span>) } .bind(to<span class="synSpecial">:</span> <span class="synType">label.rx.text</span>) .disposed(by<span class="synSpecial">:</span> <span class="synType">disposeBag</span>) </pre> <p>今回取り上げてはいませんが、mapというオペレーターを使って<code>Observable</code>から流れてきた値を加工しています。 そして加工した値をbind(to:)メソッドでラベルのテキストに直接バインドしています。<br/> このようにRxでは<code>Observable</code>とViewを直接つないだりすることもできます。(Swift標準のUIKitをRxで扱うにはRxCocoaというライブラリが必要です。そのためRxSwiftと一緒に使われることが多い。)</p> <h2>まとめ</h2> <p>今回、RxSwiftについてまとめました。少しでも概念がイメージできたなら嬉しいです。 Rxは<code>学習コストが高い</code>やら<code>パット見わかりにくい</code>と言われておりますが、慣れていくとすごく便利なライブラリだと感じます。 私はもうRx無しでは生きられない身体になりました。 難しく考えずにほんとにシンプルなことから実装してみるのが一番の近道かもしれません。 ぜひ自分のモノにしてRxのパワーを感じて欲しいです。</p> open8tech 実践!クリーンアーキテクチャ + RxSwift hatenablog://entry/17680117126990728364 2019-03-15T12:00:29+09:00 2019-03-15T12:00:29+09:00 こんにちは、最近ほんのり?ふくよかになったオープンエイトの正原です。 ダイエットをしたいとは思ってはいるのですが、 「まだ寒い、まだ慌てるような時間じゃない」と自分に言い聞かせています。 そもそも一度に10kgのみかんが届くことなどないよう、ふるさと納税は計画的にすべきでしたね。 さて、今回のテックブログなのですが、 最近私が設計しているiOSアプリケーションにおけるクリーンアーキテクチャとRxSwiftを導入する際につまづいた点についてお話したいと思います。 クリーンアーキテクチャ [引用元] : The Clean Code Blog クリーンアーキテクチャはよく分からなくても「この図は見… <p>こんにちは、最近ほんのり?ふくよかになったオープンエイトの正原です。 ダイエットをしたいとは思ってはいるのですが、 「まだ寒い、まだ慌てるような時間じゃない」と自分に言い聞かせています。 そもそも一度に10kgのみかんが届くことなどないよう、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%D5%A4%EB%A4%B5%A4%C8%C7%BC%C0%C7">ふるさと納税</a>は計画的にすべきでしたね。</p> <p>さて、今回のテックブログなのですが、 最近私が設計している<a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリケーションにおけるクリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>とRxSwiftを導入する際につまづいた点についてお話したいと思います。</p> <h2>クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a></h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190310/20190310002048.jpg" alt="f:id:open8tech:20190310002048j:plain" title="f:id:open8tech:20190310002048j:plain" class="hatena-fotolife" itemprop="image"></span> [引用元] : <a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">The Clean Code Blog</a></p> <p>クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>はよく分からなくても「この図は見たことある!」という方も多いのではないでしょうか? 2012年にRobert C. Martin (Uncle Bob)氏が<a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">The Clean Code Blog</a>にて示した図です。 この図では主に「関心事の分離」と「依存性のルール」を表現しているのですが、 この図に関しては前述の本家のブログやその<a href="https://blog.tai2.net/the_clean_architecture.html">日本語訳版</a>、 <a href="https://www.amazon.co.jp/dp/4048930656">Clean Architecture 達人に学ぶソフトウェアの構造と設計</a>、または多くのテックブログにて説明されているのでここでは割愛します。</p> <p><a href="https://www.amazon.co.jp/dp/4048930656">Clean Architecture 達人に学ぶソフトウェアの構造と設計</a>はソフトウェア開発における問題に対して、 クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>(またはクリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>で使われるテクニック)でどのように解消するかが分かりやすく書いてあるので、 一度は読んでおいて損はないと思います!ぜひぜひ!</p> <h2>どんな構成にしたの?</h2> <p>恐らく、多くの方が最初は私と同様に「理屈はなんとなく分かるけど、どのように実装したら良いかが分からない」といった状況になるのではないでしょうか。 私は多くのテックブログや<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>上にあるサンプルアプリケーションを参考にして以下のような構成にしました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190310/20190310035102.png" alt="f:id:open8tech:20190310035102p:plain" title="f:id:open8tech:20190310035102p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>以降ではこの構成を基に今回のトピックである「どのようにつまづいたり転んだりしたか」をみていきます。</p> <h2>参照と依存の向きによる違和感</h2> <p>クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>を勉強したら「実際に少し書いてみよう」となるでしょう。 しかし、実際に書くとなると何か参考になる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>が欲しくなると思います。 そんなとき、<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>で公開されているサンプルアプリケーションが非常に役立ちます。</p> <h3>ビューコントローラとプレゼンター</h3> <p>では、まずエントリポイントとなるビューコントローラとプレゼンターを見ていきたいと思います。</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// SomeViewController.swift</span> <span class="synPreProc">class</span> <span class="synType">SomeViewController</span><span class="synSpecial">:</span> <span class="synType">UIViewController</span> { <span class="synPreProc">var</span> <span class="synIdentifier">presenter</span><span class="synSpecial">:</span> <span class="synType">SomePresenter</span>? <span class="synStatement">override</span> <span class="synPreProc">func</span> <span class="synIdentifier">viewDidLoad</span>() { <span class="synIdentifier">super</span>.viewDidLoad() presenter?.doByPresenter() } } <span class="synPreProc">extension</span> <span class="synType">SomeViewController</span><span class="synSpecial">:</span> <span class="synType">SomePresenterDelegate</span> { <span class="synPreProc">func</span> <span class="synIdentifier">doByDelegate</span>() { } } </pre> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// SomePresenter.swift</span> <span class="synPreProc">protocol</span> <span class="synType">SomePresenter</span> { <span class="synPreProc">func</span> <span class="synIdentifier">doByPresenter</span>() } <span class="synPreProc">protocol</span> <span class="synType">SomePresenterDelegate</span><span class="synSpecial">:</span> <span class="synType">AnyObject</span> { <span class="synPreProc">func</span> <span class="synIdentifier">doByDelegate</span>() } <span class="synPreProc">class</span> <span class="synType">SomePresenterImpl</span><span class="synSpecial">:</span> <span class="synType">SomePresenter</span> { weak <span class="synPreProc">var</span> <span class="synIdentifier">delegate</span><span class="synSpecial">:</span> <span class="synType">SomePresenterDelegate</span>? <span class="synPreProc">func</span> <span class="synIdentifier">doByPresenter</span>() { } } </pre> <p>ビューコントローラおよびプレゼンターはお互いインターフェース(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C8%A5%B3%A5%EB">プロトコル</a>)のみに依存して詳細を知らないようにします。 そしてDIコンテナにて以下のように依存性を注入すると思います。</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">let</span> <span class="synIdentifier">viewController</span> <span class="synIdentifier">=</span> SomeViewController() <span class="synPreProc">let</span> <span class="synIdentifier">presenter</span> <span class="synIdentifier">=</span> SomePresenterImpl() viewController.presenter <span class="synIdentifier">=</span> presenter presenter.delegate <span class="synIdentifier">=</span> viewController </pre> <p>ここまではまだ違和感を抱かないで理解できるのではないでしょうか。 ちなみに、同じファイルに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C8%A5%B3%A5%EB">プロトコル</a>とその実装を書くことで、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Xcode">Xcode</a>でジャンプしても迷子にならずにすみます。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%BB%B2%BE%C8%B8%B5">参照元</a>が要求しているのは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C8%A5%B3%A5%EB">プロトコル</a>なので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%C8%A5%B3%A5%EB">プロトコル</a>定義にジャンプしてしまい、 実装まで直接ジャンプできなくなってしまうんですね。</p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B2%A1%BC%A5%C8%A5%A6%A5%A7%A5%A4">ゲートウェイ</a>とレポジトリ</h3> <p>では、次に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B2%A1%BC%A5%C8%A5%A6%A5%A7%A5%A4">ゲートウェイ</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を見ていきたいと思います。</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// SomeGateway.swift</span> <span class="synPreProc">protocol</span> <span class="synType">SomeGateway</span> { <span class="synPreProc">func</span> <span class="synIdentifier">doByGateway</span>() } <span class="synPreProc">protocol</span> <span class="synType">SomeGatewayDelegate</span><span class="synSpecial">:</span> <span class="synType">AnyObject</span> { <span class="synPreProc">func</span> <span class="synIdentifier">doByUseCase</span>() } <span class="synPreProc">class</span> <span class="synType">SomeGatewayImpl</span><span class="synSpecial">:</span> <span class="synType">SomeGateway</span> { <span class="synPreProc">private</span> <span class="synPreProc">let</span> <span class="synIdentifier">repository</span><span class="synSpecial">:</span> <span class="synType">SomeRepository</span> weak <span class="synPreProc">var</span> <span class="synIdentifier">delegate</span><span class="synSpecial">:</span> <span class="synType">SomeGatewayDelegate</span>? <span class="synIdentifier">init</span>(repository<span class="synSpecial">:</span> <span class="synType">SomeRepository</span>) { <span class="synIdentifier">self</span>.repository <span class="synIdentifier">=</span> repository } <span class="synPreProc">func</span> <span class="synIdentifier">doByGateway</span>() { } } </pre> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// SomeRepository.swift</span> <span class="synPreProc">protocol</span> <span class="synType">SomeRepository</span> { <span class="synPreProc">func</span> <span class="synIdentifier">doByRepository</span>() } <span class="synPreProc">protocol</span> <span class="synType">SomeRepositoryDelegate</span><span class="synSpecial">:</span> <span class="synType">AnyObject</span> { <span class="synPreProc">func</span> <span class="synIdentifier">doByGateway</span>() } <span class="synPreProc">class</span> <span class="synType">SomeRepositoryImpl</span><span class="synSpecial">:</span> <span class="synType">SomeRepository</span> { weak <span class="synPreProc">var</span> <span class="synIdentifier">delegate</span><span class="synSpecial">:</span> <span class="synType">SomeRepositoryDelegate</span>? <span class="synPreProc">func</span> <span class="synIdentifier">doByRepository</span>() { } } </pre> <p>この辺で「ん?<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B2%A1%BC%A5%C8%A5%A6%A5%A7%A5%A4">ゲートウェイ</a>がレポジトリを持つ形になって良いのか・・・?」となりました。 先程の構成図に依存性の方向を入れた図を見てみたいと思います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190311/20190311100910.png" alt="f:id:open8tech:20190311100910p:plain" title="f:id:open8tech:20190311100910p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>レポジトリは最も円の外側であるDBや<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>などの詳細が含まれている領域です。 それは正しいのですが、なぜ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B2%A1%BC%A5%C8%A5%A6%A5%A7%A5%A4">ゲートウェイ</a>がレポジトリを持つような形になってしまうのかが違和感でした。 ですが、それもそのはず。<a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>では基本的にビューコントローラが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BB%B2%BE%C8%B8%B5">参照元</a>となり、 プレゼンターや<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B2%A1%BC%A5%C8%A5%A6%A5%A7%A5%A4">ゲートウェイ</a>、レポジトリの順に参照されているからです。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%BB%B2%BE%C8%B8%B5">参照元</a>と参照先の関係は以下の図のようになると思います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190311/20190311100919.png" alt="f:id:open8tech:20190311100919p:plain" title="f:id:open8tech:20190311100919p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>これは恐らく、ビューコントローラが破棄された際、 その画面を構成する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>も同様に破棄されて欲しいためだと思います。 ですが、クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>の依存性の方向と参照の方向が異なるため、 このような違和感を抱くことになったんですね。</p> <p>(もっと上手にやれば参照の方向なども自然な設計になったりするんでしょうか?)</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>とはどのようなもので何を実装するか?</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>とはどのようなものでしょうか? 本家のブログや書籍には「アプリケーション固有のビジネスルール」とありますが、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>から取得したデータをビューに表示するだけのシンプルなアプリの場合、 恥ずかしながら何が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>に相当するのかがイマイチ想像できませんでした。</p> <p>こんなとき頼りになるのがグーグル検索です。 そこで「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>」と検索しますと、様々な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>図が出てきて気づきました。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリケーションにおいては純粋にユーザーのアクションで良いのではないか?と。 例えば、皆さんが知ってる<a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>アプリなどの場合、以下のようになるかと思います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/open8tech/20190311/20190311121355.png" alt="f:id:open8tech:20190311121355p:plain" title="f:id:open8tech:20190311121355p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>アプリケーションでなくとも、多くのアプリケーションにおいて 「お気に入り」や「再読み込み」、「追加読み込み」などの機能があると思います。 これらユーザーのアクションを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>とするのが<a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリケーションにとって 自然なのではないでしょうか。</p> <p>(クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>の「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>」にとらわれて広義での「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>」がすっぽり抜け落ちてたのは内緒です)</p> <h2>エンティティとRxSwift</h2> <p>皆さんRxSwift使っていますでしょうか? タイトルに入れたくせにこれまで全く触れられてこなかったRxSwiftについてですが、個人的にはとても好きなライブラリです。 さて、クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>を用いた実装でRxSwiftをどのように使うかですが、意外とエンティティと相性が良いのではないでしょうか。 (ビュー層はこれまでと同様の使い方をすると思います。また必ずしもエンティティにしなければならないわけではないと思います)</p> <p>例えば、テーブルビューやコレクションビューにて1つ1つのセルを構成するデータを<code>SomeData</code>として、 そのデータの配列を <code>SomeDataList</code> とした場合、以下のような実装になります。</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// SomeData.swift</span> <span class="synPreProc">struct</span> <span class="synType">SomeData</span> { <span class="synPreProc">let</span> <span class="synIdentifier">title</span><span class="synSpecial">:</span> <span class="synType">String</span> <span class="synPreProc">let</span> <span class="synIdentifier">description</span><span class="synSpecial">:</span> <span class="synType">String</span> } </pre> <p>(以下の <code>SomeDataList</code> では<code>BehaviorRelay</code>を用いていますので<code>RxCocoa</code>のインポートが必要です。)</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synComment">// SomeDataList.swift</span> <span class="synPreProc">import</span> RxSwift <span class="synPreProc">import</span> RxCocoa <span class="synPreProc">class</span> <span class="synType">SomeDataList</span> { <span class="synPreProc">let</span> <span class="synIdentifier">list</span><span class="synSpecial">:</span> <span class="synType">BehaviorRelay</span><span class="synSpecial">&lt;[SomeData]&gt;</span> <span class="synPreProc">var</span> <span class="synIdentifier">stream</span><span class="synSpecial">:</span> <span class="synType">Observable</span><span class="synSpecial">&lt;[SomeData]&gt;</span> { <span class="synStatement">return</span> list.asObservable() } <span class="synIdentifier">init</span>(value<span class="synSpecial">:</span> <span class="synPreProc">[SomeData]</span> <span class="synIdentifier">=</span> []) { list <span class="synIdentifier">=</span> BehaviorRelay(value<span class="synSpecial">:</span> <span class="synType">value</span>) } <span class="synPreProc">func</span> <span class="synIdentifier">set</span>(value<span class="synSpecial">:</span> <span class="synPreProc">[SomeData]</span>) { list.accept(value) } <span class="synPreProc">func</span> <span class="synIdentifier">add</span>(value<span class="synSpecial">:</span> <span class="synPreProc">[SomeData]</span>) { list.accept(list.value <span class="synIdentifier">+</span> value) } } </pre> <p>このように <code>Observable</code> で観測可能な状態にして<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>からプレゼンターへ、 そしてプレゼンターで <code>Driver</code> にしてからビューコントローラへ渡していくことで ビュー層では観測するだけですむのではないかと思います。 詳細な部分は違うかもしれませんが、目的やイメージとしてはfluxに近いかもしれません。</p> <p>またエンティティ(<code>SomeDataList</code>)が扱う<code>[SomeData]</code>ですが、これは最初の構成図における<code>data</code>に相当します。 クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>では「境界線を超えるデータは単純なデータ構造で構成されている」とあるので、 <code>struct</code>で定義した単純なデータ構造の配列である<code>[SomeData]</code>は境界線を超えても良いのではないかと思いました。</p> <p>最後に、最初の構成図でエンティティが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>をまたがっている部分についてですが、 同一のエンティティを異なる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>で使うパターンがあるかもしれない、と思ったからです。 ログイン・ログアウトのような場合は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>を再利用したら良いかもしれませんが、 上記のようなデータの再読み込みと追加読み込みなどは、ユーザーのアクションは異なりますが、 扱うエンティティは同じ方が都合が良いと思います。</p> <p>ただ、データの再読み込みと追加読み込みに関しては、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B2%A1%BC%A5%C8%A5%A6%A5%A7%A5%A4">ゲートウェイ</a>やレポジトリも同じ方が都合が良いため、 今のところ「ロード」という1つの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>に集約しています。 (もしかしたらそのようなエンティティは存在しない・・・!?)</p> <h2>試してみた感想</h2> <p>まだ直面していないので自信を持って言えないですが、 利点としてはやはり「仕様変更に強そう」ではないでしょうか。 ですが、サービスそのものが開発途上の<a class="keyword" href="http://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリケーションにおいては、 中心の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>の仕様変更も少なくないのが少々怖いところです。</p> <p>逆に欠点ですが、やはり「ファイル数が多い」と「理解が難しい」です。 「理解が難しい」は勉強しろと言われればそれまでかもしれませんが、 チーム開発を行う上では全員に理解してもらわないといけないので 勉強会を行うなどしないといけないと思います。</p> <h2>まとめ</h2> <p>いかがでしたでしょうか? 正直、まだまだ実装している最中なので何か不都合が出てこないか、 そもそも自分のクリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>の理解が間違っていないか毎日ドキドキしています。 また、今回お見せした例は「こうあるべきだ」とか「こうしないといけない」というものでもないです。 ですが、クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>を勉強を通して「こうしたらもっと仕様の変更に強くなりそうだ」というのが 以前より多くなったと思いますので、(プロダクトに導入するかはともかく)一度本家のブログだけでも 読んでみてはいかがでしょうか。</p> open8tech styled-componentsのスタイル拡張について hatenablog://entry/10257846132708065690 2019-01-25T11:39:16+09:00 2019-01-25T13:47:19+09:00 2018年度に新卒で入社した東度です。配属当初はRailsでサーバーサイドを書いていたのですが、最近はReactでフロントを書いています。 自社プロダクトの一つであるVIDEO BRAINでは、CSS in JSのライブラリの一つであるstyled-componentsを使ってスタイリングをしているのですが、中でも度々登場するスタイル拡張について自身の勉強も兼ねて記事をまとめてみました。 styled-componentsとは 現在、CSS in JSの中で最も人気のあるライブラリで、要素にスタイルを付加したコンポーネントが作成できるというのが特徴です。 styled.div``のように記述す… <p>2018年度に新卒で入社した東度です。配属当初は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>でサーバーサイドを書いていたのですが、最近はReactでフロントを書いています。</p> <p>自社プロダクトの一つであるVIDEO BRAINでは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a> in JSのライブラリの一つである<a href="https://github.com/styled-components/styled-components">styled-components</a>を使ってスタイリングをしているのですが、中でも度々登場するスタイル拡張について自身の勉強も兼ねて記事をまとめてみました。</p> <h2>styled-componentsとは</h2> <p>現在、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a> in JSの中で最も人気のあるライブラリで、要素にスタイルを付加した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>が作成できるというのが特徴です。</p> <p> <code>styled.div``</code>のように記述すると、div要素にタグ付きテンプテート<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>内のスタイルを付加した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>が返される仕組みとなっています。</p> <p>以下の例では、button要素にスタイルを当てた<code>Button</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を定義しています。 定義した<code>Button</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を呼び出すと以下のボタンが出力されます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// Button.js</span> <span class="synStatement">import</span> styled from <span class="synConstant">'styled-components'</span>; <span class="synStatement">export</span> <span class="synStatement">const</span> Button = styled.button` margin: 1em; padding: 0.25em 1em; border: none; outline: none; border-radius: 3px; color: #fff; background-color: #404143; `; </pre> <p><a href="https://gyazo.com/942c9e825769ac47985342a3b4b9fdd9"><img src="https://i.gyazo.com/942c9e825769ac47985342a3b4b9fdd9.png" alt="Image from Gyazo" /></a></p> <h2>スタイル拡張について</h2> <p>styledされた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>に対して<code>styled()</code>コンスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タでラップすると、スタイルを拡張した新しい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を作成することができます。</p> <p>例えば<code>Button</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の場合、色やサイズなどを調整して新しい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を作成したい場合などがあるかと思います。以下の例では<code>Button</code><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>を継承し、スタイルを拡張した新しいボタン作成しています。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// Button.js</span> <span class="synStatement">import</span> styled from <span class="synConstant">'styled-components'</span>; <span class="synStatement">export</span> <span class="synStatement">const</span> Button = styled.button` margin: 1em; padding: 0.25em 1em; border: none; outline: none; border-radius: 3px; color: #fff; background-color: #404143; `; <span class="synStatement">export</span> <span class="synStatement">const</span> RedButton = styled(Button)` background-color: #ff0000; `; <span class="synStatement">export</span> <span class="synStatement">const</span> GreenButton = styled(Button)` background-color: #00ff00; ` <span class="synStatement">export</span> <span class="synStatement">const</span> BlueButton = styled(Button)` background-color: #0000ff; ` </pre> <p><a href="https://gyazo.com/a18c2b986313927e6e9f71aaf6156da8"><img src="https://i.gyazo.com/a18c2b986313927e6e9f71aaf6156da8.png" alt="Image from Gyazo" /></a></p> <p>次に、styledされていない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>に対してスタイルを拡張する方法を紹介します。</p> <p>styled-componentsでは、classNameに指定したスタイルを渡すことでスタイルを適用しています。 styledされていない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>に対しては、以下のように、propsで渡されたclassNameを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>のDOM要素に適用することで、スタイル拡張が行えるようになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// Hello.js</span> <span class="synStatement">import</span> React from <span class="synConstant">'react'</span>; <span class="synStatement">import</span> styled from <span class="synConstant">'styled-components'</span>; <span class="synStatement">export</span> <span class="synIdentifier">function</span> Hello(<span class="synIdentifier">{</span>className, children<span class="synIdentifier">}</span>) <span class="synIdentifier">{</span> <span class="synStatement">return</span>( &lt;a className=<span class="synIdentifier">{</span>className<span class="synIdentifier">}</span>&gt; <span class="synIdentifier">{</span>children<span class="synIdentifier">}</span> &lt;/a&gt; ); <span class="synIdentifier">}</span> <span class="synStatement">export</span> <span class="synStatement">const</span> StyledHello = styled(Hello)` color: #7F9DB5; font-size: 30px; font-weight: bold; `; </pre> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// App.js</span> <span class="synStatement">import</span> React, <span class="synIdentifier">{</span> Component <span class="synIdentifier">}</span> from <span class="synConstant">'react'</span>; <span class="synStatement">import</span> <span class="synIdentifier">{</span> Hello, StyledHello <span class="synIdentifier">}</span> from <span class="synConstant">'./Hello.js'</span> <span class="synStatement">class</span> App <span class="synStatement">extends</span> Component <span class="synIdentifier">{</span> render() <span class="synIdentifier">{</span> <span class="synStatement">return</span> ( &lt;div&gt; &lt;Hello&gt;Hello&lt;/Hello&gt; &lt;br /&gt; &lt;StyledHello&gt;Hello&lt;/StyledHello&gt; &lt;/div&gt; ); <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">export</span> <span class="synStatement">default</span> App; </pre> <p><a href="https://gyazo.com/95444c70fe7c7009fe4ae30ca08f470f"><img src="https://i.gyazo.com/95444c70fe7c7009fe4ae30ca08f470f.png" alt="Image from Gyazo" /></a></p> <h2>まとめ</h2> <p>今回はstyled-componentsを使ったスタイル拡張について記事をまとめました。今後React等に触れていく中で、得られた知見などを共有できたらと思います。</p> <p>最後に、告知になるのですが1月29日(火)に「スタートアップテック vol.2:スタートアップとフロントエンド」が開催されます。 弊社からも澤木(<a href="https://www.twitter.com/ayatas0623">@ayatas0623</a>)が「reduxのstate設計の話」について発表します。彼の勇姿を是非ご観覧ください!</p> <p>イベントの詳細については下記のリンクからご確認ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fconnpass.com%2Fevent%2F113502%2F" title="スタートアップテック vol.2:スタートアップとフロントエンド (2019/01/29 19:30〜)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://connpass.com/event/113502/">connpass.com</a></cite></p> open8tech 「スタートアップテック vol.1」を開催しました! hatenablog://entry/10257846132686086717 2018-12-21T16:24:08+09:00 2018-12-21T16:24:08+09:00 こんにちは、インフラ・データ基盤部の川島です。最近寒くなってきましたね。今年も終わりますね。心はすでに年末休みです。 先日、エンジニア交流イベント「スタートアップテック vol.1」を開催しました。 今回のテーマは、みんなが大好きな「Ruby」です。弊社のプロダクトもほとんど全てと言ってもいいくらいRubyを使っています。 主催枠として、私もLTを行ったので、発表内容を紹介できればと思います。 発表内容 LTのテーマは、「Rubyとffmpegのfilter_complex」について発表しました。オープンエイトのプロダクト全てが動画をベースに展開しているため、Rubyと同じく、全てのプロダクト… <p><a href="https://connpass-tokyo.s3.amazonaws.com/thumbs/87/36/87360719e01f4b0d089645f47e2f7d45.png" class="http-image" target="_blank"><img src="https://connpass-tokyo.s3.amazonaws.com/thumbs/87/36/87360719e01f4b0d089645f47e2f7d45.png" class="http-image" alt="https://connpass-tokyo.s3.amazonaws.com/thumbs/87/36/87360719e01f4b0d089645f47e2f7d45.png"></a></p> <p>こんにちは、インフラ・データ基盤部の川島です。最近寒くなってきましたね。今年も終わりますね。心はすでに年末休みです。</p> <p>先日、エンジニア交流イベント「スタートアップテック vol.1」を開催しました。</p> <p>今回のテーマは、みんなが大好きな「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>」です。弊社のプロダクトもほとんど全てと言ってもいいくらい<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>を使っています。 主催枠として、私もLTを行ったので、発表内容を紹介できればと思います。</p> <h2>発表内容</h2> <p>LTのテーマは、「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/ffmpeg">ffmpeg</a>のfilter_complex」について発表しました。オープンエイトのプロダクト全てが動画をベースに展開しているため、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>と同じく、全てのプロダクトで<a class="keyword" href="http://d.hatena.ne.jp/keyword/ffmpeg">ffmpeg</a>を使っています。最近のプロジェクトの中での<a class="keyword" href="http://d.hatena.ne.jp/keyword/ffmpeg">ffmpeg</a>を活用した実装事例について紹介しました。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/ffmpeg">ffmpeg</a> は動画や音声に対して、非常に多くのことができる優秀なツールです。基本的に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%DE%A5%F3%A5%C9%A5%E9%A5%A4%A5%F3">コマンドライン</a>で操作を行うため、多くのオプションを設定しなければいけません。</p> <p>下記のコマンドが実際に使用したものになります。</p> <pre class="code" data-lang="" data-unlink>ffmpeg -y -r 15 \ -i ./image/%d.jpg -i 01.mp3 -i 02.mp3 \ -filter_complex &#34;[0:v]fps=fps=15[img];[1:a]atrim=duration=20.0,afade=type=out:start_time=19.0:duration=1.0,asetpts=PTS-STARTPTS[OA1]; [OA1]amerge=inputs=1[a]&#34; \ -map [img] -map [a] -s 640x360 -crf 30 -movflags faststart -vcodec libx264 -pix_fmt yuv420p -q:a 0 -strict -2 \ output.mp4</pre> <p>プロジェクトの中では、このコマンドを <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> で生成しています。 LTの中では、複数あるオプションの中でも、 filter_complex 部分に焦点を絞って紹介しました。</p> <p>filter_complexは大きく3つの構成に分類することができます。</p> <ul> <li>filter</li> <li>filter_chain</li> <li>filter_graph</li> </ul> <pre class="code" data-lang="" data-unlink>fps=fps=15 # filter [0:v]fps=fps=15[img] # filter_chain &#34;[0:v]fps=fps=15[img];....&#34; # filter_grpah</pre> <p><code>filter_graph[filter_chian[filter]]</code> という形でそれぞれが内包されているイメージです。</p> <p>コードで実装する場合にも、この構成を元にクラス定義していきます。</p> <pre class="code" data-lang="" data-unlink>filter class filter_chain class ( N filters ) filter_graph class ( N filter_chains ( N filters ))</pre> <p>対象の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リの構成としては下記になります。</p> <pre class="code" data-lang="" data-unlink>- filter_complex │ └──-filter │ │ xx_filter.rb │ └───filter_chain │ │ xx_chain.rb │ └───filter_graph │ xx_graph.rb</pre> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BE%C1%B0%B6%F5%B4%D6">名前空間</a>、filter毎のクラスで分けることによって、それぞれのfilterの責務を明確にすることができます。また、追加したいfilterがあったとしても、filter, filter_chain, filter_graph をセットで追加することで、容易に作成ができます。</p> <h2>まとめ</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/ffmpeg">ffmpeg</a>は、本当にたくさんのことができるツールです。この記事で<a class="keyword" href="http://d.hatena.ne.jp/keyword/ffmpeg">ffmpeg</a>、特にfilter_complex に興味を持っていただけるとうれしいです。</p> <h2>次回のイベント告知</h2> <p>1/29(火)にも、弊社主催のLT大会を行なっていきます。次回のテーマは「フロント」です。 また、「一緒に勉強会を行いたい!」などのご要望がありましたら、ぜひご連絡<a href="https://twitter.com/hisatake">(@hisatake)</a>をお待ちしております!</p> <h2>おまけ</h2> <p>コードなどの内容は下記のスライドに掲載してあります。</p> <p><iframe src="https://www.slideshare.net/slideshow/embed_code/key/rPY1jH0qsopP5S" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="https://www.slideshare.net/ssuser551c92/ruby-ffmpeg-filtercomplex-126073733" title="Ruby で ffmpeg の filter_complex と戯れる話" target="_blank">Ruby で ffmpeg の filter_complex と戯れる話</a> </strong> from <strong><a href="https://www.slideshare.net/ssuser551c92" target="_blank">ssuser551c92</a></strong> </div><cite class="hatena-citation"><a href="https://www.slideshare.net/ssuser551c92/ruby-ffmpeg-filtercomplex-126073733">www.slideshare.net</a></cite></p> open8tech 技術書典にサークル参加してきました hatenablog://entry/10257846132670354922 2018-11-22T14:34:48+09:00 2018-11-22T19:33:31+09:00 こんにちは。初めまして。 OPEN8の大津( @14__oz )です。 技術ブログを始めることになりました!! 編集長に抜擢され、初回の内容を任されています。 頑張っていきますのでよろしくお願いします! OPEN8には2017年新卒エンジニアとして入社し、ルトロンのアンドロイドアプリを初期開発からやってきました。 ReactNativeを使っています。 詳しくは、Wantedlyインタビュー記事があるので読んでみてください。 www.wantedly.com 早速、初回のテーマですが、「技術書典5」に出展したときの話をします! 新機能の開発が忙しいなか、時間を作って書き上げた技術書を頒布してき… <p>こんにちは。初めまして。 OPEN8の大津( <a href="https://twitter.com/14__oz">@14__oz</a> )です。</p> <p>技術ブログを始めることになりました!! 編集長に抜擢され、初回の内容を任されています。 頑張っていきますのでよろしくお願いします!</p> <p>OPEN8には2017年新卒エンジニアとして入社し、ルトロンのアンドロイドアプリを初期開発からやってきました。 ReactNativeを使っています。</p> <p>詳しくは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wantedly">Wantedly</a>インタビュー記事があるので読んでみてください。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fopen8%2Fpost_articles%2F140081" title="ワクワクは行動力を大きくする。新卒一期生エンジニアの声 | MEMBER’S VOICE CALL" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/open8/post_articles/140081">www.wantedly.com</a></cite></p> <p>早速、初回のテーマですが、「技術書典5」に出展したときの話をします! 新機能の開発が忙しいなか、時間を作って書き上げた技術書を頒布してきました。 内容は、ReactNativeのアニメーションについて書いています。 今更ですが、振り返っていきます(笑)。</p> <h2>技術書典</h2> <p><a href="https://techbookfest.org">技術書典</a>とは、技術書<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%F3%A5%EA%A1%BC%A5%A4%A5%D9%A5%F3%A5%C8">オンリーイベント</a>です。 技術者のための<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%DF%A5%B1">コミケ</a>をイメージしてもらえると良さそうです。 出展者が普段から使っている・興味のある技術のノウハウをギュッと技術書に詰め込んでいて、とても読み応えがあります。 かなり幅の広いジャンルが揃っていて、内容も初学者に向けたものから、ニッチなものまで様々です。 どの技術書も販売されているものに劣らない内容となっています。</p> <p>もともと興味があったなか、CTOの石橋( <a href="https://twitter.com/hisatake">@hisatake</a> )からの後押しでチャレンジしてみることになり、 10/8(月)に池袋<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A5%F3%A5%B7%A5%E3%A5%A4%A5%F3%A5%B7%A5%C6%A5%A3">サンシャインシティ</a>にて開催された技術書典5に出展してきました。</p> <p>当日は、およそ10,000人も来場されたそうです! 前回の来場者数が、およそ6,000人で、技術書典の規模がどんどん拡大していますね。 今後も楽しみです!</p> <p><blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">これにて <a href="https://twitter.com/hashtag/%E6%8A%80%E8%A1%93%E6%9B%B8%E5%85%B8?src=hash&amp;ref_src=twsrc%5Etfw">#技術書典</a> 閉会します。ご来場、誠にありがとうございました。総来場者数は のべ10341人、うちサークル・スタッフ等関係者は889人でした。</p>&mdash; 技術書典公式アカウント (@techbookfest) <a href="https://twitter.com/techbookfest/status/1049208448372465664?ref_src=twsrc%5Etfw">2018年10月8日</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <h2>初めてのサークル参加</h2> <p>技術書典5に無事当選はしたものの、当時は、内容さえ決まって書き始めれば何とかなるだろうという見切り発進をしていました。</p> <ul> <li>即売会にサークル参加する</li> <li>技術書を書く</li> <li>書籍を作る</li> </ul> <p>執筆や書籍を作ろうとした事もないのに、余裕をこいていたのはどうしてなんでしょう・・・。</p> <p>後々に大変痛い目をみる羽目に・・・。</p> <h2>技術書の内容</h2> <p>頒布した技術書の内容は、</p> <ul> <li>業務ではAnimatedを使う機会がほぼなかった事</li> <li>新卒当時は難しくて断念していたアニメーションにリベンジしたかった</li> <li>実際に動かして、理解したかった</li> </ul> <p>と言った理由から、ReactNativeのAnimatedクラス・<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>をテーマとして選んでみました。</p> <p>技術書を書くにあたり、ReactNativeでパズルアプリを作りました。 アプリ内でAnimatedを用いた部分のコードを載せて解説しています。 Animatedとスクロールの連携やinterpolateを用いた例を書いています。</p> <p>Easingの使い方や、PanResponderを用いたフリックの取得も書いています。 知らないことが多かったので、ReactNative本体の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を読んで理解しことや、 AnimationLayoutなど、一通りアニメーションに必要なものを含んだ内容になっています。</p> <p>書籍の最後には、およそ1年ReactNativeを使ってきて、印象的な出来事を中心にトピックとして残しておくことにしました。</p> <h2>当日の様子</h2> <p>開場1時間前には、列ができていました。 この時点で早くも自分のテンションが上がり始めます。</p> <p><blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">先ほど、一般参加者の列を、形成し始めました。一般参加の皆さんは、会場前廊下の再奥、広場までどうぞ。 <a href="https://twitter.com/hashtag/%E6%8A%80%E8%A1%93%E6%9B%B8%E5%85%B8?src=hash&amp;ref_src=twsrc%5Etfw">#技術書典</a> <a href="https://t.co/jdf4LLQ6Iv">pic.twitter.com/jdf4LLQ6Iv</a></p>&mdash; 技術書典公式アカウント (@techbookfest) <a href="https://twitter.com/techbookfest/status/1049090927686344704?ref_src=twsrc%5Etfw">2018年10月8日</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p>緑色の服を来た運営の方たちに出迎えてもらい、自分のブースへと向かいます。 ブースに作った技術書が直接届いていたので荷物整理をしていました。</p> <p>一息ついてからの、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%AB%C9%F5">開封</a>の儀。</p> <p><blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%AB%C9%F5">開封</a>の儀 <a href="https://t.co/4yVagrufAw">pic.twitter.com/4yVagrufAw</a></p>&mdash; おず。 (@14__oz) <a href="https://twitter.com/14__oz/status/1049102280610406401?ref_src=twsrc%5Etfw">2018年10月8日</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p>そして、ブースの設営をしました。 必要そうなものの大半は前日・前々日に100均に駆け込んで買い揃えました。 書籍の内容がReactNativeだったので<a class="keyword" href="http://d.hatena.ne.jp/keyword/Facebook">Facebook</a>の服を着て待機。</p> <p><blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ブース設営!<br>う-77にてお待ちしています。<a href="https://twitter.com/hashtag/%E6%8A%80%E8%A1%93%E6%9B%B8%E5%85%B85?src=hash&amp;ref_src=twsrc%5Etfw">#技術書典5</a><a href="https://twitter.com/hashtag/%E5%88%9D%E5%8F%82%E5%8A%A0?src=hash&amp;ref_src=twsrc%5Etfw">#初参加</a> <a href="https://t.co/tkBP2n2FAw">pic.twitter.com/tkBP2n2FAw</a></p>&mdash; おず。 (@14__oz) <a href="https://twitter.com/14__oz/status/1049113914217193473?ref_src=twsrc%5Etfw">2018年10月8日</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p>始まるまでに時間が少しばかり余ったので、顔見知り方のブースに挨拶に行ったり、 となりのブースの方と話していました。 なんだかんだ、お隣の方とは1日中話していました。</p> <p>始まってすぐに3名の方がブースに来てくれたのは、かなり嬉しかったです。 そのあとは、入場規制の関係で人の波が一定間隔で押し寄せるといった感じでした。 ブースに来て見本誌を読んでくれる方がたくさんいて、読まれているときの緊張感は計り知れないです。 じっと観察されているような気分でした。 見本誌を見て買っていただけた時は嬉しくて顔に出ていたかもしれないですね。 同僚や知り合いなど、たくさんの方にブースへ来てもらえたのでありがたかったです。</p> <p>最終的には、50部ほど頒布できました。 ただ、今だから思うと、調子に乗って100部刷ったのは完全にミスですね。 余ってしまったので、今でも家に眠っています。 また、機会があれば頒布しようと思います。</p> <h2>失敗したこと</h2> <p>失敗したことはたくさんあって、</p> <ul> <li>部数を刷りすぎた</li> <li>表紙・裏表紙の入稿データの形式が違っていた</li> <li>PDFの用紙サイズが間違っていた</li> <li>書く時間をほとんど取れなかった</li> </ul> <p>などなど、出し始めるとキリがなさそうです(笑)。</p> <p>見切り発進した結果、ギリギリまで入稿方法について調べずにいました。 いざ、入稿するぞってときにノンブルがない、PDFの用紙サイズが違うなど、かなり焦りました。 表紙は塗り足しがなくて作り直しと、PDF同様にダメな点がありました。 事前確認を怠り、行き当たりばったりで良くなかったですが、ノウハウを得たので次は活かせられそうです。</p> <p>一番キツかったのは、書く時間を取れなかったことです。 時間があまりにもなくて最後までずっと終わらないのではないかと内心思い、 まるで、締め切りに迫られた作家のようでした。 また、時間を置いてから内容を読み直して修正する時間もほとんど取れず、 完成度が下がってしまったのは悔やまれます。</p> <h2>技術書典を終えて</h2> <p>総合的にはかなり満足しています。 執筆に当たっては失敗しことも多いですが、やって良かったと思えたからですね。</p> <p>実際に技術書を出そうとすると、 かなりのインプットがいることが身に染みました。 今回の執筆では体感で2倍くらいのインプットが必要でした。 業務でReactNative本体の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を読むことはほとんどないので、いい機会だったと思います。 次回もまた出展したいとは思います!内容が決まればですが・・・。</p> <p>技術書典では、参加者としてもたくさんの技術書を買いました。 手当たりしだい買っていたので、むしろ買いすぎた気がしています。 今でも出社前に読みふけっています。</p> <p><blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">なんか思ってたより多い。。。 <a href="https://t.co/gapXe0Jmwq">pic.twitter.com/gapXe0Jmwq</a></p>&mdash; おず。 (@14__oz) <a href="https://twitter.com/14__oz/status/1058123999517921280?ref_src=twsrc%5Etfw">2018年11月1日</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <h2>おわりに</h2> <p>ここまで読んでいただきありがとうございました。</p> <p>今後は、弊社の個性豊かなメンバーが更新していきます。 もちろん、僕も何度か登場することになります。 楽しみにしていてください!!</p> <p>最後に告知があります!!</p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> LT大会</h3> <p>11/29(木)に「スタートアップテック vol.1: <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> LT大会」が開催されます。 スタートアップテックとは5社合同で開催するエンジニア交流会です。 初回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>に関するLT大会です。</p> <p>OPEN8からは、インフラエンジニアとして活躍する川島が登壇します。 発表の内容は、OPEN8のコアテク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CE%A5%ED">ノロ</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B8%A1%BC">ジー</a>にも関わってくる動画処理についてです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fconnpass.com%2Fevent%2F106048%2F" title="スタートアップテック vol.1: Ruby LT大会 (2018/11/29 19:00〜)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://connpass.com/event/106048/">connpass.com</a></cite></p> <p>参加枠は多めにとってあり、現在も枠は空いています。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>に興味がある、これから始める予定でLTだけでも聞いてみたい方など、 ぜひご参加ください。</p> <p>今後、様々なテーマで開催していきます。</p> open8tech