デザインワン・ジャパン Tech Blog DesignOne Japan | Activate the World. 2023-08-30T11:54:48+09:00 tech-doj Hatena::Blog hatenablog://blog/26006613798645494 文字コードの罠 hatenablog://entry/820878482961571148 2023-08-30T11:54:48+09:00 2023-08-30T11:54:48+09:00 はじめに こんにちは!株式会社デザインワン・ジャパンでエキテンの開発を担当しているサービス開発部の寺井です。 文字コードを使う際、今の時代 UTF-8 を使うことがほとんどでしょう。 昔は Shift-JIS や EUC-JP などの文字コードが混在していて、文字化けをしばしば見かけましたが、ここ数年は Web ページで遭遇したことはないです。 (UTF-8 の CSV を Excel で開こうとしたときには、文字化けを見ましたが...) 今や UTF-8 が主流なので、もう文字コードに悩まされることはないんだ。きっとそう。そう、であって欲しかった...。 パピコ問題 気まぐれで、同僚に以下の… <h2 id="はじめに">はじめに</h2> <p>こんにちは!株式会社デザインワン・ジャパンでエキテンの開発を担当しているサービス開発部の寺井です。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>を使う際、今の時代 <a class="keyword" href="https://d.hatena.ne.jp/keyword/UTF-8">UTF-8</a> を使うことがほとんどでしょう。 昔は Shift-JIS や <a class="keyword" href="https://d.hatena.ne.jp/keyword/EUC">EUC</a>-JP などの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>が混在していて、文字化けをしばしば見かけましたが、ここ数年は Web ページで遭遇したことはないです。 (<a class="keyword" href="https://d.hatena.ne.jp/keyword/UTF-8">UTF-8</a> の <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSV">CSV</a> を <a class="keyword" href="https://d.hatena.ne.jp/keyword/Excel">Excel</a> で開こうとしたときには、文字化けを見ましたが...)</p> <p>今や <a class="keyword" href="https://d.hatena.ne.jp/keyword/UTF-8">UTF-8</a> が主流なので、もう<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>に悩まされることはないんだ。きっとそう。そう、であって欲しかった...。</p> <h2 id="パピコ問題"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D1%A5%D4%A5%B3">パピコ</a>問題</h2> <p>気まぐれで、同僚に以下の問題を出しました。</p> <p>これらの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D1%A5%D4%A5%B3">パピコ</a>は同じでしょうか?</p> <pre class="code" data-lang="" data-unlink>パピコ パピコ</pre> <p>実は違う<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D1%A5%D4%A5%B3">パピコ</a>です。</p> <h2 id="文字合成">文字合成</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/UTF-8">UTF-8</a> には文字合成というものができます。 例えば「ハ」と半濁音を足して「パ」という文字を合成ができます。 一方、「パ」単体にも<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>が割り当てられています。 それにより、「パ」には2通りの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>の書き方ができちゃうわけです。</p> <p>このことを、実際に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>見ていきましょう。 ↑の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D1%A5%D4%A5%B3">パピコ</a>を papiko.txt に保存して、バイナリモードで見ていきます。</p> <pre class="code" data-lang="" data-unlink>$ vim -b papiko.txt </pre> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/vim">vim</a> でおまじないをかけることで、</p> <pre class="code" data-lang="" data-unlink>:%!xxd</pre> <p>16進数でファイルの中身を見ることができました。</p> <pre class="code" data-lang="" data-unlink> 00000000: e383 8fe3 829a e383 92e3 829a e382 b30a  ................ 00000010: e383 91e3 8394 e382 b30a                 .......... </pre> <p>1行目が1つ目のパピコ、2行目が2つ目の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D1%A5%D4%A5%B3">パピコ</a>を指しています(行末の <code>0a</code> は改行コード)。</p> <p>「ハ」の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>が <code>e3 83 8f</code> で、半濁音の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>が <code>e3 82 9a</code> であります。 また、「パ」の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>が <code>e3 83 91</code> であるので、各行の先頭を見ると対応していることがわかります。</p> <p>これにより、内部的には違う文字扱いになるので、例えば検索とかかける際には注意が必要です。</p> <h2 id="文字一致判定">文字一致判定</h2> <p>検索に気をつけないといけないですが、 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Chrome">Chrome</a> ではそこらへん上手くしてくれています。 試しに、このページを <a class="keyword" href="https://d.hatena.ne.jp/keyword/Chrome">Chrome</a> で開いてブラウザ検索で「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D1%A5%D4%A5%B3">パピコ</a>」と検索すると、以下のようにちゃんと両方とも検索されていました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20230829/20230829200310.png" width="713" height="200" loading="lazy" title="" class="hatena-fotolife" style="width:450px" itemprop="image"></span></p> <p>一方、この<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%D6%A5%ED%A5%B0">はてなブログ</a>で自動的にリンク付けされる機能ですが、文字合成の方のパピコにはリンクがつけられていません。 つまりは、文字合成と区別がされていることになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20230829/20230829200610.png" width="677" height="79" loading="lazy" title="" class="hatena-fotolife" style="width:450px" itemprop="image"></span></p> <h2 id="最後に">最後に</h2> <p>特にオチがある話ではないですが、同じ文字に見えても中身が違うことがあるということで、検索時などで罠にはまって悩むことがあるかもしれないというお話でした。</p> <h2 id="仲間を募集しております">仲間を募集しております</h2> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/designone/projects">www.wantedly.com</a></cite></p> doj_rterai ウミガメは障害調査に役立つ hatenablog://entry/4207112889935224726 2022-11-25T11:47:46+09:00 2022-11-25T11:47:46+09:00 はじめに こんにちは!株式会社デザインワン・ジャパンでエキテンの開発を担当しているサービス開発部の寺井です。 最近社内でウミガメのスープが流行っています。私は出題者に回ることが多いのですが、回答者になって質問している時、ふと気づきました。 「最初何もわからない状況から答えを探し出すのは、障害調査と似ているのでは?」 と。そして、 「ウミガメのスープを解く能力は、障害調査に役立つのでは?」 と。そのことを実際の障害調査を出しながら見ていこうと思います。 ウミガメのスープとは まず、水平思考という用語について触れていきます。Wikipedia によりますと、 水平思考(すいへいしこう、英:Late… <h3 id="はじめに">はじめに</h3> <p>こんにちは!株式会社デザインワン・ジャパンでエキテンの開発を担当しているサービス開発部の寺井です。</p> <p> </p> <p>最近社内で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A6%A5%DF%A5%AC%A5%E1%A4%CE%A5%B9%A1%BC%A5%D7">ウミガメのスープ</a>が流行っています。私は出題者に回ることが多いのですが、回答者になって質問している時、ふと気づきました。</p> <p><em>「最初何もわからない状況から答えを探し出すのは、障害調査と似ているのでは?」</em></p> <p>と。そして、</p> <p><em>「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A6%A5%DF%A5%AC%A5%E1%A4%CE%A5%B9%A1%BC%A5%D7">ウミガメのスープ</a>を解く能力は、障害調査に役立つのでは?」</em></p> <p>と。そのことを実際の障害調査を出しながら見ていこうと思います。</p> <p> </p> <h3 id="ウミガメのスープとは"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A6%A5%DF%A5%AC%A5%E1%A4%CE%A5%B9%A1%BC%A5%D7">ウミガメのスープ</a>とは</h3> <p>まず、水平思考という用語について触れていきます。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wikipedia">Wikipedia</a> によりますと、</p> <blockquote> <p>水平思考(すいへいしこう、英:Lateral thinking)は、問題解決のために既成の理論や概念にとらわれずア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%C7%A5%A2">イデア</a>を生み出す方法である。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%C9">エド</a>ワード・デボノが1967年頃に提唱した。</p> </blockquote> <p><a href="https://ja.wikipedia.org/wiki/%E6%B0%B4%E5%B9%B3%E6%80%9D%E8%80%83">水平思考 - Wikipedia</a></p> <p>と説明されています。簡単に言うと、固定概念に囚われずに思考していくのが水平思考になります。</p> <p>(水平思考の対となる用語として、垂直思考というものがあります。既にある情報などを使って論理深めていく方法です)</p> <p> </p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A6%A5%DF%A5%AC%A5%E1%A4%CE%A5%B9%A1%BC%A5%D7">ウミガメのスープ</a>は、その水平思考ゲームの一種です。出題者が問題を出し、回答者は「はい」「いいえ」「関係ない」で答えられる質問を出題者にしていき、答えを導くゲームとなっています。</p> <p> </p> <h3 id="ウミガメのスープを解いてみよう"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A6%A5%DF%A5%AC%A5%E1%A4%CE%A5%B9%A1%BC%A5%D7">ウミガメのスープ</a>を解いてみよう</h3> <p>「温度計を買いに店まで行ったが、結局買わなかった。なぜ」</p> <p>という自作問題を社内で出してみました。</p> <p> </p> <p>絵文字は質問に対する回答を表しています。</p> <p>🙆: はい</p> <p>🙅: いいえ</p> <p>🙂: 関係ない</p> <p> </p> <p>「売り切れだったからですか」🙅</p> <p>「本当に店までは行きましたか」🙆</p> <p>「そこは一般的に温度計が売っている店舗ですか」🙆</p> <p> </p> <p>という想定通りの質問が飛んできました。ただ、定番の質問だけで先に進むことは珍しいです。</p> <p> </p> <p>「室内の温度を測るものですか」🙆</p> <p>「買いに行った理由はその時の温度に関係がありますか」🙅</p> <p>「温度計の大きさは一人で持ち帰られるものですか」🙅</p> <p>「その買いに行った人と同居している人はいますか」🙂</p> <p> </p> <p>などと、自分が想定していなかった質問も飛んできました。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A6%A5%DF%A5%AC%A5%E1%A4%CE%A5%B9%A1%BC%A5%D7">ウミガメのスープ</a>で大切なことの一つは、「室内の温度を測るものですか」のような、当たり前だと思っていることを聞くことです。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BD%F6%BD%D2%A5%C8%A5%EA%A5%C3%A5%AF">叙述トリック</a>とかで騙されることがあるためです。</p> <p> </p> <p>「温度計に満足しなかったからですか」🙆</p> <p>「°F仕様が欲しかったのに℃仕様しか売ってなかったとかですか」🙅</p> <p>「温度計を1つだけ買おうとしていましたか」🙆</p> <p>「他に温度計を買おうとしていた人がいましたか」🙅</p> <p> </p> <p>という鋭い質問も飛んできました。今回は関係なかったですが、摂氏と華氏は作問のア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%C7%A5%A2">イデア</a>の一つとして使えそうだなぁと思いました。</p> <p> </p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A6%A5%DF%A5%AC%A5%E1%A4%CE%A5%B9%A1%BC%A5%D7">ウミガメのスープ</a>の欠点としては、出題者がいないと進まないことです。つまり、業務中の雑談では向いていないということです。特に、ブログなどだと問題と答えを載せるだけになってしまいます。なので、この続きが気になる方は答えを見るなり、人に答えを見させて出題者になってもらってください。</p> <p> </p> <p>答え:<span style="color: #ffffff;"><span style="color: #000000;">「</span>売り場に置いてあった温度計が全部バラバラの値を指していたので、精度がよくないと思って買わなかった<span style="color: #000000;">」</span></span></p> <p> </p> <h3 id="実際の障害調査">実際の障害調査</h3> <blockquote> <p>20XX年X月X日——。 障害は突然起こった。</p> <p>Yはいつも通り起床し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/twitter">twitter</a> を見ようと寝ぼけ眼で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>を手に取る。</p> <p>「なんかメールが来ているなぁー。まぁ、また<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%D1%A5%E0%A5%E1%A1%BC%A5%EB">スパムメール</a>かなぁ...、って、サービスZへの連携バッチが落ちとるじゃん!!!!!!!!!!」</p> <p>波乱の一日となりそうだなと思った朝だった。</p> </blockquote> <p>この場合、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A6%A5%DF%A5%AC%A5%E1%A4%CE%A5%B9%A1%BC%A5%D7">ウミガメのスープ</a>風に問題をおくと「サービスZへの連携バッチが落ちた。なぜか」という問題になります。そして、出題者と回答者は基本的に自分となります。</p> <p> </p> <p>「そのバッチは毎日動いていますか」🙆</p> <p>「前回実行は正常でしたか」🙆</p> <p>「前回実行時から、バッチの<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>「前回実行時から、何かしらの<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>毎日起動しているバッチで、前日は成功している。前日と今日の<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>「サービスZに障害情報はでていますか」🙅</p> <p>ほほぅ。出ていないのか。そしたらサービスZも関係ない?</p> <p> </p> <p>「バッチのログで落ちた箇所がわかりますか」🙆</p> <p>ログを見ると、サービスZへの <a class="keyword" href="http://d.hatena.ne.jp/keyword/curl">curl</a> が落ちていることがわかった。リトライ 10 までするようにしているが、全て落ちていた。</p> <p>ということは、ネットワーク周りの障害?</p> <p> </p> <p>「他にネットワークに関するエラーログは出ていますか」🙅</p> <p>「<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>は出ていますか」🙅</p> <p><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>すら出ていないのか...。これは辛い。</p> <p> </p> <p>「<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>を出す処理はありますか」🙅</p> <p>そもそも出そうとしていないのかい!誰だよ実装した人は。</p> <p>(一年前の僕です。すみません)</p> <p> </p> <p>「<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>やエラー詳細を出すような修正を行ってもいいですか」🙆</p> <p>「修正後、再実行してもいいですか」🙆</p> <p>上長から修正と再実行の許可をもらったのでやってみる。</p> <p> </p> <p>「<a class="keyword" href="http://d.hatena.ne.jp/keyword/curl">curl</a> の結果でなにか特殊なログが出ていますか」🙆</p> <p>ログを見ると <a class="keyword" href="http://d.hatena.ne.jp/keyword/ssl">ssl</a>_verify_result の値が 10 であることがわかった。そのエラーコードは証明書の有効期限切れを指している。</p> <p> </p> <p>「サービスZの証明書は直近で入れ替わりましたか」🙆</p> <p>証明書を見ると前日に入れ替わっていることがわかった。そして Let's Encypt の証明書を使っていることもわかった。</p> <p> </p> <p>「EC2 で Let's Encypt の証明書が使えないことがありますか」🙆</p> <p>ようやく原因が突き止められた。やったぜ。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fpremiumsupport%2Fknowledge-center%2Fec2-expired-certificate%2F" title="EC2 インスタンスにある期限切れの Let’s Encrypt 証明書を修正する" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/premiumsupport/knowledge-center/ec2-expired-certificate/">aws.amazon.com</a></cite></p> <p> </p> <p>追伸: そのあと、上記ページの解決方法を情シスにやってもらい、再実行することで今度はちゃんと動きました。めでたし、めでたし。</p> <p> </p> <h3 id="まとめ">まとめ</h3> <p>障害調査は、最初は思いつくものを確認していく感じになります(水平思考)。とっかかりを見つければ、それを深掘りしていきます(垂直思考)。その水平思考を鍛えるには、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A6%A5%DF%A5%AC%A5%E1%A4%CE%A5%B9%A1%BC%A5%D7">ウミガメのスープ</a>が役立つのではと思った次第でした。</p> <p> </p> <h3 id="仲間を募集しております">仲間を募集しております</h3> <p style="box-sizing: border-box; margin: 0px 0px 0.7em; color: #293030; font-family: helvetica, 'Segoe UI', 游ゴシック体, YuGothic, '游ゴシック Medium', 'Yu Gothic Medium', 游ゴシック, 'Yu Gothic', メイリオ, Meiryo, sans-serif; font-size: 17px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/designone/projects">www.wantedly.com</a></cite></p> <p> </p> <p> </p> <p> </p> <p> </p> doj_rterai API Gateway をつかってパラメータストアの値を取得してみた hatenablog://entry/4207112889922699236 2022-10-03T19:20:58+09:00 2022-10-04T08:49:40+09:00 まえがき API Gateway 経由でパラメータストアの値を取得することになった経緯 事前準備 API Gatewayの構築、設定 まとめ おわりに まえがき インフラエンジニアの 冨田(@komitta)です。好きなAWSサービスはAPI Gatewayです。 ということでAPI GatewayのAWS サービス統合機能を使ってパラメータストアの値を取得してみたお話をします。 API Gateway 経由でパラメータストアの値を取得することになった経緯 弊社で運営しているエキテンサービスではAWS ECS Fargateを利用してアプリケーションを開発しています。 Fargate環境で秘匿… <ul class="table-of-contents"> <li><a href="#まえがき">まえがき</a></li> <li><a href="#API-Gateway-経由でパラメータストアの値を取得することになった経緯">API Gateway 経由でパラメータストアの値を取得することになった経緯</a></li> <li><a href="#事前準備">事前準備</a></li> <li><a href="#API-Gatewayの構築設定">API Gatewayの構築、設定</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h3 id="まえがき">まえがき</h3> <p>インフラエンジニアの 冨田(<a href="https://twitter.com/komitta">@komitta</a>)です。好きな<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>サービスは<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Gateway">Gateway</a>です。</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/Gateway">Gateway</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> サービス統合機能を使ってパラメータストアの値を取得してみたお話をします。</p> <h3 id="API-Gateway-経由でパラメータストアの値を取得することになった経緯"><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Gateway">Gateway</a> 経由でパラメータストアの値を取得することになった経緯</h3> <p>弊社で運営しているエキテンサービスでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ECS Fargateを利用してアプリケーションを開発しています。</p> <p>Fargate環境で秘匿情報の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>を設定する際、<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Systems Manager パラメータストア<a href="#f-5f609779" name="fn-5f609779" title="https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-parameter-store.html">*1</a>に値を格納し、Fargateのコンテナ定義にてパラメータストアの名前を設定することで、コンテナ内部の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>として値を取得することが可能です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.aws.amazon.com%2Fja_jp%2FAmazonECS%2Flatest%2Fuserguide%2Fspecifying-sensitive-data-parameters.html" title="Systems Manager Parameter Store を使用して機密データを保護する - Amazon ECS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/specifying-sensitive-data-parameters.html">docs.aws.amazon.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdev.classmethod.jp%2Farticles%2Fecs-secrets%2F" title="ECSでごっつ簡単に機密情報を環境変数に展開できるようになりました! | DevelopersIO" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://dev.classmethod.jp/articles/ecs-secrets/">dev.classmethod.jp</a></cite></p> <p>ですがパラメータストアの値を更新した場合、更新された値を取得するにはコンテナを起動し直す必要があります。</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/SDK">SDK</a><a href="#f-8a3c805a" name="fn-8a3c805a" title="https://aws.amazon.com/jp/getting-started/tools-sdks/">*2</a>を使うことでパラメータストアの値の取得を行うことは可能ですが、コンテナのイメージサイズ最適化のためなるべく<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/SDK">SDK</a>を使用しない形を検討していました。</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/Gateway">Gateway</a> の<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>サービス統合機能<a href="#f-5d8a39e3" name="fn-5d8a39e3" title="https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/getting-started-aws-proxy.html">*3</a>を使ってパラメータストアの値を取得できるか検証しました。</p> <h3 id="事前準備">事前準備</h3> <p>事前に下記を準備しておきます。</p> <ul> <li>パラメータストアに 暗号化された文字列で作成</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20221003/20221003092250.png" width="913" height="868" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>上記SSMおよびkmsの権限が付与されたIAMロールの作成</li> </ul> <h3 id="API-Gatewayの構築設定"><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Gateway">Gateway</a>の構築、設定</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Gateway">Gateway</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/REST%20API">REST API</a>で作成します。GETメソッド作成し、以下の統合リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トの設定を行います。</p> <ul> <li><p>統合タイプ : <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> サービス</p></li> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>リージョン : ap-northeast-1</p></li> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> サービス : Simple Systems Management (SSM)</p></li> <li><p>HTTP メソッド : POST</p></li> <li><p>アクション : GetParameter</p></li> <li><p>実行ロール : 事前に作成したIAMロールのarnを入力</p></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20221003/20221003092833.png" width="1200" height="631" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>GetParameter の実行には <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> Reference<a href="#f-59e96701" name="fn-59e96701" title="https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html">*4</a>より、下記値のリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを送る必要があります。</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">Name</span>&quot;: &quot;<span class="synConstant">/sample/test</span>&quot;, &quot;<span class="synStatement">WithDecryption</span>&quot;: <span class="synConstant">true</span> <span class="synSpecial">}</span> </pre> <p>上記の値をURL クエリ文字列パラメータに設定します。(※)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20221003/20221003102227.png" width="978" height="244" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>※メソッドをPOSTで作成しリク<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/json">json</a>形式の内容をPOSTするとエラーが発生して取得することはできない<a href="#f-be54f24b" name="fn-be54f24b" title="https://stackoverflow.com/questions/63346097/aws-apigateway-simple-system-mangement-integration">*5</a>ので注意が必要です。自分はここでハマりました。。</p> <p>URL クエリ文字列パラメータに設定後、デプロイしたURLに対してGETリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを送ると以下レスポンスが返却されます。</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">GetParameterResponse</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">GetParameterResult</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Parameter</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">ARN</span>&quot;: &quot;<span class="synConstant">arn:aws:ssm:ap-northeast-1:***************:parameter/sample/test</span>&quot;, &quot;<span class="synStatement">DataType</span>&quot;: &quot;<span class="synConstant">text</span>&quot;, &quot;<span class="synStatement">LastModifiedDate</span>&quot;: <span class="synConstant">1664518488.949</span>, &quot;<span class="synStatement">Name</span>&quot;: &quot;<span class="synConstant">/sample/test</span>&quot;, &quot;<span class="synStatement">Selector</span>&quot;: <span class="synIdentifier">null</span>, &quot;<span class="synStatement">SourceResult</span>&quot;: <span class="synIdentifier">null</span>, &quot;<span class="synStatement">Type</span>&quot;: &quot;<span class="synConstant">SecureString</span>&quot;, &quot;<span class="synStatement">Value</span>&quot;: &quot;<span class="synConstant">test</span>&quot;, &quot;<span class="synStatement">Version</span>&quot;: <span class="synConstant">1</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">ResponseMetadata</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">RequestId</span>&quot;: &quot;<span class="synConstant">9a82fed0-e0a3-45ec-9247-57c40b29245e</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Value">Value</a>の値のみ取得したい場合は、統合レスポンスの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%C3%A5%D4%A5%F3%A5%B0">マッピング</a>テンプレートに下記を設定することで取得することも可能です。</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">Value</span>&quot;: $<span class="synError">input</span>.<span class="synError">json</span>(<span class="synError">'$.GetParameterResponse.GetParameterResult.Parameter.Value'</span>) <span class="synSpecial">}</span> </pre> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20221003/20221003100621.png" width="1200" height="563" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">Value</span>&quot;: &quot;<span class="synConstant">test</span>&quot; <span class="synSpecial">}</span> </pre> <h3 id="まとめ">まとめ</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Gateway">Gateway</a>を使用して パラメータストアの値を取得する設定について紹介しました。</p> <p>今回はURLクエリ文字列パラメータに固定値でパラメータストアの名前を指定したので、指定したパラメータストアの値しか取得することはできませんが、</p> <p><code>method.request.path</code> を使用するなどして任意のパラメータストアを取得することができると思います。</p> <p>また、どこでも実行ができてしまうと情報漏洩につながるため、運用で使うには実行元の制限も行う必要があります。</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/Gateway">Gateway</a>経由でパラメータストアの値を取得するケースは稀だと思いますが、誰かの参考になれば幸いです。</p> <h3 id="おわりに">おわりに</h3> <p><strong>仲間を募集しております</strong></p> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/designone/projects">www.wantedly.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-5f609779" name="f-5f609779" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-parameter-store.html">https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-parameter-store.html</a></span></p> <p class="footnote"><a href="#fn-8a3c805a" name="f-8a3c805a" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://aws.amazon.com/jp/getting-started/tools-sdks/">https://aws.amazon.com/jp/getting-started/tools-sdks/</a></span></p> <p class="footnote"><a href="#fn-5d8a39e3" name="f-5d8a39e3" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/getting-started-aws-proxy.html">https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/getting-started-aws-proxy.html</a></span></p> <p class="footnote"><a href="#fn-59e96701" name="f-59e96701" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html">https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameter.html</a></span></p> <p class="footnote"><a href="#fn-be54f24b" name="f-be54f24b" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://stackoverflow.com/questions/63346097/aws-apigateway-simple-system-mangement-integration">https://stackoverflow.com/questions/63346097/aws-apigateway-simple-system-mangement-integration</a></span></p> </div> doj_tomi CloudFront Functions を使ったリダイレクト処理でクエリパラメータの取得に苦戦した話 hatenablog://entry/4207112889907144045 2022-08-31T17:27:58+09:00 2022-08-31T17:27:58+09:00 まえがき EC2サーバーで稼働していたリダイレクト処理について CloudFront Functions でのイベント構造について CloudFront Functions でのクエリパラメータの取得方法 まとめ おわりに まえがき インフラエンジニアの 冨田(@komitta)です。夏休みの宿題は最終日にやるタイプです。 弊社で運用しているエキテンではEC2環境からコンテナを使った環境への移行を行っています。 今回はEC2サーバーを廃棄するにあたって使用していたリダイレクト処理をCloudFront Functionsに移行したときのお話です。 すんなり終わるかと思っていたら、ハマった箇所が… <ul class="table-of-contents"> <li><a href="#まえがき">まえがき</a></li> <li><a href="#EC2サーバーで稼働していたリダイレクト処理について">EC2サーバーで稼働していたリダイレクト処理について</a></li> <li><a href="#CloudFront-Functions-でのイベント構造について">CloudFront Functions でのイベント構造について</a></li> <li><a href="#CloudFront-Functions-でのクエリパラメータの取得方法">CloudFront Functions でのクエリパラメータの取得方法</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h3 id="まえがき">まえがき</h3> <p>インフラエンジニアの 冨田(<a href="https://twitter.com/komitta">@komitta</a>)です。夏休みの宿題は最終日にやるタイプです。</p> <p>弊社で運用しているエキテンではEC2環境からコンテナを使った環境への移行を行っています。</p> <p>今回はEC2サーバーを廃棄するにあたって使用していたリダイレクト処理をCloudFront Functionsに移行したときのお話です。</p> <p>すんなり終わるかと思っていたら、ハマった箇所があったためその話をしたいと思います。</p> <h3 id="EC2サーバーで稼働していたリダイレクト処理について">EC2サーバーで稼働していたリダイレクト処理について</h3> <p>下記要件のリダイレクト処理のためにEC2サーバーで稼働している<a class="keyword" href="http://d.hatena.ne.jp/keyword/Apache">Apache</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/mod_rewrite">mod_rewrite</a>モジュールを使用してリダイレクトを行っていました。</p> <table> <thead> <tr> <th> アクセスURL </th> <th> リダイレクト先URL </th> </tr> </thead> <tbody> <tr> <td> <a href="https://example.com/s_12345/?hoge=fuga">https://example.com/s_12345/?hoge=fuga</a> </td> <td> <a href="https://example.com/shop_12345/?hoge=fuga">https://example.com/shop_12345/?hoge=fuga</a> </td> </tr> </tbody> </table> <p>行っている処理の内容は <code>s_12345</code> から <code>shop_12345</code> へのパスの書き換えになります。</p> <p>この通り複雑な処理ではないため、サーバーを新規で構築するのではなく既に稼働しているCloudFront Functions<a href="#f-7a18c1d3" name="fn-7a18c1d3" title="https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/example-function-redirect-url.html">*1</a>を使えば無駄なリソースを用意することなく対応できると考えました。</p> <p>※Lambda@Edgeも置き換えの選択肢にありましたが要件がシンプルであるため、より軽量に処理することができるClouFront Functionsを選定しています。</p> <h3 id="CloudFront-Functions-でのイベント構造について">CloudFront Functions でのイベント構造について</h3> <p>CloudFront Functions で受け取るイベント構造<a href="#f-789afe3b" name="fn-789afe3b" title="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-event-structure.html#functions-event-structure-example">*2</a>は以下になります。</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">version</span>&quot;: &quot;<span class="synConstant">1.0</span>&quot;, &quot;<span class="synStatement">context</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">distributionDomainName</span>&quot;: &quot;<span class="synConstant">d111111abcdef8.cloudfront.net</span>&quot;, &quot;<span class="synStatement">distributionId</span>&quot;: &quot;<span class="synConstant">EDFDVBD6EXAMPLE</span>&quot;, &quot;<span class="synStatement">eventType</span>&quot;: &quot;<span class="synConstant">viewer-response</span>&quot;, &quot;<span class="synStatement">requestId</span>&quot;: &quot;<span class="synConstant">EXAMPLEntjQpEXAMPLE_SG5Z-EXAMPLEPmPfEXAMPLEu3EqEXAMPLE==</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">viewer</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">ip</span>&quot;: &quot;<span class="synConstant">198.51.100.11</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">request</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">method</span>&quot;: &quot;<span class="synConstant">GET</span>&quot;, &quot;<span class="synStatement">uri</span>&quot;: &quot;<span class="synConstant">/media/index.mpd</span>&quot;, &quot;<span class="synStatement">querystring</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">ID</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">42</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">Exp</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">1619740800</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">TTL</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">1440</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">NoValue</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">querymv</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">val1</span>&quot;, &quot;<span class="synStatement">multiValue</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">val1</span>&quot; <span class="synSpecial">}</span>, <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">val2,val3</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">headers</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">host</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">video.example.com</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">user-agent</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0</span>&quot; <span class="synSpecial">}</span>, ~~以下省略~~ } </pre> <p>例えばリク<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/URI">URI</a>が <code>https://example.com/s_12345/?hoge=fuga</code> の場合、以下リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トイベントを受け取ります。</p> <pre class="code lang-json" data-lang="json" data-unlink>&quot;<span class="synStatement">request</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">method</span>&quot;: &quot;<span class="synConstant">GET</span>&quot;, &quot;<span class="synStatement">uri</span>&quot;: &quot;<span class="synConstant">/s_12345</span>&quot;, &quot;<span class="synStatement">querystring</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">hoge</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">fuga</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">headers</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">host</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">value</span>&quot;: &quot;<span class="synConstant">example.com</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>そのため</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> handler(<span class="synStatement">event</span>) <span class="synIdentifier">{</span> ~~省略~~~ <span class="synIdentifier">var</span> domain = <span class="synStatement">event</span>.request.headers.host.value <span class="synIdentifier">var</span> uri = <span class="synStatement">event</span>.request.uri ~~省略~~~ <span class="synIdentifier">}</span> </pre> <p>のように指定すればそれぞれ <code>example.com</code> と <code>s_12345</code> を取得することが可能です。</p> <p>しかしクエリパラメータは 上記の通り querystring に格納されますが、格納される値は一意ではないのでホスト名やパスと同じ指定方法をとることができません。</p> <h3 id="CloudFront-Functions-でのクエリパラメータの取得方法">CloudFront Functions でのクエリパラメータの取得方法</h3> <p>悩んだときは先人の力を借りようということで、似たような要件でリダイレクトを行っているページを探したところ、以下ページにquerystringを格納している方法が記載されていました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Faws-samples%2Famazon-cloudfront-functions%2Fissues%2F7" title="Cut the .html from uri but remain querystring · Issue #7 · aws-samples/amazon-cloudfront-functions" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/aws-samples/amazon-cloudfront-functions/issues/7">github.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Faws-samples%2Famazon-cloudfront-functions%2Fissues%2F11" title="Redirect preserve query string · Issue #11 · aws-samples/amazon-cloudfront-functions" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/aws-samples/amazon-cloudfront-functions/issues/11">github.com</a></cite></p> <pre class="code lang-javascript" data-lang="javascript" data-unlink> <span class="synIdentifier">function</span> objectToQueryString(obj) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> str = <span class="synIdentifier">[]</span>; <span class="synStatement">for</span> (<span class="synIdentifier">var</span> param <span class="synStatement">in</span> obj) <span class="synStatement">if</span> (obj<span class="synIdentifier">[</span>param<span class="synIdentifier">]</span>.multiValue) str.push(param + <span class="synConstant">&quot;=&quot;</span> + obj<span class="synIdentifier">[</span>param<span class="synIdentifier">]</span>.multiValue.map((item) =&gt; item.value).join(<span class="synConstant">','</span>)); <span class="synStatement">else</span> <span class="synStatement">if</span> (obj<span class="synIdentifier">[</span>param<span class="synIdentifier">]</span>.value == <span class="synConstant">''</span>) str.push(param); <span class="synStatement">else</span> str.push(param + <span class="synConstant">&quot;=&quot;</span> + obj<span class="synIdentifier">[</span>param<span class="synIdentifier">]</span>.value); <span class="synStatement">return</span> str.join(<span class="synConstant">&quot;&amp;&quot;</span>); <span class="synIdentifier">}</span> <span class="synIdentifier">function</span> handler(<span class="synStatement">event</span>) <span class="synIdentifier">{</span> ~~省略~~~  objectToQueryString(<span class="synStatement">event</span>.request.querystring) ~~省略~~~ <span class="synIdentifier">}</span> </pre> <p><code>event.request.querystring</code> の値を配列に格納して値を一つづつ <code>&amp;</code> で連携させることでクエリパラメータを表現しています。</p> <p>こちらの内容を参考に最終的に以下のような形で要件をみたしたリダイレクトを行うことができました。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> objectToQueryString(obj) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> str = <span class="synIdentifier">[]</span>; <span class="synStatement">for</span> (<span class="synIdentifier">var</span> param <span class="synStatement">in</span> obj) <span class="synStatement">if</span> (obj<span class="synIdentifier">[</span>param<span class="synIdentifier">]</span>.multiValue) str.push(param + <span class="synConstant">&quot;=&quot;</span> + obj<span class="synIdentifier">[</span>param<span class="synIdentifier">]</span>.multiValue.map((item) =&gt; item.value).join(<span class="synConstant">','</span>)); <span class="synStatement">else</span> <span class="synStatement">if</span> (obj<span class="synIdentifier">[</span>param<span class="synIdentifier">]</span>.value == <span class="synConstant">''</span>) str.push(param); <span class="synStatement">else</span> str.push(param + <span class="synConstant">&quot;=&quot;</span> + obj<span class="synIdentifier">[</span>param<span class="synIdentifier">]</span>.value); <span class="synStatement">return</span> str.join(<span class="synConstant">&quot;&amp;&quot;</span>); <span class="synIdentifier">}</span> <span class="synIdentifier">function</span> handler(<span class="synStatement">event</span>) <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> path = <span class="synStatement">event</span>.request.uri; <span class="synIdentifier">var</span> domain = request.headers.host.value; <span class="synIdentifier">var</span> beforepath = <span class="synConstant">/s_/</span>; <span class="synIdentifier">var</span> redirectPath = path.replace(beforepath,<span class="synConstant">`shop_`</span>); <span class="synStatement">if</span> (<span class="synType">Object</span>.keys(<span class="synStatement">event</span>.request.querystring).length) <span class="synIdentifier">var</span> loc = <span class="synConstant">`https://</span><span class="synSpecial">${domain}${redirectPath}</span><span class="synConstant">?</span><span class="synSpecial">${objectToQueryString(event.request.querystring)}</span><span class="synConstant">`</span> <span class="synStatement">else</span> <span class="synIdentifier">var</span> loc = <span class="synConstant">`https://</span><span class="synSpecial">${domain}${redirectPath}</span><span class="synConstant">`</span> <span class="synIdentifier">var</span> response = <span class="synIdentifier">{</span> statusCode: 301, statusDescription: <span class="synConstant">'Found'</span>, headers: <span class="synIdentifier">{</span> <span class="synConstant">'location'</span>: <span class="synIdentifier">{</span> value: loc <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>; <span class="synStatement">return</span> response; <span class="synIdentifier">}</span> </pre> <h3 id="まとめ">まとめ</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>が不慣れだったため、時間がかかりましたが要件を満たすリダイレクト処理をCloudFront Functionsを使って行うことができました。</p> <p>ただしCloudFront Functionsの最大実行時間が1ms となっているように、あまり複雑なリダイレクト処理は行うべきではないと思います。</p> <p>これからも要件に応じて最適な構成を選択していければと思います。</p> <h3 id="おわりに">おわりに</h3> <p><strong>仲間を募集しております</strong></p> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/designone/projects">www.wantedly.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-7a18c1d3" name="f-7a18c1d3" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/example-function-redirect-url.html">https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/example-function-redirect-url.html</a></span></p> <p class="footnote"><a href="#fn-789afe3b" name="f-789afe3b" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-event-structure.html#functions-event-structure-example">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-event-structure.html#functions-event-structure-example</a></span></p> </div> doj_tomi 【PHP】タイプヒンティングをより強力にするArrayShape hatenablog://entry/4207112889906533844 2022-08-18T15:24:47+09:00 2022-08-18T15:24:47+09:00 はじめに タイプヒンティングとは 配列のタイプヒンティング ArrayShapeとは ArrayShapeによる連想配列の定義 ArrayShapeによる配列の定義 まとめ 仲間を募集しております はじめに こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(@suzuki_cecil_)です。 弊社では現在、15年間運用され続けている口コミサービス エキテン のリニューアルプロジェクトに取り組んでおります。 エキテンはリニューアル前後ともにPHPで開発をしているのですがリニューアル前は一部古いアプリケーションがPHP5系… <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#タイプヒンティングとは">タイプヒンティングとは</a></li> <li><a href="#配列のタイプヒンティング">配列のタイプヒンティング</a></li> <li><a href="#ArrayShapeとは">ArrayShapeとは</a><ul> <li><a href="#ArrayShapeによる連想配列の定義">ArrayShapeによる連想配列の定義</a></li> <li><a href="#ArrayShapeによる配列の定義">ArrayShapeによる配列の定義</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#仲間を募集しております">仲間を募集しております</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(<a href="https://twitter.com/suzuki_cecil_">@suzuki_cecil_</a>)です。</p> <p>弊社では現在、15年間運用され続けている口コミサービス <a href="https://www.ekiten.jp/">エキテン</a> のリニューアルプロジェクトに取り組んでおります。</p> <p>エキテンはリニューアル前後ともに<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>で開発をしているのですがリニューアル前は一部古いアプリケーションがPHP5系で、リニューアル後は全体がPHP8.1に統一されリニューアルによってレガシー<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>からモダン<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>に進化を遂げることになります。(※本記事投稿時点における<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>の最新リリースは8.1です)</p> <p>PHP7系では <a href="https://www.php.net/manual/ja/language.types.declarations.php">タイプヒンティング</a> で定義できる型の拡張、PHP8.1では <a href="https://wiki.php.net/rfc/named_params">名前付き引数</a> や <a href="https://wiki.php.net/rfc/constructor_promotion">コンストラクタプロパティプロモーション</a> などの新機能の採用により、<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>今回はその中でもタイプヒンティングおよびタイプヒンティングに関連して、PHPStormのArrayShapeに関して記事を書かさせていただきます。</p> <h1 id="タイプヒンティングとは">タイプヒンティングとは</h1> <p>まずタイプヒンティングをご存知でない方に向けてタイプヒンティングの解説を行います。ArrayShapeに関して知りたい方は次の段落からお読みください。</p> <p>タイプヒンティングとは型宣言とも呼ばれ、<a href="https://www.php.net/manual/ja/language.types.declarations.php">公式ドキュメント</a>では以下のように説明されております。</p> <blockquote><p>関数のパラメータや戻り値、 クラスのプロパティ (<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a> 7.4.0 以降) に対して型を宣言することができます。 これによって、その値が特定の型であることを保証できます。 その型でない場合は、TypeError がスローされます。</p></blockquote> <p>以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>1を例に説明しますと、</p> <p><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>1</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synStatement">declare</span><span class="synSpecial">(</span>strict_types<span class="synStatement">=</span><span class="synConstant">1</span><span class="synSpecial">)</span>; <span class="synComment">// 強い型付けとすることで型が異なる場合はTypeErrorをスローできる</span> <span class="synPreProc">function</span> displayName<span class="synSpecial">(</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">name</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synPreProc">echo</span> <span class="synConstant">&quot;</span><span class="synSpecial">{</span><span class="synStatement">$</span><span class="synIdentifier">name</span><span class="synSpecial">}\n</span><span class="synConstant">&quot;</span>; <span class="synSpecial">}</span> displayName<span class="synSpecial">(</span><span class="synConstant">&quot;Suzuki&quot;</span><span class="synSpecial">)</span>; <span class="synComment">// Suzukiと表示される</span> displayName<span class="synSpecial">(</span><span class="synConstant">1</span><span class="synSpecial">)</span>; <span class="synComment">// TypeErrorがスローされる</span> </pre> <p>引数をstring型でタイプヒンティングされた関数 <code>displayName</code> に対して、</p> <ul> <li><code>displayName("Suzuki");</code> とコールした場合 <ul> <li>宣言通りの型の変数が引数に渡されるため正常に関数が実行される</li> </ul> </li> <li><code>displayName(1);</code> とコールした場合 <ul> <li>int型の変数が引数に渡されるためTypeErrorがスローされる</li> </ul> </li> </ul> <p>といった挙動になります。</p> <p>また関数の戻り値に対してもタイプヒンティングが可能です。</p> <p><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>2</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synStatement">declare</span><span class="synSpecial">(</span>strict_types<span class="synStatement">=</span><span class="synConstant">1</span><span class="synSpecial">)</span>; <span class="synComment">// 強い型付けとすることで型が異なる場合はTypeErrorをスローできる</span> <span class="synPreProc">function</span> square<span class="synSpecial">(</span><span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">num</span><span class="synSpecial">)</span><span class="synStatement">:</span> <span class="synType">int</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synStatement">$</span><span class="synIdentifier">num</span> <span class="synStatement">*</span> <span class="synStatement">$</span><span class="synIdentifier">num</span>; <span class="synComment">// $numの二乗が返される</span> <span class="synSpecial">}</span> <span class="synPreProc">function</span> invalidSquare<span class="synSpecial">(</span><span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">num</span><span class="synSpecial">)</span><span class="synStatement">:</span> <span class="synType">int</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synConstant">&quot;hoge&quot;</span>; <span class="synComment">// TypeErrorがスローされる</span> <span class="synSpecial">}</span> <span class="synPreProc">echo</span> square<span class="synSpecial">(</span><span class="synConstant">2</span><span class="synSpecial">)</span>; <span class="synPreProc">echo</span> invalidSquare<span class="synSpecial">(</span><span class="synConstant">2</span><span class="synSpecial">)</span>; </pre> <p>戻り値をint型でタイプヒンティングされた関数 <code>square</code> および <code>invalidSquare</code> に関して、</p> <ul> <li><code>square</code> <ul> <li>int型の変数が戻り値に該当するため正常に関数が実行される</li> </ul> </li> <li><code>invalidSquare</code> <ul> <li>string型の変数が戻り値に該当するためTypeErrorがスローされる</li> </ul> </li> </ul> <p>といった挙動になります。</p> <p>タイプヒンティングを行うことで、想定される引数・戻り値が可視化されることにより<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/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>の堅牢性が向上されるといった恩恵を受けることが可能です。</p> <blockquote><p>強い型付けと弱い型付けについて</p> <p>宣言した型と異なる型の変数が引数・戻り値に設定された場合の挙動として強い型付けと弱い型付けの2つが存在します。</p> <p>強い型付けでは型が異なる時点でTypeErrorがスローされますが、弱い型付けでは宣言した型にキャストが可能な場合はキャストを行い、キャストが不可能な場合はTypeErrorがスローされます。</p></blockquote> <h1 id="配列のタイプヒンティング">配列のタイプヒンティング</h1> <p>タイプヒンティングにより宣言できる型にarray型が存在するため、配列に関してもタイプヒンティングが可能です。</p> <p>ただしPHP8.1時点における、<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>のタイプヒンティングの弱点としてarray型としてのみタイプヒンティングが可能であるため配列の要素に対してタイプヒンティングを行うことができません。</p> <p><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>3</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synPreProc">function</span> displayNames<span class="synSpecial">(</span><span class="synType">array</span> <span class="synStatement">$</span><span class="synIdentifier">names</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">foreach</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">names</span> <span class="synStatement">as</span> <span class="synStatement">$</span><span class="synIdentifier">name</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synPreProc">echo</span> <span class="synConstant">&quot;</span><span class="synSpecial">{</span><span class="synStatement">$</span><span class="synIdentifier">name</span><span class="synSpecial">}\n</span><span class="synConstant">&quot;</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> displayNames<span class="synSpecial">([</span><span class="synConstant">&quot;Suzuki&quot;</span>, <span class="synConstant">&quot;Sato&quot;</span>, <span class="synConstant">&quot;Takahashi&quot;</span><span class="synSpecial">])</span>; displayNames<span class="synSpecial">(</span><span class="synConstant">&quot;Suzuki&quot;</span><span class="synSpecial">)</span>; displayNames<span class="synSpecial">([</span><span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span><span class="synSpecial">])</span>; </pre> <p>引数にstring型を要素に持つ配列が渡されることを想定した関数 <code>displayNames</code> に対して、</p> <ul> <li><code>displayNames(["Suzuki", "Sato", "Takahashi"]);</code> とコールした場合 <ul> <li>string型を要素に持つ配列が引数に渡されるため正常に関数が実行される</li> </ul> </li> <li><code>displayNames("Suzuki");</code> とコールした場合 <ul> <li>string型の変数が引数に渡されるためTypeErrorがスローされる</li> </ul> </li> <li><code>displayNames([1, 2, 3]);</code> とコールした場合 <ul> <li>要素がint型ではあるものの配列が引数に渡されるため正常に関数が実行される</li> </ul> </li> </ul> <p>といった挙動になり、配列の要素が想定と異なる場合にTypeErrorはスローされません。</p> <p>これでは可読性・堅牢性の向上に繋がりませんが、対策としてPHPDocによる補完を行うことで品質を担保することが可能です。</p> <p><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>4</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@param </span><span class="synComment">string[] $names // string型の配列であることを明示的にする</span> <span class="synComment"> */</span> <span class="synPreProc">function</span> displayNames<span class="synSpecial">(</span><span class="synType">array</span> <span class="synStatement">$</span><span class="synIdentifier">names</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">foreach</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">names</span> <span class="synStatement">as</span> <span class="synStatement">$</span><span class="synIdentifier">name</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synPreProc">echo</span> <span class="synConstant">&quot;</span><span class="synSpecial">{</span><span class="synStatement">$</span><span class="synIdentifier">name</span><span class="synSpecial">}\n</span><span class="synConstant">&quot;</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>※PHPDocを記述しても要素が想定と異なる場合にTypeErrorはスローされませんが、静的解析により配列の要素まで精査が可能なため結果的に堅牢性の向上に繋がります</p> <p>上記の通りPHPDocを記述することで、配列のタイプヒンティングの場合でも<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>の品質を向上することが可能となりましたが、ただPHPDocを書くだけでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%A2%C1%DB%C7%DB%CE%F3">連想配列</a>のような複雑な配列の定義を行うことができません。そこで登場するのがArrayShapeです。</p> <h1 id="ArrayShapeとは">ArrayShapeとは</h1> <p>ArrayShape とはPHPDocの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%CE%A5%C6%A1%BC%A5%B7%A5%E7%A5%F3">アノテーション</a>の一つでオブジェクト上の配列(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%A2%C1%DB%C7%DB%CE%F3">連想配列</a>)に対して、その構造を定義可能とします。</p> <p>PHPStormでは<a href="https://pleiades.io/help/phpstorm/php-arrayshape-attribute-can-be-added.html">2022.2のバージョン</a>にてリリースされました。</p> <p>ただしArrayShapeはPHPDocの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%CE%A5%C6%A1%BC%A5%B7%A5%E7%A5%F3">アノテーション</a>であり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>の仕様とは異なり配列のタイプヒンティング同様、定義とは異なる値が引数・戻り値に設定されたとしてもTypeErrorはスローされないため静的解析にて堅牢性を担保する必要があります。</p> <h2 id="ArrayShapeによる連想配列の定義">ArrayShapeによる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%A2%C1%DB%C7%DB%CE%F3">連想配列</a>の定義</h2> <p>ArrayShapeで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%A2%C1%DB%C7%DB%CE%F3">連想配列</a>を定義する場合は、PHPDocに <code>array{キー名: 型名, キー名: 型名}</code> と記述します。</p> <p><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>5</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">class</span> UserId <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">public</span> readonly <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">class</span> UserName <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">public</span> readonly <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">array</span> <span class="synComment"> */</span> <span class="synPreProc">function</span> getUserWithoutArrayShape<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">array</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synSpecial">[</span> <span class="synConstant">&quot;id&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserId<span class="synSpecial">(</span><span class="synConstant">&quot;1&quot;</span><span class="synSpecial">)</span>, <span class="synConstant">&quot;name&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserName<span class="synSpecial">(</span><span class="synConstant">&quot;Suzuki&quot;</span><span class="synSpecial">)</span>, <span class="synSpecial">]</span>; <span class="synSpecial">}</span> <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">array{&quot;id&quot;: UserId, &quot;name&quot;: UserName}</span> <span class="synComment"> */</span> <span class="synPreProc">function</span> getUserWithArrayShape<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">array</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synSpecial">[</span> <span class="synConstant">&quot;id&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserId<span class="synSpecial">(</span><span class="synConstant">&quot;1&quot;</span><span class="synSpecial">)</span>, <span class="synConstant">&quot;name&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserName<span class="synSpecial">(</span><span class="synConstant">&quot;Suzuki&quot;</span><span class="synSpecial">)</span>, <span class="synSpecial">]</span>; <span class="synSpecial">}</span> <span class="synStatement">$</span><span class="synIdentifier">user1</span> <span class="synStatement">=</span> getUserWithoutArrayShape<span class="synSpecial">()</span>; <span class="synPreProc">echo</span> <span class="synStatement">$</span><span class="synIdentifier">user1</span><span class="synSpecial">[</span><span class="synConstant">&quot;name&quot;</span><span class="synSpecial">]</span><span class="synType">-&gt;</span><span class="synIdentifier">value</span> <span class="synStatement">.</span> <span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>; <span class="synStatement">$</span><span class="synIdentifier">user2</span> <span class="synStatement">=</span> getUserWithArrayShape<span class="synSpecial">()</span>; <span class="synPreProc">echo</span> <span class="synStatement">$</span><span class="synIdentifier">user2</span><span class="synSpecial">[</span><span class="synConstant">&quot;name&quot;</span><span class="synSpecial">]</span><span class="synType">-&gt;</span><span class="synIdentifier">value</span> <span class="synStatement">.</span> <span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>; </pre> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>5ではArrayShapeで戻り値を定義していない <code>getUserWithoutArrayShape</code> 関数と、ArrayShapeで戻り値を定義した <code>getUserWithArrayShape</code> 関数の2つが存在します。これらの関数はArrayShapeによる戻り値の定義の有無以外は全て一致しています。</p> <p>戻り値の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%A2%C1%DB%C7%DB%CE%F3">連想配列</a>は <code>id</code> キーでUserIdクラスの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を、 <code>name</code> キーでUserNameクラスの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を保持しているためArrayShapeは <code>array{"id": UserId, "name": UserName}</code> となります。</p> <p>ArrayShapeを用いて定義することにより<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%A2%C1%DB%C7%DB%CE%F3">連想配列</a>が保持するキーおよび各キーが保持する変数の型が明示的になります。</p> <p>またPHPStormではArrayShapeによる定義を基にコード補完を行なってくれます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20220814/20220814154703.png" width="1200" height="194" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20220814/20220814154437.png" width="1200" height="183" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="ArrayShapeによる配列の定義">ArrayShapeによる配列の定義</h2> <p>ArrayShapeで配列の定義を行うことも可能です。その場合は <code>array&lt;型名&gt;</code> と記述します。</p> <p>またこれを応用して要素に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%A2%C1%DB%C7%DB%CE%F3">連想配列</a>を持つ配列を定義することも可能です。</p> <p><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>6</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">class</span> UserId <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">public</span> readonly <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">class</span> UserName <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">public</span> readonly <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">array&lt;array{&quot;id&quot;: UserId, &quot;name&quot;: UserName}&gt;</span> <span class="synComment"> */</span> <span class="synPreProc">function</span> getUsers<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">array</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synSpecial">[</span> <span class="synSpecial">[</span> <span class="synConstant">&quot;id&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserId<span class="synSpecial">(</span><span class="synConstant">&quot;1&quot;</span><span class="synSpecial">)</span>, <span class="synConstant">&quot;name&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserName<span class="synSpecial">(</span><span class="synConstant">&quot;Suzuki&quot;</span><span class="synSpecial">)</span>, <span class="synSpecial">]</span>, <span class="synSpecial">[</span> <span class="synConstant">&quot;id&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserId<span class="synSpecial">(</span><span class="synConstant">&quot;2&quot;</span><span class="synSpecial">)</span>, <span class="synConstant">&quot;name&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserName<span class="synSpecial">(</span><span class="synConstant">&quot;Sato&quot;</span><span class="synSpecial">)</span>, <span class="synSpecial">]</span>, <span class="synSpecial">[</span> <span class="synConstant">&quot;id&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserId<span class="synSpecial">(</span><span class="synConstant">&quot;3&quot;</span><span class="synSpecial">)</span>, <span class="synConstant">&quot;name&quot;</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> UserName<span class="synSpecial">(</span><span class="synConstant">&quot;Takahashi&quot;</span><span class="synSpecial">)</span>, <span class="synSpecial">]</span>, <span class="synSpecial">]</span>; <span class="synSpecial">}</span> <span class="synStatement">$</span><span class="synIdentifier">users</span> <span class="synStatement">=</span> getUsers<span class="synSpecial">()</span>; <span class="synPreProc">echo</span> <span class="synStatement">$</span><span class="synIdentifier">users</span><span class="synSpecial">[</span><span class="synConstant">0</span><span class="synSpecial">][</span><span class="synConstant">&quot;name&quot;</span><span class="synSpecial">]</span><span class="synType">-&gt;</span><span class="synIdentifier">value</span>; </pre> <p>PHPStormではArrayShapeによる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%A2%C1%DB%C7%DB%CE%F3">連想配列</a>の配列の定義も解析してくれ、コード補完を行なってくれます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20220814/20220814155433.png" width="1200" height="191" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20220814/20220814155454.png" width="1200" height="177" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="まとめ">まとめ</h1> <p>今回はArrayShapeによる<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>ArrayShapeを用いることで配列定義の表現が一段と広がります。</p> <p>ただし<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>の仕様ではなく定義と異なる値が設定されてもTypeErrorがスローされないため、個人的には今後の<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>のバージョンアップによるタイプヒンティングによる配列定義の表現が広がることに期待します。</p> <h1 id="仲間を募集しております">仲間を募集しております</h1> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/designone/projects">www.wantedly.com</a></cite></p> doj_suzuki_cecil kanikoを使ったGitLab Container Registry と AWS ECRの認証方法について hatenablog://entry/4207112889895047632 2022-07-29T20:33:42+09:00 2022-07-29T20:40:20+09:00 まえがき これまでのコンテナのデプロイ方法 kaniko を使ったDockerイメージビルド 独自ドメインで自前の証明書を使用する場合 GitLab Container RegistryとAWS ECRの併用する場合 まとめ おわりに まえがき インフラエンジニアの 冨田(@komitta)です。今回はデプロイのお話です。 弊社で運用しているエキテンでは一部EC2の構成も稼働していますが、コンテナを使った環境も存在しています。今回はこのコンテナでのデプロイについてお話します。 これまでのコンテナのデプロイ方法 エキテンではEC2上で構築したGitLabサーバーのGitLab CIを使ってデプロ… <ul class="table-of-contents"> <li><a href="#まえがき">まえがき</a></li> <li><a href="#これまでのコンテナのデプロイ方法">これまでのコンテナのデプロイ方法</a></li> <li><a href="#kaniko-を使ったDockerイメージビルド">kaniko を使ったDockerイメージビルド</a><ul> <li><a href="#独自ドメインで自前の証明書を使用する場合">独自ドメインで自前の証明書を使用する場合</a></li> <li><a href="#GitLab-Container-RegistryとAWS-ECRの併用する場合">GitLab Container RegistryとAWS ECRの併用する場合</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h3 id="まえがき">まえがき</h3> <p>インフラエンジニアの 冨田(<a href="https://twitter.com/komitta">@komitta</a>)です。今回はデプロイのお話です。</p> <p>弊社で運用しているエキテンでは一部EC2の構成も稼働していますが、コンテナを使った環境も存在しています。今回はこのコンテナでのデプロイについてお話します。</p> <h3 id="これまでのコンテナのデプロイ方法">これまでのコンテナのデプロイ方法</h3> <p>エキテンではEC2上で構築したGitLabサーバーのGitLab CIを使ってデプロイを行っています。GitLab CIを動かす runnerはスポット<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>で起動したEC2のdocker+executorで実行しています。</p> <p>EC2へのファイルデプロイやビルド<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>、テストCIの実行であれば特に問題はないのですが、Dockerイメージのビルドをこのdocker runner 上で動かすには通常のDockerイメージでは実行できないので、検討が必要になります。</p> <p>これまでdocker runner 上でのDockerイメージビルドにはDocker in Docker (以下dind) <a href="#f-e647ac7a" name="fn-e647ac7a" title="https://esakat.github.io/esakat-blog/posts/docker-in-docker/">*1</a>を使用していました。</p> <p>dindの仕組みとしてprivileged オプションを使うことでホストOS側の権限を開放してDockerコンテナ上でDocker デーモンを操作しているため、コンテナからホストOSに影響を与えることからセキュリティの面で望ましくないです。</p> <p>そのため、Dockerデーモンを使わないビルドツールのkaniko <a href="#f-f805a595" name="fn-f805a595" title="https://github.com/GoogleContainerTools/kaniko">*2</a>を使用してみました。</p> <h3 id="kaniko-を使ったDockerイメージビルド">kaniko を使ったDockerイメージビルド</h3> <p>GitLabのマニュアルページにkanikoを使用したDockerイメージビルド<a href="#f-34cda3bf" name="fn-34cda3bf" title="https://docs.GitLab.com/ee/ci/docker/using_kaniko.html">*3</a>があるのでそれに沿って行いました。</p> <p>以下設定においてハマった部分を紹介します。</p> <h5 id="独自ドメインで自前の証明書を使用する場合"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%C8%BC%AB%A5%C9%A5%E1%A5%A4%A5%F3">独自ドメイン</a>で自前の証明書を使用する場合</h5> <p>弊社のGitLabサーバーはEC2上に構築しており、証明書も自前で用意しています。</p> <p>自前の証明書を使っている場合、ビルド時に以下のエラーが出力されます。</p> <pre class="code" data-lang="" data-unlink>error building image: getting stage builder for stage 0: Get https://***********/v2/: x509: certificate signed by unknown authority</pre> <p>そのため、証明書本文をGitLabの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>に記載し <code>/kaniko/ssl/certs/additional-ca-cert-bundle.crt</code> ファイルに追記することでエラーを回避しています。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synIdentifier">image</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> gcr.io/kaniko-project/executor:debug <span class="synIdentifier">entrypoint</span><span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synConstant">&quot;&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">script</span><span class="synSpecial">:</span> <span class="synStatement">- </span>echo <span class="synConstant">&quot;$REGISTRY_CERT&quot;</span> &gt;&gt; /kaniko/ssl/certs/additional-ca-cert-bundle.crt </pre> <p>上記は設定でREGISTRY_CERT という名前でGitLabの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>の値に証明書本文を登録しているときの例です。</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/d/doj_tomi/20220729/20220729193207.png" width="1009" height="354" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>設定例</figcaption></figure></p> <h5 id="GitLab-Container-RegistryとAWS-ECRの併用する場合">GitLab Container Registryと<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ECRの併用する場合</h5> <p>エキテンで使用するDocker<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%C8%A5%EA">レジストリ</a>は、GitLab Container Registry と <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ECRを使用しています。</p> <p>用途の使い分けとしてローカル開発環境上で使用するDockerイメージはGitLab Container Registryを使用し、サービス側で使用するDocker イメージは<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ECRを使用しています。</p> <p>ビルド実行コマンド実行前に /kaniko/.docker/config.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>にauths 情報を記載することで認証することができます。</p> <ul> <li>GitLab Container Registryの認証方法<a href="#f-a1de951d" name="fn-a1de951d" title="https://docs.GitLab.com/ee/ci/docker/using_kaniko.html">*4</a></li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synIdentifier">image</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> gcr.io/kaniko-project/executor:debug <span class="synIdentifier">entrypoint</span><span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synConstant">&quot;&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">script</span><span class="synSpecial">:</span> <span class="synStatement">- </span>echo <span class="synConstant">&quot;$REGISTRY_CERT&quot;</span> &gt;&gt; /kaniko/ssl/certs/additional-ca-cert-bundle.crt <span class="synStatement">- </span>echo <span class="synConstant">&quot;{</span><span class="synSpecial">\&quot;</span><span class="synConstant">auths</span><span class="synSpecial">\&quot;</span><span class="synConstant">:{</span><span class="synSpecial">\&quot;</span><span class="synConstant">${CI_REGISTRY}</span><span class="synSpecial">\&quot;</span><span class="synConstant">:{</span><span class="synSpecial">\&quot;</span><span class="synConstant">auth</span><span class="synSpecial">\&quot;</span><span class="synConstant">:</span><span class="synSpecial">\&quot;</span><span class="synConstant">$(printf &quot;</span>%s:%s&quot; <span class="synConstant">&quot;${CI_REGISTRY_USER}&quot;</span> <span class="synConstant">&quot;${CI_REGISTRY_PASSWORD}&quot;</span> | base64 | tr -d <span class="synConstant">'\n'</span>)\&quot;}}}&quot; &gt; /kaniko/.docker/config.json </pre> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ECRの認証方法<a href="#f-785e04a6" name="fn-785e04a6" title="https://github.com/awslabs/amazon-ecr-credential-helper">*5</a></li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synIdentifier">image</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> gcr.io/kaniko-project/executor:debug <span class="synIdentifier">entrypoint</span><span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synConstant">&quot;&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">script</span><span class="synSpecial">:</span> <span class="synStatement">- </span>echo <span class="synConstant">&quot;$REGISTRY_CERT&quot;</span> &gt;&gt; /kaniko/ssl/certs/additional-ca-cert-bundle.crt <span class="synStatement">- </span>echo <span class="synConstant">&quot;{</span><span class="synSpecial">\&quot;</span><span class="synConstant">auths</span><span class="synSpecial">\&quot;</span><span class="synConstant">: </span><span class="synSpecial">\&quot;</span><span class="synConstant">credHelpers</span><span class="synSpecial">\&quot;</span><span class="synConstant">:{</span><span class="synSpecial">\&quot;</span><span class="synConstant">&lt;aws_account_id&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com</span><span class="synSpecial">\&quot;</span><span class="synConstant">:</span><span class="synSpecial">\&quot;</span><span class="synConstant">ecr-login</span><span class="synSpecial">\&quot;</span><span class="synConstant">}}&quot;</span> &gt; /kaniko/.docker/config.json </pre> <p>また、エキテンではマルチステージビルド<a href="#f-94ce4a13" name="fn-94ce4a13" title="https://docs.docker.jp/develop/develop-images/multistage-build.html">*6</a>を用いて、GitLab Container Registryに配置されたイメージ(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DF%A5%C9%A5%EB%A5%A6%A5%A7%A5%A2">ミドルウェア</a>情報等を格納)をベースとし、アプリを含んだサービスで公開するイメージについては<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ECRにPushする運用を取っているため、ビルド時にはGitLab Container Registryと<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ECRに対して2つの認証がされている必要があります。</p> <p>その場合は、以下の記載をすることで2つの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%C8%A5%EA">レジストリ</a>に対して認証することができます。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synIdentifier">image</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> gcr.io/kaniko-project/executor:debug <span class="synIdentifier">entrypoint</span><span class="synSpecial">:</span> <span class="synSpecial">[</span><span class="synConstant">&quot;&quot;</span><span class="synSpecial">]</span> <span class="synIdentifier">script</span><span class="synSpecial">:</span> <span class="synStatement">- </span>echo <span class="synConstant">&quot;$REGISTRY_CERT&quot;</span> &gt;&gt; /kaniko/ssl/certs/additional-ca-cert-bundle.crt <span class="synStatement">- </span>echo <span class="synConstant">&quot;{</span><span class="synSpecial">\&quot;</span><span class="synConstant">auths</span><span class="synSpecial">\&quot;</span><span class="synConstant">:{</span><span class="synSpecial">\&quot;</span><span class="synConstant">${CI_REGISTRY}</span><span class="synSpecial">\&quot;</span><span class="synConstant">:{</span><span class="synSpecial">\&quot;</span><span class="synConstant">auth</span><span class="synSpecial">\&quot;</span><span class="synConstant">:</span><span class="synSpecial">\&quot;</span><span class="synConstant">$(printf &quot;</span>%s:%s&quot; <span class="synConstant">&quot;${CI_REGISTRY_USER}&quot;</span> <span class="synConstant">&quot;${CI_REGISTRY_PASSWORD}&quot;</span> | base64 | tr -d <span class="synConstant">'\n'</span>)\&quot;}},\&quot;credHelpers\&quot;:{\&quot;&lt;aws_account_id&gt;.dkr.ecr.&lt;region&gt;.amazonaws.com\&quot;:\&quot;ecr-login\&quot;}}&quot; &gt; /kaniko/.docker/config.json </pre> <h3 id="まとめ">まとめ</h3> <p>kaniko を 使ったGitLab Container Registryと<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ECR の認証方法について 紹介しました。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%B8%A5%B9%A5%C8%A5%EA">レジストリ</a>単体の設定方法についてはよく事例にあげられますが、今回のように同時に認証が必要な場合の設定方法について情報が少なかったので少し苦労しました。</p> <p>kanikoを使用する場合の参考になれば幸いです。</p> <h3 id="おわりに">おわりに</h3> <p><strong>仲間を募集しております</strong></p> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/designone/projects">www.wantedly.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-e647ac7a" name="f-e647ac7a" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://esakat.github.io/esakat-blog/posts/docker-in-docker/">https://esakat.github.io/esakat-blog/posts/docker-in-docker/</a></span></p> <p class="footnote"><a href="#fn-f805a595" name="f-f805a595" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://github.com/GoogleContainerTools/kaniko">https://github.com/GoogleContainerTools/kaniko</a></span></p> <p class="footnote"><a href="#fn-34cda3bf" name="f-34cda3bf" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.GitLab.com/ee/ci/docker/using_kaniko.html">https://docs.GitLab.com/ee/ci/docker/using_kaniko.html</a></span></p> <p class="footnote"><a href="#fn-a1de951d" name="f-a1de951d" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.GitLab.com/ee/ci/docker/using_kaniko.html">https://docs.GitLab.com/ee/ci/docker/using_kaniko.html</a></span></p> <p class="footnote"><a href="#fn-785e04a6" name="f-785e04a6" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://github.com/awslabs/amazon-ecr-credential-helper">https://github.com/awslabs/amazon-ecr-credential-helper</a></span></p> <p class="footnote"><a href="#fn-94ce4a13" name="f-94ce4a13" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.docker.jp/develop/develop-images/multistage-build.html">https://docs.docker.jp/develop/develop-images/multistage-build.html</a></span></p> </div> doj_tomi AWS 分散負荷テスト(Distributed Load Testing on AWS)でJMeterのシナリオを使ったテストについて hatenablog://entry/13574176438097948005 2022-06-29T15:31:01+09:00 2022-06-29T15:31:01+09:00 まえがき Distributed Load Testing on AWS とは JMeterのシナリオを使って実行が可能 JMeterシナリオテストで外部ファイルを使う場合 まとめ おわりに まえがき なかなかゴルフが上手くならない インフラエンジニアの 冨田(@komitta)です。 今回はAWS 上の公式ソリューションサービスであるDistributed Load Testing on AWS*1上でJMeterのシナリオを使って、負荷試験を実施したときの内容をお話ししたいと思います。 Distributed Load Testing on AWS とは Distributed Load … <ul class="table-of-contents"> <li><a href="#まえがき">まえがき</a></li> <li><a href="#Distributed-Load-Testing-on-AWS-とは">Distributed Load Testing on AWS とは</a></li> <li><a href="#JMeterのシナリオを使って実行が可能">JMeterのシナリオを使って実行が可能</a></li> <li><a href="#JMeterシナリオテストで外部ファイルを使う場合">JMeterシナリオテストで外部ファイルを使う場合</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h3 id="まえがき">まえがき</h3> <p>なかなかゴルフが上手くならない インフラエンジニアの 冨田(<a href="https://twitter.com/komitta">@komitta</a>)です。</p> <p>今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> 上の公式ソリューションサービスであるDistributed Load Testing on <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a><a href="#f-0ce4ed23" name="fn-0ce4ed23" title="https://aws.amazon.com/jp/solutions/implementations/distributed-load-testing-on-aws/">*1</a>上で<a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a>のシナリオを使って、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C9%E9%B2%D9%BB%EE%B8%B3">負荷試験</a>を実施したときの内容をお話ししたいと思います。</p> <h3 id="Distributed-Load-Testing-on-AWS-とは">Distributed Load Testing on <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> とは</h3> <p>Distributed Load Testing on <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>が提供している<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C9%E9%B2%D9%BB%EE%B8%B3">負荷試験</a>のためのソリューションサービスで、</p> <p>CloudFormationテンプレートを使ってデプロイすることができます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20220601/20220601144248.png" width="1200" height="713" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>最大、50タスクのFargate<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/%C9%E9%B2%D9%BB%EE%B8%B3">負荷試験</a>を行う際クライアント側の負荷を意識する必要がないのが特徴です。</p> <p>※導入方法については割愛します。クラスメソッドさんの下記記事を参考にしてください。</p> <p><a href="https://dev.classmethod.jp/articles/distributed-load-testing-on-aws/">AWS&#x306E;&#x8CA0;&#x8377;&#x30C6;&#x30B9;&#x30C8;&#x30BD;&#x30EA;&#x30E5;&#x30FC;&#x30B7;&#x30E7;&#x30F3;&#x3092;&#x8A66;&#x3057;&#x3066;&#x307F;&#x305F; | DevelopersIO</a></p> <h3 id="JMeterのシナリオを使って実行が可能"><a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a>のシナリオを使って実行が可能</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Apache">Apache</a>ソフトウェア財団が提供する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A1%BC%A5%D7%A5%F3%A5%BD%A1%BC%A5%B9">オープンソース</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C9%E9%B2%D9%BB%EE%B8%B3">負荷試験</a>ツール<a href="#f-2e04a219" name="fn-2e04a219" title="https://jmeter.apache.org/">*2</a>です。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a>ではテストシナリオを設定することができ、作ったシナリオは <a class="keyword" href="http://d.hatena.ne.jp/keyword/jmx">jmx</a> ファイルとして保存することができます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20220629/20220629113524.png" width="994" height="658" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Distributed Load Testing on <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>上ではこの<a class="keyword" href="http://d.hatena.ne.jp/keyword/jmx">jmx</a>ファイルを使ってテストを行うことができます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20220629/20220629104139.png" width="629" height="493" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="JMeterシナリオテストで外部ファイルを使う場合"><a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a>シナリオテストで外部ファイルを使う場合</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a>で複雑なシナリオを作成することができます。</p> <p>例えば複数のパスに対してテストを実行したいとき、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSV">CSV</a> DATA Set Config<a href="#f-b902d4ce" name="fn-b902d4ce" title="http://sy5.sakura.ne.jp/jmeter/ref/configurationelements/csvdatasetconfig.html">*3</a>を使うことで複数のパスに対してテストを実行することができます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20220629/20220629114234.png" width="1200" height="377" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>このようなシナリオの場合、<a class="keyword" href="http://d.hatena.ne.jp/keyword/csv">csv</a>ファイルの指定はローカル上ファイルパスが指定されているため、そのまま<a class="keyword" href="http://d.hatena.ne.jp/keyword/jmx">jmx</a> ファイルをDistributed Load Testing on <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> にアップロードして実行をしても<a class="keyword" href="http://d.hatena.ne.jp/keyword/csv">csv</a>ファイルが見つからないためエラーとなり実行することができません。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20220629/20220629133422.png" width="1200" height="132" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この場合、<a class="keyword" href="http://d.hatena.ne.jp/keyword/csv">csv</a>ファイルと<a class="keyword" href="http://d.hatena.ne.jp/keyword/jmx">jmx</a> ファイルを同一の階層に配置し、<strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%EA%C2%D0%A5%D1%A5%B9">相対パス</a></strong> で指定する必要があります。</p> <blockquote><p>If you include <a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a> input files with your <a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a> script file, you must include the relative path of the input files in your <a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a> script file. In addition, the input files must be at the relative path. For example, when your <a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a> input files and script file are in the /home/user directory and you refer to the input files in the <a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a> script file, the path of input files must be ./INPUT_FILES. If you use /home/user/INPUT_FILES instead, the test will fail because it will not be able to find the input files.</p></blockquote> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.aws.amazon.com%2Fja_jp%2Fsolutions%2Flatest%2Fdistributed-load-testing-on-aws%2Fconsiderations.html" title="Design considerations - Distributed Load Testing on AWS" 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://docs.aws.amazon.com/ja_jp/solutions/latest/distributed-load-testing-on-aws/considerations.html">docs.aws.amazon.com</a></cite></p> <p>配置したファイルをzip で固めてDistributed Load Testing on <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>にアップロードすることで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/csv">csv</a>ファイルが読み込まれて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C9%E9%B2%D9%BB%EE%B8%B3">負荷試験</a>を実行することができるようになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20220629/20220629133932.png" width="1200" height="363" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="まとめ">まとめ</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a>のシナリオファイルを使った Distributed Load Testing on <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> について紹介しました。</p> <p>Distributed Load Testing on <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> 単体では1URLへのシンプルなテストしかできませんが、</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/JMeter">JMeter</a>のシナリオファイルを使うことでより複雑なテストを実行することができます。</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/%C9%E9%B2%D9%BB%EE%B8%B3">負荷試験</a>を行う際の参考になれば幸いです。</p> <h3 id="おわりに">おわりに</h3> <p><strong>仲間を募集しております</strong></p> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-0ce4ed23" name="f-0ce4ed23" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://aws.amazon.com/jp/solutions/implementations/distributed-load-testing-on-aws/">https://aws.amazon.com/jp/solutions/implementations/distributed-load-testing-on-aws/</a></span></p> <p class="footnote"><a href="#fn-2e04a219" name="f-2e04a219" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://jmeter.apache.org/">https://jmeter.apache.org/</a></span></p> <p class="footnote"><a href="#fn-b902d4ce" name="f-b902d4ce" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="http://sy5.sakura.ne.jp/jmeter/ref/configurationelements/csvdatasetconfig.html">http://sy5.sakura.ne.jp/jmeter/ref/configurationelements/csvdatasetconfig.html</a></span></p> </div> doj_tomi 【Guzzle】アップロード(multipart/form-dataを送信)したファイルが壊れていた際の対応 hatenablog://entry/13574176438078226088 2022-05-30T10:06:47+09:00 2022-05-30T10:06:47+09:00 はじめに Guzzleによるファイルアップロード方法1(Request Optionsにmultipartを指定) Guzzleによるファイルアップロード方法2(Request Optionsにheadersを指定) まとめ 仲間を募集しております はじめに こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(@suzuki_cecil_)です。 弊社では現在、15年間運用され続けている口コミサービス エキテン のリニューアルプロジェクトに取り組んでおります。 リニューアルプロジェクトではHTTPクライアントに Guzzl… <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#Guzzleによるファイルアップロード方法1Request-Optionsにmultipartを指定">Guzzleによるファイルアップロード方法1(Request Optionsにmultipartを指定)</a></li> <li><a href="#Guzzleによるファイルアップロード方法2Request-Optionsにheadersを指定">Guzzleによるファイルアップロード方法2(Request Optionsにheadersを指定)</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#仲間を募集しております">仲間を募集しております</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(<a href="https://twitter.com/suzuki_cecil_">@suzuki_cecil_</a>)です。</p> <p>弊社では現在、15年間運用され続けている口コミサービス <a href="https://www.ekiten.jp/">エキテン</a> のリニューアルプロジェクトに取り組んでおります。</p> <p>リニューアルプロジェクトではHTTPクライアントに <a href="https://github.com/guzzle/guzzle">Guzzle</a> を利用しているのですが、今回、Guzzleによるファイルのアップロードで少々苦戦することとなったためその時の内容を記事にまとめたいと思います。</p> <h1 id="Guzzleによるファイルアップロード方法1Request-Optionsにmultipartを指定">Guzzleによるファイルアップロード方法1(Request Optionsにmultipartを指定)</h1> <p>POSTやPUTでファイルをアップロードするためには、<strong>Content-Type</strong>を<strong>multipart/form-data</strong>にする必要があります。Guzzleで<strong>Content-Type</strong>を<strong>multipart/form-data</strong>にする方法としては一般に、以下のサンプルコード1のように<strong>Request Options</strong>に<strong>multipart</strong>を指定する方法が知られているかなと思います。</p> <p><strong>サンプルコード1</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synPreProc">use</span> GuzzleHttp\Client; <span class="synType">class</span> FileStorage <span class="synSpecial">{</span> <span class="synType">private</span> Client <span class="synStatement">$</span><span class="synIdentifier">client</span>; <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">private</span> Client <span class="synStatement">$</span><span class="synIdentifier">client</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> upload<span class="synSpecial">(</span>UploadedFile <span class="synStatement">$</span><span class="synIdentifier">uploadedFile</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>client<span class="synType">-&gt;</span>put<span class="synSpecial">(</span><span class="synConstant">&quot;https://example.jp/test_1.jpeg&quot;</span>, <span class="synSpecial">[</span> <span class="synConstant">'multipart'</span> <span class="synStatement">=&gt;</span> <span class="synSpecial">[</span> <span class="synSpecial">[</span> <span class="synConstant">'name'</span> <span class="synStatement">=&gt;</span> <span class="synConstant">''</span>, <span class="synConstant">'contents'</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">fopen</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">uploadedFile</span><span class="synType">-&gt;</span>openFile<span class="synSpecial">()</span><span class="synType">-&gt;</span>getRealPath<span class="synSpecial">()</span>, <span class="synConstant">&quot;r&quot;</span><span class="synSpecial">)</span>, <span class="synSpecial">]</span>, <span class="synSpecial">]</span> <span class="synSpecial">]</span> <span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>Guzzleでは<strong>Request Options</strong>をコンスト<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%A8%A5%B9">エス</a>トをカスタマイズすることが可能です。</p> <p>サンプルコード1では<strong>Request Options</strong>に<strong>multipart</strong>を指定することで、<strong>multipart/form-data</strong>を送信するようにカスタマイズしてあります。</p> <p>ファイルアップロード処理の実装が完了したと思い、サンプルコード1を実行して動作確認したところ、ファイルのアップロード先に <code>test_1.jpeg</code> が生成されていたのですが、生成されたファイルが壊れていました。</p> <p>ファイルが壊れているということで、悪い予感がしたため <code>test_1.jpeg</code> のバイナリデータを確認したところ、やはりヘッダ情報がバイナリデータに含まれていました。</p> <pre class="code" data-lang="" data-unlink> Content-Disposition: form-data; name=&#34;file&#34;; filename=&#34;phpMnWyGV&#34;^M Content-Length: 32643^M </pre> <p>そのため <code>test_1.jpeg</code> がjpgのフォーマットに則っておらずファイルが壊れていると認識されていたのです。</p> <p>通常であれば方法1として記述した、Request Optionsにmultipartを指定する方法でファイルのアップロードを行うことは可能であるはずなのですが、今回私が試したところ何故かファイルのバイナリデータにヘッダ情報が含まれてしまいました(今回、ファイルが壊れた理由は判明できていないです。)</p> <p>そこで後述するRequest Optionsにheadersを指定する方法でmultipart/form-dataを送信し、ファイルのアップロードを試みてみました。</p> <h1 id="Guzzleによるファイルアップロード方法2Request-Optionsにheadersを指定">Guzzleによるファイルアップロード方法2(Request Optionsにheadersを指定)</h1> <p><strong>Request Options</strong>に<strong>multipart</strong>を指定する以外の方法で、<strong>multipart/form-data</strong>を送信できないかを確認するために<a href="https://docs.guzzlephp.org/en/stable">公式ドキュメント</a>のRequest Optionsのページを確認したところ <a href="https://docs.guzzlephp.org/en/stable/request-options.html#headers">headers</a>の項目を見つけました。</p> <p>公式ドキュメントによると<strong>Request Options</strong>に<strong>multipart</strong>を指定するのではなく<strong>headers</strong>としてリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トヘッダを直接定義することが可能なようでした。<strong>Request Options</strong>に<strong>headers</strong>を指定するようにしたのがサンプルコード2です。</p> <p><strong>サンプルコード2</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synPreProc">use</span> GuzzleHttp\Client; <span class="synType">class</span> FileStorage <span class="synSpecial">{</span> <span class="synType">private</span> Client <span class="synStatement">$</span><span class="synIdentifier">client</span>; <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">private</span> Client <span class="synStatement">$</span><span class="synIdentifier">client</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> upload<span class="synSpecial">(</span>UploadedFile <span class="synStatement">$</span><span class="synIdentifier">uploadedFile</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>client<span class="synType">-&gt;</span>put<span class="synSpecial">(</span><span class="synConstant">&quot;https://example.jp/test_2.jpeg&quot;</span>, <span class="synSpecial">[</span> <span class="synConstant">'body'</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">fopen</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">uploadedFile</span><span class="synType">-&gt;</span>openFile<span class="synSpecial">()</span><span class="synType">-&gt;</span>getRealPath<span class="synSpecial">()</span>, <span class="synConstant">&quot;r&quot;</span><span class="synSpecial">)</span>, <span class="synConstant">'headers'</span> <span class="synStatement">=&gt;</span> <span class="synSpecial">[</span><span class="synConstant">'Content-Type'</span> <span class="synStatement">=&gt;</span> <span class="synConstant">'multipart/form-data'</span><span class="synSpecial">]</span>, <span class="synSpecial">]</span> <span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>サンプルコード2を実行してみたところファイルのアップロード先に <code>test_2.jpeg</code> が作られている、またバイナリデータにheaderが含まれていないことが確認できました。</p> <h1 id="まとめ">まとめ</h1> <p>公式ドキュメントのRequest Optionsのページの<a href="https://docs.guzzlephp.org/en/stable/request-options.html#multipart">multipart</a>には以下のように記載があります。</p> <blockquote><p>Summary</p> <p>Sets the body of the request to a multipart/form-data form.</p></blockquote> <p>リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トのボディを <code>multipart/form-data</code> 形式で設定します、とあるためサンプルコード1とサンプルコード2は同義だと思うのですが、サンプルコード1ではファイルのバイナリデータにヘッダ情報が含まれ、サンプルコード2ではファイルのバイナリデータにヘッダ情報が含まれないという結果となりました。</p> <p>今回のケースでは方法1として記述したRequest Optionsにmultipartを指定する方法だとファイルのバイナリデータにヘッダ情報が含まれてしまったため、方法2として記述したRequest Optionsにheadersを指定する方法で、リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トのボディを <code>multipart/form-data</code> 形式で設定する方が安全であると考えます。</p> <h1 id="仲間を募集しております">仲間を募集しております</h1> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> doj_suzuki_cecil 【PHP】【初学者向け】抽象メソッド&インターフェース入門 hatenablog://entry/13574176438068459480 2022-04-01T11:05:20+09:00 2022-04-01T11:05:20+09:00 はじめに インターフェースと抽象クラスの言語仕様の違い 多重継承が不可能である 定義できるメソッドのアクセス修飾子が異なる インターフェースではプロパティの定義ができない インターフェースと抽象メソッドの活用例 インターフェースの活用例 抽象メソッドの活用例 まとめ 仲間を募集しております はじめに こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(@suzuki_cecil_)です。 弊社では少し前からインターンシップとして大学院生のK君が入社されたのですが、そんなK君から「インターフェースに定義するメソッドと抽象クラ… <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#インターフェースと抽象クラスの言語仕様の違い">インターフェースと抽象クラスの言語仕様の違い</a><ul> <li><a href="#多重継承が不可能である">多重継承が不可能である</a></li> <li><a href="#定義できるメソッドのアクセス修飾子が異なる">定義できるメソッドのアクセス修飾子が異なる</a></li> <li><a href="#インターフェースではプロパティの定義ができない">インターフェースではプロパティの定義ができない</a></li> </ul> </li> <li><a href="#インターフェースと抽象メソッドの活用例">インターフェースと抽象メソッドの活用例</a><ul> <li><a href="#インターフェースの活用例">インターフェースの活用例</a></li> <li><a href="#抽象メソッドの活用例">抽象メソッドの活用例</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#仲間を募集しております">仲間を募集しております</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(<a href="https://twitter.com/suzuki_cecil_">@suzuki_cecil_</a>)です。</p> <p>弊社では少し前から<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3%A5%B7%A5%C3%A5%D7">インターンシップ</a>として大学院生のK君が入社されたのですが、そんなK君から「インターフェースに定義するメソッドと抽象クラスに定義する抽象メソッドは何が違うのでしょう?」と質問を受けました。どうやらK君は抽象クラスとインターフェースについて以下のように理解していたようでした。</p> <ul> <li>インターフェースはメソッドの定義はできるが実装をすることはできない</li> <li>抽象クラスにはメソッドを実装することができるが、抽象メソッドとして定義だけを行うことができる</li> </ul> <p>理解としては間違っていないのですが、メソッドの定義のみを行うという点が共通であるがために、どういう時にインターフェースに定義し、どういう時に抽象メソッドとして定義すればいいのかが整理できていないようでした。</p> <p>確かに抽象クラスやインターフェースは<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>をはじめとする<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%D6%A5%B8%A5%A7%A5%AF%A5%C8%BB%D8%B8%FE">オブジェクト指向</a>初学者の方の多くが躓きがちな点だと思います(かくゆう私も初学者の頃は、あまり理解できていなかったです)</p> <p>せっかくなので今回はK君に対して行った解説をもとに記事にしてTechBlogに投稿します。少しでも多くの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%D6%A5%B8%A5%A7%A5%AF%A5%C8%BB%D8%B8%FE">オブジェクト指向</a>初学者の方に届いてくれればいいなと思います。</p> <h1 id="インターフェースと抽象クラスの言語仕様の違い">インターフェースと抽象クラスの言語仕様の違い</h1> <p>そもそも<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>の言語仕様としてインターフェースと抽象クラスの違いは以下の3つだと思います。</p> <h3 id="多重継承が不可能である">多重継承が不可能である</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>など一部の言語においては複数の(抽象)クラスを継承(多重継承)することが可能ですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>や<a class="keyword" href="http://d.hatena.ne.jp/keyword/Java">Java</a>など多くの<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 href="https://ja.wikipedia.org/wiki/%E8%8F%B1%E5%BD%A2%E7%B6%99%E6%89%BF%E5%95%8F%E9%A1%8C">菱形継承問題</a>による複雑さを排除するためです。</p> <p>一方で複数のインターフェースを実装することは可能です。ゆえにインターフェース分離の原則(<a class="keyword" href="http://d.hatena.ne.jp/keyword/ISP">ISP</a>)でも述べられている通り、インターフェースには最小限のメソッドの定義のみを行うということが可能になっています。またPHP8.0, 8.1で登場したUNION型や交差型を活用することでインターフェースを組み合わせることで実装の幅を広げることも可能になっています(いわゆる型パズル)。</p> <p>この辺りに関しては語り出すとキリがないので、本記事では割愛させていただきます。</p> <h3 id="定義できるメソッドのアクセス修飾子が異なる">定義できるメソッドのアクセス修飾子が異なる</h3> <p>インターフェースは「接点」「境界面」といった意味を持つ通り、外部からの参照を前提としております。そのためインターフェースにはアクセス修飾子がpublicのメソッドのみ定義できます。</p> <p>一方で抽象クラスは外部からの参照を前提としていないためアクセス修飾子がpublicもしくはprotectedのメソッドを定義することが可能です(継承を前提としているためprivateのメソッドを定義することはできません)</p> <h3 id="インターフェースではプロパティの定義ができない">インターフェースではプロパティの定義ができない</h3> <p>インターフェースは具体的な処理を持たない関係からプロパティを定義することができず、定義できるのはpublicの定数のみで、またインターフェースを実装したクラスで定数をオーバーライドすることができません。</p> <p>一方で抽象クラスではプロパティ/ローカル変数/定数いずれも定義することができ、アクセス修飾子にはpublic, protected, privateを指定することができます。またインターフェースとは異なりサブクラスでプロパティ/定数をオーバーライドすることが可能です。</p> <h1 id="インターフェースと抽象メソッドの活用例">インターフェースと抽象メソッドの活用例</h1> <p>インターフェースと抽象クラスの言語仕様の違いが分かったところで、本題のインターフェースと抽象メソッドの活用例をサンプルコードとともに見ていきましょう。</p> <h3 id="インターフェースの活用例">インターフェースの活用例</h3> <p>答えから言うとインターフェースは<strong>依存関係を整理する</strong>ために使います。とは言っても、この一文だけでは理解が難しいかと思いますので詳しく解説させていただきます。</p> <p><strong>サンプルコード1</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">class</span> FizzBuzz <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">private</span> DisplayPrinter <span class="synStatement">$</span><span class="synIdentifier">displayPrinter</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> execute<span class="synSpecial">(</span><span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">n</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">for</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">=</span> <span class="synConstant">1</span>; <span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">&lt;=</span> <span class="synStatement">$</span><span class="synIdentifier">n</span>; <span class="synStatement">$</span><span class="synIdentifier">i</span><span class="synStatement">++</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">if</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">15</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>displayPrinter<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;FizzBuzz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">elseif</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">5</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>displayPrinter<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;Buzz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">elseif</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">3</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>displayPrinter<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;Fizz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">else</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>displayPrinter<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">class</span> DisplayPrinter <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> output<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> void <span class="synSpecial">{</span> <span class="synPreProc">echo</span> <span class="synStatement">$</span><span class="synIdentifier">value</span> <span class="synStatement">.</span> <span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synStatement">$</span><span class="synIdentifier">fizzBuzz</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> FizzBuzz<span class="synSpecial">(</span><span class="synPreProc">new</span> DisplayPrinter<span class="synSpecial">())</span>; <span class="synStatement">$</span><span class="synIdentifier">fizzBuzz</span><span class="synType">-&gt;</span>execute<span class="synSpecial">(</span><span class="synConstant">15</span><span class="synSpecial">)</span>; </pre> <p>サンプルコード1は見ての通り、<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>を行うプログラムで結果は標準出力しております。</p> <p>※<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスから責務を分離するために出力処理はDisplayPrinterに移譲しております。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスはDisplayPrinterクラスを参照しています。このように他のクラスやメソッドを参照する関係性のことを依存関係と言い、<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスはDisplayPrinterクラスに依存している、というように表現されます。</p> <p>では、結果を標準出力するのではなくテキストファイルに出力するように修正するとしたらどうでしょう。</p> <p><strong>サンプルコード2</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">class</span> FizzBuzz <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">private</span> FilePrinter <span class="synStatement">$</span><span class="synIdentifier">filePrinter</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> execute<span class="synSpecial">(</span><span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">n</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">for</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">=</span> <span class="synConstant">1</span>; <span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">&lt;=</span> <span class="synStatement">$</span><span class="synIdentifier">n</span>; <span class="synStatement">$</span><span class="synIdentifier">i</span><span class="synStatement">++</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">if</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">15</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>filePrinter<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;FizzBuzz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">elseif</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">5</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>filePrinter<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;Buzz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">elseif</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">3</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>filePrinter<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;Fizz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">else</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>filePrinter<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">class</span> FilePrinter <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> output<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> void <span class="synSpecial">{</span> <span class="synIdentifier">file_put_contents</span><span class="synSpecial">(</span><span class="synConstant">&quot;result.txt&quot;</span>, <span class="synStatement">$</span><span class="synIdentifier">value</span> <span class="synStatement">.</span> <span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>, FILE_APPEND<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synStatement">$</span><span class="synIdentifier">fizzBuzz</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> FizzBuzz<span class="synSpecial">(</span><span class="synPreProc">new</span> FilePrinter<span class="synSpecial">())</span>; <span class="synStatement">$</span><span class="synIdentifier">fizzBuzz</span><span class="synType">-&gt;</span>execute<span class="synSpecial">(</span><span class="synConstant">15</span><span class="synSpecial">)</span>; </pre> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>の結果をファイル出力するようにしたプログラムがサンプルコード2になります。</p> <p>サンプルコード1と比較すると以下の点が変更されています。</p> <ol> <li>結果を出力するクラスがDisplayPrinterクラスからFilePrinterクラスに変更されている</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスのコンスト<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%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>がDisplayPrinter型からFilePrinter型に変更されている、ついでにプロパティの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>も変更している</li> </ol> <p>責務を分離するために<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスから結果を出力する処理を切り出したのに、結果を出力する処理を変更するのに<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスを修正するという矛盾が発生してしまいました。</p> <p>この矛盾を回避するために活用されるのがインターフェースです。インターフェースを用いた場合どうなるでしょうか。</p> <p><strong>サンプルコード3</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">class</span> FizzBuzz <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">private</span> Printer <span class="synStatement">$</span><span class="synIdentifier">printer</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> execute<span class="synSpecial">(</span><span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">n</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">for</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">=</span> <span class="synConstant">1</span>; <span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">&lt;=</span> <span class="synStatement">$</span><span class="synIdentifier">n</span>; <span class="synStatement">$</span><span class="synIdentifier">i</span><span class="synStatement">++</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">if</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">15</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>printer<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;FizzBuzz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">elseif</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">5</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>printer<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;Buzz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">elseif</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">3</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>printer<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;Fizz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">else</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>printer<span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">interface</span> Printer <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> output<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> void; <span class="synSpecial">}</span> <span class="synType">class</span> DisplayPrinter <span class="synType">implements</span> Printer <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> output<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> void <span class="synSpecial">{</span> <span class="synPreProc">echo</span> <span class="synStatement">$</span><span class="synIdentifier">value</span> <span class="synStatement">.</span> <span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">class</span> FilePrinter <span class="synType">implements</span> Printer <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> output<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> void <span class="synSpecial">{</span> <span class="synIdentifier">file_put_contents</span><span class="synSpecial">(</span><span class="synConstant">&quot;result.txt&quot;</span>, <span class="synStatement">$</span><span class="synIdentifier">value</span> <span class="synStatement">.</span> <span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>, FILE_APPEND<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synStatement">$</span><span class="synIdentifier">fizzBuzz</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> FizzBuzz<span class="synSpecial">(</span><span class="synPreProc">new</span> FilePrinter<span class="synSpecial">())</span>; <span class="synStatement">$</span><span class="synIdentifier">fizzBuzz</span><span class="synType">-&gt;</span>execute<span class="synSpecial">(</span><span class="synConstant">15</span><span class="synSpecial">)</span>; </pre> <p>インターフェースを用いた場合のプログラムがサンプルコード3になります。</p> <p>サンプルコード2と比較すると以下の点が変更されています。</p> <ol> <li>Printerインターフェースを定義している</li> <li>DisplayPrinterクラスとFilePrinterクラスはPrinterインターフェースを実装している</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスのコンスト<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%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>がFilePrinter型からPrinter型に変更されている、ついでにプロパティの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>も変更している</li> </ol> <p>サンプルコード3のようにインターフェースを用いることで、例えば結果を標準出力するように再変更したい場合、<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスのコンスト<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%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>をDisplayPrinterの<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/FizzBuzz">FizzBuzz</a>クラスを修正する必要はなくなりました。</p> <p>サンプルコード1では<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスはDisplayPrinterクラスに依存しているのに対して、サンプルコード3では<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスもDisplayPrinterクラス(およびFilePrinterクラス)もPrinterインターフェースに依存しています。</p> <p>書籍「<a href="https://www.amazon.co.jp/%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA%E3%81%AE%E5%A5%A5%E7%BE%A9-%E7%AC%AC2%E7%89%88-%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E9%96%8B%E7%99%BA%E3%81%AE%E7%A5%9E%E9%AB%84%E3%81%A8%E5%8C%A0%E3%81%AE%E6%8A%80-%E3%83%AD%E3%83%90%E3%83%BC%E3%83%88%E3%83%BBC%E3%83%BB%E3%83%9E%E3%83%BC%E3%83%81%E3%83%B3/dp/4797347783">アジャイルソフトウェア開発の奥義</a>」では、このように書かれています。</p> <blockquote><p>上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである。</p></blockquote> <p>上位や下位といったレイヤーの解説は一旦省略させていただきますが、大規模な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を作り上げていく上で抽象(インターフェース)に依存するようにすることで正しい依存関係を作り上げ、 <strong>依存関係を整理する</strong> ことは非常に重要であるということは覚えておくと必ず役に立ちます。</p> <h3 id="抽象メソッドの活用例">抽象メソッドの活用例</h3> <p>インターフェースが<strong>依存関係を整理する</strong> のに活用されるのに対して、抽象クラスを含むクラスの継承はクラスの再利用をする際に活用されます。特に抽象メソッドに関して着目してみると、個人的な意見としては<a class="keyword" href="http://d.hatena.ne.jp/keyword/TemplateMethod">TemplateMethod</a>パターンを適用する際が一番の活用どころではないかと考えます。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/TemplateMethod">TemplateMethod</a>パターンとは一連の流れが定まっている処理(テンプレート)に対して一部の処理のみを場合わけしたいときに活用される<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>です。</p> <p>先ほどのサンプルコード同じく<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>を例に<a class="keyword" href="http://d.hatena.ne.jp/keyword/TemplateMethod">TemplateMethod</a>パターンの<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><strong>サンプルコード4</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">abstract</span> <span class="synType">class</span> FizzBuzz <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> execute<span class="synSpecial">(</span><span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">n</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">for</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">=</span> <span class="synConstant">1</span>; <span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">&lt;=</span> <span class="synStatement">$</span><span class="synIdentifier">n</span>; <span class="synStatement">$</span><span class="synIdentifier">i</span><span class="synStatement">++</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">if</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">15</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;FizzBuzz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">elseif</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">5</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;Buzz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">elseif</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span> <span class="synStatement">%</span> <span class="synConstant">3</span> <span class="synStatement">===</span> <span class="synConstant">0</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synConstant">&quot;Fizz&quot;</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">else</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span><span class="synIdentifier">output</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">i</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">abstract</span> <span class="synType">protected</span> <span class="synPreProc">function</span> output<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> void; <span class="synSpecial">}</span> <span class="synType">class</span> DisplayPrintedFizzBuzz <span class="synType">extends</span> FizzBuzz <span class="synSpecial">{</span> <span class="synType">protected</span> <span class="synPreProc">function</span> output<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> void <span class="synSpecial">{</span> <span class="synPreProc">echo</span> <span class="synStatement">$</span><span class="synIdentifier">value</span> <span class="synStatement">.</span> <span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">class</span> FilePrintedFizzBuzz <span class="synType">extends</span> FizzBuzz <span class="synSpecial">{</span> <span class="synType">protected</span> <span class="synPreProc">function</span> output<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> void <span class="synSpecial">{</span> <span class="synIdentifier">file_put_contents</span><span class="synSpecial">(</span><span class="synConstant">&quot;result.txt&quot;</span>, <span class="synStatement">$</span><span class="synIdentifier">value</span> <span class="synStatement">.</span> <span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>, FILE_APPEND<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synStatement">$</span><span class="synIdentifier">displayPrintedFizzBuzz</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> DisplayPrintedFizzBuzz<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">displayPrintedFizzBuzz</span><span class="synType">-&gt;</span>execute<span class="synSpecial">(</span><span class="synConstant">15</span><span class="synSpecial">)</span>; <span class="synStatement">$</span><span class="synIdentifier">filePrintedFizzBuzz</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> FilePrintedFizzBuzz<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">filePrintedFizzBuzz</span><span class="synType">-&gt;</span>execute<span class="synSpecial">(</span><span class="synConstant">15</span><span class="synSpecial">)</span>; </pre> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>の一連の処理は以下のように分解することができます。</p> <ol> <li>1からnまで繰り返す</li> <li>15で割り切れる場合は<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>, 5で割り切れる場合はBuzz, 3で割り切れる場合は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Fizz">Fizz</a>、それ以外の場合はその数字を結果とする</li> <li>結果を出力する</li> </ol> <p>この一連の処理をそのままに「3. 結果を出力する」の具体的な処理だけを場合分けするようにしたのがサンプルコード4のプログラムになります。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスのexecuteメソッドを見てみると1からnまで繰り返し、15で割り切れる場合は<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>, 5で割り切れる場合はBuzz, 3で割り切れる場合は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Fizz">Fizz</a>、それ以外の場合はその数字を結果とし、その結果を出力するという、前述した1~3の処理の流れが記述されており、結果の出力の具体的な処理がDisplayPrintedFizzBuzzクラスおよびFilePrintedFizzBuzzクラスのoutputメソッドに記述されているのがわかるかと思います。</p> <p>このように<a class="keyword" href="http://d.hatena.ne.jp/keyword/TemplateMethod">TemplateMethod</a>パターンでは一連の流れを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D1%A1%BC%A5%AF%A5%E9%A5%B9">スーパークラス</a>(今回の場合は<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラス)、場合分けしたい処理をサブクラス(今回の場合はDisplayPrintedFizzBuzzクラスおよびFilePrintedFizzBuzzクラス)に記述します。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D1%A1%BC%A5%AF%A5%E9%A5%B9">スーパークラス</a>に一連の流れを記述する際に場合わけする処理を具体的に書く必要はありませんが、一連の処理の中に組み込む必要があります。その際に場合分けしたい処理を抽象メソッドとして切り出すことによって具体的な処理を記述することなく一連の処理の中に組み込めるようになります。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/TemplateMethod">TemplateMethod</a>パターンを例に解説したように抽象メソッドはインターフェースとは異なりクラス内部からの参照のために活用されることが多いため、言語仕様としてはアクセス修飾子にpublicもしくはprotectedを指定することがきますが、実際に活用する場面を考えてみると基本的にはprotectedメソッドとして定義されることがほとんどだと思います。</p> <h1 id="まとめ">まとめ</h1> <p>サンプルコード3とサンプルコード4の共通点として<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラスから出力処理を移譲するという点で共通しています。</p> <p>ただしインターフェースを活用したサンプルコード3では<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラス外に移譲しているのに対して、抽象メソッドを活用したサンプルコード4では<a class="keyword" href="http://d.hatena.ne.jp/keyword/FizzBuzz">FizzBuzz</a>クラス内(サブクラス)の他のメソッドに移譲しているというように、移譲先が異なる点がサンプルコード3とサンプルコード4の決定的な違いとなります。</p> <p>一概にどちらの方が優れていると断言することは難しいです(※私個人としてはインターフェースの方が可読性に長けていると考えています)が、作りたいプログラムに応じて、クラス外に処理を移譲したい場合はインターフェースを、クラス内に処理を移譲したい場合は抽象メソッドを活用するといいでしょう。</p> <h1 id="仲間を募集しております">仲間を募集しております</h1> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> doj_suzuki_cecil GAS で Slack 投稿してみたというお話 hatenablog://entry/13574176438068021180 2022-03-01T10:31:33+09:00 2022-03-01T10:31:33+09:00 はじめに こんにちは!株式会社デザインワン・ジャパンでエキテンの開発を担当しているサービス開発部の寺井です。 弊社ではスライド出社という制度があり、前日までに申請さえすれば、勤務開始時間を7時から11時の間で1時間単位でずらすことができます。 この制度は「前日夜遅いから翌日は11時出社にしよう」「退社後予定あるから7時出社にしよう」という感じで使える、プライベートの予定に強い制度です。 ただ、自由度が高い上に欠点がありました。それは、翌営業日のチームメンバーの出社時間がぱっとわからない、ということです。 各々の Google カレンダーには勤務時間がわかるように登録されているのですが、いちいち… <h2>はじめに</h2> <p>こんにちは!株式会社デザインワン・ジャパンでエキテンの開発を担当しているサービス開発部の寺井です。</p> <p>弊社ではスライド出社という制度があり、前日までに申請さえすれば、勤務開始時間を7時から11時の間で1時間単位でずらすことができます。 この制度は「前日夜遅いから翌日は11時出社にしよう」「退社後予定あるから7時出社にしよう」という感じで使える、プライベートの予定に強い制度です。</p> <p>ただ、自由度が高い上に欠点がありました。それは、翌営業日のチームメンバーの出社時間がぱっとわからない、ということです。 各々の <a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a> カレンダーには勤務時間がわかるように登録されているのですが、いちいち見に行かないと行けないし、勤務時間以外にも予定が入っていたりするので見にくいです。 それを解決するために、翌営業日のチームメンバーの勤怠情報を、 GAS (<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google%20Apps">Google Apps</a> Script) を使って、 Slack に流す <a class="keyword" href="http://d.hatena.ne.jp/keyword/Bot">Bot</a> を作りました。</p> <p>今回はそういうお話です。</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/Bot">Bot</a> の設定</h2> <p>Slack の <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> 設定から、アプリを新しく作成し、投稿するための token を控えておきます(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Bot">Bot</a> User OAuth Token の方を使います)。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20220228/20220228171809.png" alt="f:id:doj_rterai:20220228171809p:plain" width="1200" height="659" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>GAS の方に SlackApp というライブラリをインストールします。これを使うことにより、簡単に Slack へメッセージを投げることができます(library を日本語で言うと図書館だけどさぁ...)。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20220228/20220228172657.png" alt="f:id:doj_rterai:20220228172657p:plain" width="1200" height="264" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>そして、先ほど控えた token をプロジェクトのプロパティに設定します。今回は TOKEN という値にセットします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20220228/20220228172444.png" alt="f:id:doj_rterai:20220228172444p:plain" width="1200" height="737" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>以上で <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> の設定は完了です。実際にできるか試してみましょう。下記コードで Slack に投稿できるようになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> main() <span class="synIdentifier">{</span> bot(<span class="synConstant">&quot;てーすーと&quot;</span>, <span class="synConstant">&quot;#scpecial_hoge_hoge_value&quot;</span>); <span class="synIdentifier">}</span> <span class="synIdentifier">function</span> bot(message, channelId) <span class="synIdentifier">{</span> <span class="synComment">// API用のトークン</span> <span class="synStatement">const</span> slackApp = SlackApp.create(PropertiesService.getScriptProperties().getProperty(<span class="synConstant">'TOKEN'</span>)); <span class="synStatement">const</span> options = <span class="synIdentifier">{</span> username: <span class="synConstant">&quot;sliderman&quot;</span>, icon_emoji: <span class="synConstant">&quot;:man-surfing:&quot;</span> <span class="synIdentifier">}</span> slackApp.postMessage(channelId, message, options); <span class="synIdentifier">}</span> </pre> <p>実行したところ Slack に投稿されていました!ちなみに今回作る <a class="keyword" href="http://d.hatena.ne.jp/keyword/Bot">Bot</a> の名前は sliderman です。スライド情報を伝える人ということから<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>しました。どこかの蜘蛛男は関係ないです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20220228/20220228173216.png" alt="f:id:doj_rterai:20220228173216p:plain" width="414" height="118" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a> カレンダーにフォーマット通り予定を入れてもらう</h2> <p>各自のカレンダーに、9時から18時の間でスライドによって不在になる時間帯に <code>【スライドのため不在】</code> という文言を含んだタイトルの予定を入れてもらいます(この人の場合、毎日10時出社にしているため、繰り返し設定で登録していますね)。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20220228/20220228164622.png" alt="f:id:doj_rterai:20220228164622p:plain" width="1200" height="381" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2>GAS から <a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a> カレンダーを呼び出す</h2> <p>メンバーの email と取得したい日付を渡して、その人のその日の予定を全て取得する関数を作ります。 <code>getNextDay()</code> は次の日を返す関数です。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> getEventFromGoogleCalendar(member, day) <span class="synIdentifier">{</span> <span class="synStatement">const</span> calendar = CalendarApp.getCalendarById(member.email); <span class="synStatement">return</span> calendar.getEvents(day, getNextDay(day)); <span class="synIdentifier">}</span> </pre> <p>カレンダー情報をメンバーオブジェクトに追加します。 <code>events</code> のところにカレンダーから取得してきたデータを入れます。 それによって後は <code>menbersWithEvents</code> という<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>が微妙な変数で処理していけます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> membersWithEvents = members.map(m =&gt; <span class="synIdentifier">{</span>m<span class="synIdentifier">[</span><span class="synConstant">'events'</span><span class="synIdentifier">]</span> = getEventFromGoogleCalendar(m, getNextWeekday()); <span class="synStatement">return</span> m;<span class="synIdentifier">}</span>); </pre> <p>ちなみに <code>getNextWeekday()</code> は翌営業日の日付を返してきます。営業日かどうかを判定する関数を作成し、明日の日付から一日ずつ判定していくという原始的な方法です(29日間連続で休みではないという前提。29日も連続で休みがある会社があったら入りたい)。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// 翌営業日の日付を取得する</span> <span class="synIdentifier">function</span> getNextWeekday() <span class="synIdentifier">{</span> <span class="synIdentifier">var</span> date = <span class="synStatement">new</span> <span class="synType">Date</span>(); <span class="synStatement">for</span> (<span class="synIdentifier">var</span> tmp = 1; tmp &lt; 30; tmp++) <span class="synIdentifier">{</span> date.setDate(date.getDate() + 1); <span class="synStatement">if</span> (isBusinessDay(date)) <span class="synIdentifier">{</span> <span class="synStatement">break</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synStatement">return</span> date; <span class="synIdentifier">}</span> <span class="synComment">// 与えられた日付が営業日かどうか判定する</span> <span class="synIdentifier">function</span> isBusinessDay(date)<span class="synIdentifier">{</span> <span class="synComment">// 土日の場合</span> <span class="synStatement">if</span> (date.getDay() == 0 || date.getDay() == 6) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synConstant">false</span>; <span class="synIdentifier">}</span> <span class="synComment">// 祝日の場合(google の日本の祝日カレンダーを使って判定)</span> <span class="synStatement">const</span> calJa = CalendarApp.getCalendarById(<span class="synConstant">'ja.japanese#holiday@group.v.calendar.google.com'</span>); <span class="synStatement">if</span> (calJa.getEventsForDay(date).length &gt; 0) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synConstant">false</span>; <span class="synIdentifier">}</span> <span class="synComment">// 適宜年末年始の場合を加える</span> <span class="synStatement">return</span> <span class="synConstant">true</span>; <span class="synIdentifier">}</span> </pre> <h2>いい感じに整形する</h2> <p>あとはデータをいい感じに整形するだけです。この部分は解説しません(笑)。 というのもまだまだ長くなるというのと、整形部分は使う状況によって全然変わってくるので、そこは必要に応じて整形してもらえればと思います。</p> <p>そして、いい感じに整形した結果...、で・き・た!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20220228/20220228184213.png" alt="f:id:doj_rterai:20220228184213p:plain" width="738" height="474" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>(下の段は、スライドと同様に、在宅か出社かが分かるようにしたものである)</p> <p>こうして、誰が何時に出勤か(そして、在宅か出社か)が簡単に分かるようになりました。 めでたしめでたし。</p> <h2>仲間を募集しております</h2> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> doj_rterai 静的ファイル配信システムをCloudFront+S3の構成に移行したときCORSエラーにハマったお話 hatenablog://entry/13574176438046961265 2022-02-28T09:42:27+09:00 2022-03-01T08:15:31+09:00 まえがき 移行内容について フォントファイルでCORSエラーが出力されてる そもそもCORSとは CORSが必要な条件を確認してみた S3側でバケットポリシー対応が必要だった まとめ おわりに まえがき 最近、運動不足で地球の重力を日増しに感じている インフラエンジニアの 冨田(@komitta)です。 今回は移行作業時のちょっとした失敗談をお話ししたいと思います。 移行内容について エキテンでは静的ファイルを配信するドメインとユーザーが閲覧する公開用のドメインを分けて構築しています。 静的ファイルを配信する環境の元々の構成はEC2サーバーを使用して公開しておりましたが、 より可用性およびコス… <ul class="table-of-contents"> <li><a href="#まえがき">まえがき</a></li> <li><a href="#移行内容について">移行内容について</a></li> <li><a href="#フォントファイルでCORSエラーが出力されてる">フォントファイルでCORSエラーが出力されてる</a></li> <li><a href="#そもそもCORSとは">そもそもCORSとは</a></li> <li><a href="#CORSが必要な条件を確認してみた">CORSが必要な条件を確認してみた</a></li> <li><a href="#S3側でバケットポリシー対応が必要だった">S3側でバケットポリシー対応が必要だった</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h3 id="まえがき">まえがき</h3> <p>最近、運動不足で地球の重力を日増しに感じている インフラエンジニアの 冨田(<a href="https://twitter.com/komitta">@komitta</a>)です。 今回は移行作業時のちょっとした失敗談をお話ししたいと思います。</p> <h3 id="移行内容について">移行内容について</h3> <p>エキテンでは静的ファイルを配信する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>とユーザーが閲覧する公開用の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>を分けて構築しています。</p> <p>静的ファイルを配信する環境の元々の構成はEC2サーバーを使用して公開しておりましたが、 より可用性およびコストパフォーマンスの優れているCloudFront+S3の構成に切り替え対応を行いました。</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/d/doj_tomi/20220228/20220228070917.png" alt="f:id:doj_tomi:20220228070917p:plain" width="500" height="346" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>構成図</figcaption></figure></p> <p>移行が終わりページも表示されてめでたしめでたし</p> <p>...とはならなかったお話です。</p> <h3 id="フォントファイルでCORSエラーが出力されてる">フォントファイルでCORSエラーが出力されてる</h3> <p>移行対応後に、表示がおかしいとタレコミがあり見てみると、とあるページにてフォントファイルのみCORSエラーとなっている状態を発見しました。</p> <p><figure class="figure-image figure-image-fotolife" title="CORSエラー画面"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20220222/20220222153847.png" alt="f:id:doj_tomi:20220222153847p:plain" width="1200" height="140" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CORSエラー画面</figcaption></figure></p> <p>そこでCORSについて改めて調べてみることに。</p> <h3 id="そもそもCORSとは">そもそもCORSとは</h3> <p>CORSはCross-Origin Resource Sharingの略で別<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</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/XMLHttpRequest">XMLHttpRequest</a>等)についてデフォルトではアクセスできないようブロックする仕組みです。<a href="#f-902947dd" name="fn-902947dd" title="https://developer.mozilla.org/ja/docs/Web/HTTP/CORS">*1</a></p> <p><figure class="figure-image figure-image-fotolife" title="CORSの説明画像"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20220228/20220228062729.png" alt="f:id:doj_tomi:20220228062729p:plain" width="925" height="643" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CORSの説明画像</figcaption></figure></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.mozilla.org%2Fja%2Fdocs%2FWeb%2FHTTP%2FCORS" title="オリジン間リソース共有 (CORS) - HTTP | MDN" 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://developer.mozilla.org/ja/docs/Web/HTTP/CORS">developer.mozilla.org</a></cite></p> <p>たしかにエキテンサイトは公開ページに含まれる静的ファイルと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>が異なっているので意識する必要はありそう。 ただし <a class="keyword" href="http://d.hatena.ne.jp/keyword/XMLHttpRequest">XMLHttpRequest</a>のアクセスでもないし、そもそも許可できていなかったらページ全体が見れないはず。。</p> <p>なぜフォントファイルだけ...</p> <p>というわけでもう少し詳しく確認してみました。</p> <h3 id="CORSが必要な条件を確認してみた">CORSが必要な条件を確認してみた</h3> <p><a href="https://developer.mozilla.org/ja/docs/Web/HTTP/CORS">Mozillaのサイト</a>にCORSが使うリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トの記述が記載されてました。</p> <blockquote><p>What requests use CORS?</p> <p>This cross-origin sharing standard can enable cross-origin HTTP requests for:</p> <ul> <li><p>Invocations of the <a class="keyword" href="http://d.hatena.ne.jp/keyword/XMLHttpRequest">XMLHttpRequest</a> or Fetch APIs, as discussed above.</p></li> <li><p><strong>Web Fonts (for cross-domain font usage in @font-face within <a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>) </strong> , so that servers can deploy TrueType fonts that can only be loaded cross-origin and used by web sites that are permitted to do so.</p></li> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/WebGL">WebGL</a> textures.</p></li> <li><p>Images/video frames drawn to a <a class="keyword" href="http://d.hatena.ne.jp/keyword/canvas">canvas</a> using drawImage().</p></li> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a> Shapes from images.</p></li> </ul> <p>This is a general article about Cross-Origin Resource Sharing and includes a discussion of the necessary HTTP headers.</p></blockquote> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.mozilla.org%2Fja%2Fdocs%2FWeb%2FHTTP%2FCORS" title="オリジン間リソース共有 (CORS) - HTTP | MDN" 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://developer.mozilla.org/ja/docs/Web/HTTP/CORS">developer.mozilla.org</a></cite></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>内部から呼び出されるフォント(<strong>Web Fonts (for cross-domain font usage in @font-face within <a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>) </strong>)の記述を確認。</p> <p>今回はこれに該当していました。</p> <p>つまり<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>内部から呼び出される別<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>へのフォントファイルに関してはレスポンスヘッダーの中に <a class="keyword" href="http://d.hatena.ne.jp/keyword/Access">Access</a>-Control-Allow-Origin の値を返す必要がありました。</p> <h3 id="S3側でバケットポリシー対応が必要だった">S3側で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%B1%A5%C3%A5%C8">バケット</a>ポリシー対応が必要だった</h3> <p>CloudFrontのバックエンドにいるのはS3なので<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>のマニュアルを確認したところ、マニュアルにCORSの設定方法がしっかり書かれていました\(^o^)/</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.aws.amazon.com%2Fja_jp%2FAmazonS3%2Flatest%2Fuserguide%2FManageCorsUsing.html" title="CORS の設定 - Amazon Simple Storage Service" 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://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ManageCorsUsing.html">docs.aws.amazon.com</a></cite></p> <p>S3 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%B1%A5%C3%A5%C8">バケット</a> > アクセス許可 > CORS設定</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">[</span> <span class="synIdentifier">{</span> <span class="synConstant">&quot;AllowedHeaders&quot;</span>: <span class="synIdentifier">[</span> <span class="synConstant">&quot;*&quot;</span> <span class="synIdentifier">]</span>, <span class="synConstant">&quot;AllowedMethods&quot;</span>: <span class="synIdentifier">[</span> <span class="synConstant">&quot;GET&quot;</span>, <span class="synConstant">&quot;HEAD&quot;</span> <span class="synIdentifier">]</span>, <span class="synConstant">&quot;AllowedOrigins&quot;</span>: <span class="synIdentifier">[</span> <span class="synConstant">&quot;mydomain.com&quot;</span> <span class="synIdentifier">]</span>, <span class="synConstant">&quot;ExposeHeaders&quot;</span>: <span class="synIdentifier">[]</span>, <span class="synConstant">&quot;MaxAgeSeconds&quot;</span>: 3000 <span class="synIdentifier">}</span> <span class="synIdentifier">]</span> </pre> <p>上記の設定後、エラーがでなくなることを確認し今度こそ完!!</p> <p><figure class="figure-image figure-image-fotolife" title="CORSリクエスト成功画面"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20220228/20220228060925.png" alt="f:id:doj_tomi:20220228060925p:plain" width="1200" height="79" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CORSリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト成功画面</figcaption></figure></p> <h3 id="まとめ">まとめ</h3> <p>CORS周りの理解がふんわりしていたために、発生した問題でした。</p> <p>仕様の理解は大事ですね。(英語力も大事)</p> <p>あと思ったよりこの問題の情報が少なくて、原因究明にたどり着くのに時間がかかりました。</p> <h3 id="おわりに">おわりに</h3> <p><strong>仲間を募集しております</strong></p> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集・採用・求人情報 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-902947dd" name="f-902947dd" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://developer.mozilla.org/ja/docs/Web/HTTP/CORS">https://developer.mozilla.org/ja/docs/Web/HTTP/CORS</a></span></p> </div> doj_tomi 【PHP 8.1】とうとうPHPにもEnumがやってきた hatenablog://entry/13574176438050305633 2022-01-12T18:11:58+09:00 2022-01-12T18:11:58+09:00 概要説明 そもそもEnumとは何か? Enumの登場でどう変わるか クラス(擬似的なEnum)による実装 Enumによる実装 Enumの仕様 cases Pure Enum, Backed Enum from, tryFrom 継承、インターフェース メソッド、staticメソッドの定義 トレイト 感想 おわりに 概要説明 こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(@suzuki_cecil_)です。 弊社が運用しているエキテンのバックエンドはほぼPHPで実装しております。そんなPHPに関して、先日メジャーリリー… <ul class="table-of-contents"> <li><a href="#概要説明">概要説明</a></li> <li><a href="#そもそもEnumとは何か">そもそもEnumとは何か?</a></li> <li><a href="#Enumの登場でどう変わるか">Enumの登場でどう変わるか</a><ul> <li><a href="#クラス擬似的なEnumによる実装">クラス(擬似的なEnum)による実装</a></li> <li><a href="#Enumによる実装">Enumによる実装</a></li> </ul> </li> <li><a href="#Enumの仕様">Enumの仕様</a><ul> <li><a href="#cases">cases</a></li> <li><a href="#Pure-Enum-Backed-Enum">Pure Enum, Backed Enum</a></li> <li><a href="#from-tryFrom">from, tryFrom</a></li> <li><a href="#継承インターフェース">継承、インターフェース</a></li> <li><a href="#メソッドstaticメソッドの定義">メソッド、staticメソッドの定義</a></li> <li><a href="#トレイト">トレイト</a></li> </ul> </li> <li><a href="#感想">感想</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h1 id="概要説明">概要説明</h1> <p>こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(<a href="https://twitter.com/suzuki_cecil_">@suzuki_cecil_</a>)です。</p> <p>弊社が運用しているエキテンのバックエンドはほぼ<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>で実装しております。そんな<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>に関して、先日メジャーリリースされたバージョン8.1から<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>でも<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>が利用できるようになりました 🎉</p> <p>(私も含め、長い間<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>を待ち望んでいたPHPerの方々もいらっしゃることでしょう笑)</p> <p>なので今回の記事ではPHP8.1の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>に関して紹介したいと思います。</p> <h1 id="そもそもEnumとは何か">そもそも<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>とは何か?</h1> <p>PHP8.1の <a href="https://www.php.net/manual/ja/language.enumerations.overview.php">公式ドキュメント</a> では列挙型(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>)に関して以下のように説明されています。</p> <blockquote><p>列挙型(Enumerations) または <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a> を使うと、 開発者は取りうる値を限定した独自の型を定義できます。 これによって、"不正な状態を表現できなくなる" ので、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>モデルを定義する時に特に役立ちます。</p></blockquote> <p>例えば<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県モデルを実装したいとします。少なくとも2022年現在において日本には47の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県が存在するため、モデルでも47個の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県のうちいずれか1つの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県を持てるようにする、逆に言えば47個の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県以外を持てないようにする必要があります。</p> <p>それぞれの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県に1~47の整数値を割り振りint型で取り扱ったとして、int型では48以上の整数値や0, マイナスといった値も取り扱えるため「47個の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県以外を持てないようにする」という要件を満たすことはできません。</p> <p>そこで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>を活用すると開発者は取りうる値を限定した独自の型を定義することが可能なため、47個の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県のみをとりうる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県型を定義することが可能になります。</p> <h1 id="Enumの登場でどう変わるか"><a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>の登場でどう変わるか</h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>の登場でどう変わるかを知るためにクラス(擬似的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>)による実装と<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>による実装を比較してみたいと思います。</p> <h2 id="クラス擬似的なEnumによる実装">クラス(擬似的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>)による実装</h2> <p>今までは<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>で<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>を利用したいと思っても<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>には備わっていないため、 <a href="https://github.com/BenSampo/laravel-enum">laravel-enum</a> などの非公式のライブラリを利用したり自前で以下のようなクラスで擬似的に<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>を実装してきたのではないでしょうか。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">class</span> Prefecture <span class="synSpecial">{</span> <span class="synType">private</span> <span class="synStatement">const</span> HOKKAIDO <span class="synStatement">=</span> <span class="synConstant">1</span>; <span class="synType">private</span> <span class="synStatement">const</span> AOMORI <span class="synStatement">=</span> <span class="synConstant">2</span>; <span class="synType">private</span> <span class="synStatement">const</span> IWATE <span class="synStatement">=</span> <span class="synConstant">3</span>; <span class="synComment">// 省略</span> <span class="synType">private</span> <span class="synStatement">const</span> KAGOSHIMA <span class="synStatement">=</span> <span class="synConstant">46</span>; <span class="synType">private</span> <span class="synStatement">const</span> OKINAWA <span class="synStatement">=</span> <span class="synConstant">47</span>; <span class="synType">private</span> <span class="synStatement">const</span> NAMES <span class="synStatement">=</span> <span class="synSpecial">[</span> <span class="synType">self</span><span class="synStatement">::</span>HOKKAIDO <span class="synStatement">=&gt;</span> <span class="synConstant">&quot;北海道&quot;</span>, <span class="synType">self</span><span class="synStatement">::</span>AOMORI <span class="synStatement">=&gt;</span> <span class="synConstant">&quot;青森県&quot;</span>, <span class="synType">self</span><span class="synStatement">::</span>IWATE <span class="synStatement">=&gt;</span> <span class="synConstant">&quot;岩手県&quot;</span>, <span class="synComment">// 省略</span> <span class="synType">self</span><span class="synStatement">::</span>KAGOSHIMA <span class="synStatement">=&gt;</span> <span class="synConstant">&quot;鹿児島県&quot;</span>, <span class="synType">self</span><span class="synStatement">::</span>OKINAWA <span class="synStatement">=&gt;</span> <span class="synConstant">&quot;沖縄県&quot;</span>, <span class="synSpecial">]</span>; <span class="synType">private</span> <span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">value</span>; <span class="synType">private</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span><span class="synIdentifier">value</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">value</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synType">static</span> <span class="synPreProc">function</span> hokkaido<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">self</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synPreProc">new</span> <span class="synType">self</span><span class="synSpecial">(</span><span class="synType">self</span><span class="synStatement">::</span>HOKKAIDO<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synType">static</span> <span class="synPreProc">function</span> aomori<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">self</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synPreProc">new</span> <span class="synType">self</span><span class="synSpecial">(</span><span class="synType">self</span><span class="synStatement">::</span>AOMORI<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synType">static</span> <span class="synPreProc">function</span> iwate<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">self</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synPreProc">new</span> <span class="synType">self</span><span class="synSpecial">(</span><span class="synType">self</span><span class="synStatement">::</span>IWATE<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synComment">// 省略</span> <span class="synType">public</span> <span class="synType">static</span> <span class="synPreProc">function</span> kagoshima<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">self</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synPreProc">new</span> <span class="synType">self</span><span class="synSpecial">(</span><span class="synType">self</span><span class="synStatement">::</span>KAGOSHIMA<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synType">static</span> <span class="synPreProc">function</span> okinawa<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">self</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synPreProc">new</span> <span class="synType">self</span><span class="synSpecial">(</span><span class="synType">self</span><span class="synStatement">::</span>OKINAWA<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> name<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">string</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synType">self</span><span class="synStatement">::</span>NAMES<span class="synSpecial">[</span><span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span><span class="synIdentifier">value</span><span class="synSpecial">]</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synIdentifier">var_dump</span><span class="synSpecial">(</span>Prefecture<span class="synStatement">::</span>hokkaido<span class="synSpecial">()</span><span class="synType">-&gt;</span><span class="synIdentifier">name</span><span class="synSpecial">())</span>; </pre> <pre class="code" data-lang="" data-unlink>$ php Prefecture.php string(9) &#34;北海道&#34;</pre> <p>このクラスでも「47個の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%D4%C6%BB">都道</a>府県以外を持てないようにする」という要件を満たすことは可能です。しかし、いささかコード量が多いように思えます。</p> <h2 id="Enumによる実装"><a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>による実装</h2> <p>PHP8.1より<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>が利用できるようになったため上記とほぼ同義のモデルを以下のコードで表現することが可能になりました。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> enum Prefecture<span class="synStatement">:</span> <span class="synType">string</span> <span class="synSpecial">{</span> <span class="synStatement">case</span> HOKKAIDO <span class="synStatement">=</span> <span class="synConstant">&quot;北海道&quot;</span>; <span class="synStatement">case</span> AOMORI <span class="synStatement">=</span> <span class="synConstant">&quot;青森県&quot;</span>; <span class="synStatement">case</span> IWATE <span class="synStatement">=</span> <span class="synConstant">&quot;岩手県&quot;</span>; <span class="synComment">// 省略</span> <span class="synStatement">case</span> KAGOSHIMA <span class="synStatement">=</span> <span class="synConstant">&quot;鹿児島県&quot;</span>; <span class="synStatement">case</span> OKINAWA <span class="synStatement">=</span> <span class="synConstant">&quot;沖縄県&quot;</span>; <span class="synSpecial">}</span> <span class="synIdentifier">var_dump</span><span class="synSpecial">(</span>Prefecture<span class="synStatement">::</span>HOKKAIDO<span class="synType">-&gt;</span><span class="synIdentifier">name</span><span class="synSpecial">)</span>; <span class="synIdentifier">var_dump</span><span class="synSpecial">(</span>Prefecture<span class="synStatement">::</span>HOKKAIDO<span class="synType">-&gt;</span><span class="synIdentifier">value</span><span class="synSpecial">)</span>; </pre> <pre class="code" data-lang="" data-unlink>$ php Prefecture.php string(8) &#34;HOKKAIDO&#34; string(9) &#34;北海道&#34;</pre> <p>コード量がかなり少なく非常に可読性の高いモデルに出来たのではないでしょうか。</p> <h1 id="Enumの仕様"><a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>の仕様</h1> <p>前述の説明だけでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>の細かい仕様まで説明できていないので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>の仕様に関して言及したいと思います。</p> <h2 id="cases">cases</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>は内部的に<a href="https://www.php.net/manual/ja/class.unitenum.php">UnitEnumインターフェース</a>を実装しています。以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>はCore_c.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a>より引用したUnitEnumインターフェースです。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@since </span><span class="synComment">8.1</span> <span class="synComment"> */</span> <span class="synType">interface</span> UnitEnum <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">name</span>; <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">static[]</span> <span class="synComment"> */</span> <span class="synType">public</span> <span class="synType">static</span> <span class="synPreProc">function</span> cases<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">array</span>; <span class="synSpecial">}</span> </pre> <p>UnitEnumインターフェースではcasesメソッドが定義されていることがわかります。</p> <p>casesメソッドは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>に定義されている全てのcaseを要素に持つ配列を返すメソッドです。配列の要素は宣言された順に含まれます。</p> <h2 id="Pure-Enum-Backed-Enum">Pure <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>, Backed <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a></h2> <p>デフォルトの<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>では<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AB%A5%E9%A1%BC">スカラー</a>値の情報を持っていません。しかし先ほどのPrefectureのようにそれぞれのcaseごとに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AB%A5%E9%A1%BC">スカラー</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%B9%A5%AB%A5%E9%A1%BC">スカラー</a>値の情報を持たない<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>を <strong>Pure <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a></strong> 、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AB%A5%E9%A1%BC">スカラー</a>値の情報を持つ<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>を <strong>Backed <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a></strong> といいます。またPure <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>のcaseを <strong>Pure Case</strong>、Backed <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>のcaseを<strong>Backed Case</strong>といいます。Pure <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>にはPure Caseのみ、Backed <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>にはBacked <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>のみを含めるため1つの<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>がPure CaseおよびBacked Caseを持つということは不可能です。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> enum Pure <span class="synSpecial">{</span> <span class="synStatement">case</span> A; <span class="synStatement">case</span> B; <span class="synStatement">case</span> C; <span class="synSpecial">}</span> enum Backed<span class="synStatement">:</span> <span class="synType">int</span> <span class="synSpecial">{</span> <span class="synStatement">case</span> A <span class="synStatement">=</span> <span class="synConstant">1</span>; <span class="synStatement">case</span> B <span class="synStatement">=</span> <span class="synConstant">2</span>; <span class="synStatement">case</span> C <span class="synStatement">=</span> <span class="synConstant">3</span>; <span class="synSpecial">}</span> <span class="synIdentifier">var_dump</span><span class="synSpecial">(</span>Pure<span class="synStatement">::</span>A<span class="synSpecial">)</span>; <span class="synIdentifier">var_dump</span><span class="synSpecial">(</span>Backed<span class="synStatement">::</span>A<span class="synSpecial">)</span>; </pre> <pre class="code" data-lang="" data-unlink>$ php PureAndBacked.php enum(Pure::A) enum(Backed::A)</pre> <p>Backed <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>が持てる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AB%A5%E9%A1%BC">スカラー</a>値にはいくつかの制約がつきます。</p> <ul> <li>intまたはstringの値のみを持つことができる(bool, floatは不可)</li> <li>単一の型のみを持つことができる(Union型は不可)</li> <li>Backed Caseの値は全てユニークでなければならない</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AB%A5%E9%A1%BC">スカラー</a>値は<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%EA%A5%C6%A5%E9%A5%EB">リテラル</a>を表す式でなければならない(定数はサポートしていない)</li> </ul> <h2 id="from-tryFrom">from, tryFrom</h2> <p>Backed <a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>は内部的にUnitEnumを継承した<a href="https://www.php.net/manual/ja/class.backedenum.php">BackedEnumインターフェース</a>を実装しています。以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>はCore_c.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a>より引用したBackedEnumインターフェースです。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@since </span><span class="synComment">8.1</span> <span class="synComment"> */</span> <span class="synType">interface</span> BackedEnum <span class="synType">extends</span> UnitEnum <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span>; <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@param </span><span class="synComment">int|string $value</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">static</span> <span class="synComment"> */</span> <span class="synType">public</span> <span class="synType">static</span> <span class="synPreProc">function</span> from<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> <span class="synType">static</span>; <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@param </span><span class="synComment">int|string $value</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">static|null</span> <span class="synComment"> */</span> <span class="synType">public</span> <span class="synType">static</span> <span class="synPreProc">function</span> tryFrom<span class="synSpecial">(</span><span class="synType">int</span><span class="synStatement">|</span><span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">value</span><span class="synSpecial">)</span><span class="synStatement">:</span> <span class="synStatement">?</span><span class="synType">static</span>; <span class="synSpecial">}</span> </pre> <p>BackedEnumインターフェースではfromメソッドとtryFromメソッドが定義されていることがわかります。</p> <p>fromメソッド、tryFromメソッドともに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AB%A5%E9%A1%BC">スカラー</a>値からBacked Caseを返すメソッドですが、対応するBacked Caseがない場合fromメソッドはValueErrorがスローされるのに対し、tryFromメソッドはNullを返します。</p> <h2 id="継承インターフェース">継承、インターフェース</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>はインターフェースを実装することが可能ですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>およびClassを継承することはできません。</p> <h2 id="メソッドstaticメソッドの定義">メソッド、staticメソッドの定義</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>はClass同様にメソッド、 staticメソッドを定義することが可能です。メソッドのスコープもClass同様にpublic、protected、privateから選択することが可能です。ただし前述の通り<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>を継承することはできないため、protectedとprivateはほぼ同義です。</p> <h2 id="トレイト">トレイト</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>はClass同様にトレイトをuseすることが可能です。useされるトレイトにはメソッドとstaticメソッドだけを含めることが可能です(プロパティを持つトレイトをuseするとFatal Errorが発生します)</p> <h1 id="感想">感想</h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>で実装可能なことはClassでも実装可能であるため特に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>モデルの実装の幅が広がるということはありません。</p> <p>ただしコード量をかなり抑えることができ可読性も向上することから現在携わっているリニューアルプロジェクトにもどんどん導入していきたいと思いました。</p> <h1 id="おわりに">おわりに</h1> <p><strong>仲間を募集しております</strong></p> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集 採用・求人情報 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> doj_suzuki_cecil 【Laravel】頻出Eloquent逆引きリファレンス hatenablog://entry/13574176438040779941 2021-12-09T18:20:32+09:00 2021-12-09T18:20:32+09:00 概要説明 テーブル モデル A AND (B OR C) GROUP BYごとにCOUNTする 関連テーブルと結合するレコードを取得 おわりに 概要説明 こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(@suzuki_cecil_)です。 リニューアルプロジェクト通して溜まったLaravelのEloquentのナレッジを活用して逆引きリファレンスにしてみようかと思います。皆様の実装の助けになれば幸いです。 テーブル 今回は店舗テーブルとカテゴリテーブルおよび中間テーブルを例に出して解説をしていきます。テーブル設計、サン… <ul class="table-of-contents"> <li><a href="#概要説明">概要説明</a><ul> <li><a href="#テーブル">テーブル</a></li> <li><a href="#モデル">モデル</a></li> </ul> </li> <li><a href="#A-AND-B-OR-C">A AND (B OR C)</a></li> <li><a href="#GROUP-BYごとにCOUNTする">GROUP BYごとにCOUNTする</a></li> <li><a href="#関連テーブルと結合するレコードを取得">関連テーブルと結合するレコードを取得</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h1 id="概要説明">概要説明</h1> <p>こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(<a href="https://twitter.com/suzuki_cecil_">@suzuki_cecil_</a>)です。</p> <p>リニューアルプロジェクト通して溜まった<a href="https://readouble.com/laravel/8.x/ja/eloquent.html">LaravelのEloquent</a>のナレッジを活用して逆引きリファレンスにしてみようかと思います。皆様の実装の助けになれば幸いです。</p> <h2 id="テーブル">テーブル</h2> <p>今回は店舗テーブルとカテゴリテーブルおよび中間テーブルを例に出して解説をしていきます。テーブル設計、サンプルデータおよびEloquentモデルは以下の通りです。</p> <p><strong>ER図</strong></p> <p><figure class="figure-image figure-image-fotolife" title="ER図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20211208/20211208190559.png" alt="f:id:doj_suzuki_cecil:20211208190559p:plain" width="547" height="100" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ER図</figcaption></figure></p> <p><strong>shop(店舗テーブル)</strong></p> <table> <thead> <tr> <th> shop_id </th> <th> name </th> <th> prefecture_id </th> <th> review_count </th> <th> photo_count </th> </tr> </thead> <tbody> <tr> <td> 1 </td> <td> エキテンマッサージ </td> <td> 1 </td> <td> 100 </td> <td> 90 </td> </tr> <tr> <td> 2 </td> <td> エキテン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B0%B9%FC%B1%A1">整骨院</a> </td> <td> 1 </td> <td> 90 </td> <td> 90 </td> </tr> <tr> <td> 3 </td> <td> エキテン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%DC%B9%FC%B1%A1">接骨院</a> </td> <td> 2 </td> <td> 90 </td> <td> 100 </td> </tr> </tbody> </table> <p><strong>category(カテゴリテーブル)</strong></p> <table> <thead> <tr> <th> category_id </th> <th> name </th> </tr> </thead> <tbody> <tr> <td> 1 </td> <td> マッサージ </td> </tr> <tr> <td> 2 </td> <td> 整体 </td> </tr> <tr> <td> 3 </td> <td> 整骨 </td> </tr> <tr> <td> 4 </td> <td> 接骨 </td> </tr> </tbody> </table> <p><strong>shop_category(中間テーブル)</strong></p> <table> <thead> <tr> <th> id </th> <th> shop_id </th> <th> category_id </th> </tr> </thead> <tbody> <tr> <td> 1 </td> <td> 1 </td> <td> 1 </td> </tr> <tr> <td> 2 </td> <td> 2 </td> <td> 1 </td> </tr> <tr> <td> 3 </td> <td> 2 </td> <td> 2 </td> </tr> <tr> <td> 4 </td> <td> 2 </td> <td> 3 </td> </tr> <tr> <td> 5 </td> <td> 3 </td> <td> 1 </td> </tr> <tr> <td> 6 </td> <td> 3 </td> <td> 2 </td> </tr> <tr> <td> 7 </td> <td> 3 </td> <td> 4 </td> </tr> </tbody> </table> <p><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/DDL">DDL</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/DML">DML</a></strong></p> <p><details><summary><a class="keyword" href="http://d.hatena.ne.jp/keyword/DDL">DDL</a>/<a class="keyword" href="http://d.hatena.ne.jp/keyword/DML">DML</a></summary><div></p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">CREATE</span> <span class="synSpecial">TABLE</span> `shop` ( `shop_id` bigint(<span class="synConstant">20</span>) unsigned <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span> AUTO_INCREMENT, `name` <span class="synType">varchar</span>(<span class="synConstant">50</span>) COLLATE utf8mb4_bin <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, `prefecture_id` int(<span class="synConstant">11</span>) <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, `review_count` int(<span class="synConstant">11</span>) <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, `photo_count` int(<span class="synConstant">11</span>) <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, PRIMARY KEY (`shop_id`) <span class="synSpecial">USING</span> BTREE ) ENGINE=InnoDB <span class="synSpecial">DEFAULT</span> CHARSET=utf8mb4 COLLATE=utf8mb4_bin; <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop` <span class="synSpecial">VALUES</span> (<span class="synConstant">1</span>, <span class="synSpecial">'</span><span class="synConstant">エキテンマッサージ</span><span class="synSpecial">'</span>, <span class="synConstant">1</span>, <span class="synConstant">100</span>, <span class="synConstant">90</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop` <span class="synSpecial">VALUES</span> (<span class="synConstant">2</span>, <span class="synSpecial">'</span><span class="synConstant">エキテン整骨院</span><span class="synSpecial">'</span>, <span class="synConstant">1</span>, <span class="synConstant">90</span>, <span class="synConstant">90</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop` <span class="synSpecial">VALUES</span> (<span class="synConstant">3</span>, <span class="synSpecial">'</span><span class="synConstant">エキテン接骨院</span><span class="synSpecial">'</span>, <span class="synConstant">2</span>, <span class="synConstant">90</span>, <span class="synConstant">100</span>); <span class="synStatement">CREATE</span> <span class="synSpecial">TABLE</span> `category` ( `category_id` bigint(<span class="synConstant">20</span>) unsigned <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span> AUTO_INCREMENT, `name` <span class="synType">varchar</span>(<span class="synConstant">50</span>) COLLATE utf8mb4_bin <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, PRIMARY KEY (`category_id`) <span class="synSpecial">USING</span> BTREE ) ENGINE=InnoDB <span class="synSpecial">DEFAULT</span> CHARSET=utf8mb4 COLLATE=utf8mb4_bin; <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `category` <span class="synSpecial">VALUES</span> (<span class="synConstant">1</span>, <span class="synSpecial">'</span><span class="synConstant">マッサージ</span><span class="synSpecial">'</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `category` <span class="synSpecial">VALUES</span> (<span class="synConstant">2</span>, <span class="synSpecial">'</span><span class="synConstant">整体</span><span class="synSpecial">'</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `category` <span class="synSpecial">VALUES</span> (<span class="synConstant">3</span>, <span class="synSpecial">'</span><span class="synConstant">整骨</span><span class="synSpecial">'</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `category` <span class="synSpecial">VALUES</span> (<span class="synConstant">4</span>, <span class="synSpecial">'</span><span class="synConstant">接骨</span><span class="synSpecial">'</span>); <span class="synStatement">CREATE</span> <span class="synSpecial">TABLE</span> `shop_category` ( `id` bigint(<span class="synConstant">20</span>) unsigned <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span> AUTO_INCREMENT, `shop_id` int(<span class="synConstant">11</span>) <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, `category_id` int(<span class="synConstant">11</span>) <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, PRIMARY KEY (`id`) <span class="synSpecial">USING</span> BTREE ) ENGINE=InnoDB <span class="synSpecial">DEFAULT</span> CHARSET=utf8mb4 COLLATE=utf8mb4_bin; <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop_category` <span class="synSpecial">VALUES</span> (<span class="synConstant">1</span>, <span class="synConstant">1</span>, <span class="synConstant">1</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop_category` <span class="synSpecial">VALUES</span> (<span class="synConstant">2</span>, <span class="synConstant">2</span>, <span class="synConstant">1</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop_category` <span class="synSpecial">VALUES</span> (<span class="synConstant">3</span>, <span class="synConstant">2</span>, <span class="synConstant">2</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop_category` <span class="synSpecial">VALUES</span> (<span class="synConstant">4</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop_category` <span class="synSpecial">VALUES</span> (<span class="synConstant">5</span>, <span class="synConstant">3</span>, <span class="synConstant">1</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop_category` <span class="synSpecial">VALUES</span> (<span class="synConstant">6</span>, <span class="synConstant">3</span>, <span class="synConstant">2</span>); <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `shop_category` <span class="synSpecial">VALUES</span> (<span class="synConstant">7</span>, <span class="synConstant">3</span>, <span class="synConstant">4</span>); </pre> <p></div></details></p> <h2 id="モデル">モデル</h2> <p><strong>Shop</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\Adapter\Gateway\Dao\Eloquent\Model; <span class="synPreProc">use</span> Illuminate\Database\Eloquent\Model; <span class="synType">class</span> Shop <span class="synType">extends</span> Model <span class="synSpecial">{</span> <span class="synType">protected</span> <span class="synStatement">$</span><span class="synIdentifier">table</span> <span class="synStatement">=</span> <span class="synConstant">'shop'</span>; <span class="synType">protected</span> <span class="synStatement">$</span><span class="synIdentifier">primaryKey</span> <span class="synStatement">=</span> <span class="synConstant">'shop_id'</span>; <span class="synSpecial">}</span> </pre> <p><strong>Category</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\Adapter\Gateway\Dao\Eloquent\Model; <span class="synPreProc">use</span> Illuminate\Database\Eloquent\Model; <span class="synType">class</span> Category <span class="synType">extends</span> Model <span class="synSpecial">{</span> <span class="synType">protected</span> <span class="synStatement">$</span><span class="synIdentifier">table</span> <span class="synStatement">=</span> <span class="synConstant">'category'</span>; <span class="synType">protected</span> <span class="synStatement">$</span><span class="synIdentifier">primaryKey</span> <span class="synStatement">=</span> <span class="synConstant">'category_id'</span>; <span class="synSpecial">}</span> </pre> <p><strong>ShopCategory</strong></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\Adapter\Gateway\Dao\Eloquent\Model; <span class="synPreProc">use</span> Illuminate\Database\Eloquent\Model; <span class="synType">class</span> ShopCategory <span class="synType">extends</span> Model <span class="synSpecial">{</span> <span class="synType">protected</span> <span class="synStatement">$</span><span class="synIdentifier">table</span> <span class="synStatement">=</span> <span class="synConstant">'shop_category'</span>; <span class="synType">protected</span> <span class="synStatement">$</span><span class="synIdentifier">primaryKey</span> <span class="synStatement">=</span> <span class="synConstant">'id'</span>; <span class="synSpecial">}</span> </pre> <h1 id="A-AND-B-OR-C">A AND (B OR C)</h1> <p>shopテーブルから <code>prefecture_id</code> が 1かつ、 <code>review_count</code> もしくは <code>photo_count</code> が100以上のレコードを取得するとします(該当するレコードは<code>shop_id = 1</code> のレコード)。</p> <p>単純に考えると以下のような考えに至るかと思います。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> Shop<span class="synStatement">::</span>query<span class="synSpecial">()</span> <span class="synType">-&gt;</span>where<span class="synSpecial">(</span><span class="synConstant">&quot;prefecture_id&quot;</span>, <span class="synConstant">&quot;=&quot;</span>, <span class="synConstant">1</span><span class="synSpecial">)</span> <span class="synType">-&gt;</span>where<span class="synSpecial">(</span><span class="synConstant">&quot;review_count&quot;</span>, <span class="synConstant">&quot;&gt;=&quot;</span>, <span class="synConstant">100</span><span class="synSpecial">)</span> <span class="synType">-&gt;</span>orWhere<span class="synSpecial">(</span><span class="synConstant">&quot;photo_count&quot;</span>, <span class="synConstant">&quot;&gt;=&quot;</span>, <span class="synConstant">100</span><span class="synSpecial">)</span> <span class="synType">-&gt;</span>get<span class="synSpecial">()</span>; </pre> <p>しかし、これだと <code>shop_id = 1</code> のレコードと、 <code>shop_id = 3</code> のレコードが取得されてしまいます。上記の実装のWhere句を生の<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>で書くと</p> <p> <code>prefecture_id = 1 AND review_count &gt;= 100 OR photo_count &gt;= 100</code></p> <p>となり、評価の順番が <code>prefecture_id = 1</code>, <code>AND review_count &gt;= 100</code>, <code>OR photo_count &gt;= 100</code> となるため <code>prefecture_id</code> の値に関わらず <code>photo_count &gt;= 100</code> のレコードが全て取得されてしまうためです。</p> <p>A AND (B OR C)のように明示的に評価の順番を指定するためには以下のように<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%ED%A1%BC%A5%B8%A5%E3">クロージャ</a>で定義する必要があります。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> Shop<span class="synStatement">::</span>query<span class="synSpecial">()</span> <span class="synType">-&gt;</span>where<span class="synSpecial">(</span><span class="synConstant">&quot;prefecture_id&quot;</span>, <span class="synConstant">&quot;=&quot;</span>, <span class="synConstant">1</span><span class="synSpecial">)</span> <span class="synType">-&gt;</span>where<span class="synSpecial">(</span><span class="synPreProc">function</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">query</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">query</span><span class="synType">-&gt;</span>where<span class="synSpecial">(</span><span class="synConstant">&quot;review_count&quot;</span>, <span class="synConstant">&quot;&gt;=&quot;</span>, <span class="synConstant">100</span><span class="synSpecial">)</span> <span class="synType">-&gt;</span>orWhere<span class="synSpecial">(</span><span class="synConstant">&quot;photo_count&quot;</span>, <span class="synConstant">&quot;&gt;=&quot;</span>, <span class="synConstant">100</span><span class="synSpecial">)</span>; <span class="synSpecial">})</span> <span class="synType">-&gt;</span>get<span class="synSpecial">()</span>; </pre> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%ED%A1%BC%A5%B8%A5%E3">クロージャ</a>で定義した場合は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%ED%A1%BC%A5%B8%A5%E3">クロージャ</a>内が先に評価されます。そのため <code>review_count &gt;= 100</code>, <code>OR photo_count &gt;= 100</code> が先に評価され、その後に <code>AND prefecture_id = 1</code> が評価されることとなり、無事 <code>shop_id = 1</code> のレコードのみが取得されます。</p> <h1 id="GROUP-BYごとにCOUNTする">GROUP BYごとにCOUNTする</h1> <p>shop_categoryテーブルから <code>category_id</code> ごとのレコード数(店舗数)を取得するとします。</p> <p>直感的には以下のように <code>groupBy</code> メソッドと <code>count</code> メソッドを利用すれば取得できそうです。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> ShopCategory<span class="synStatement">::</span>query<span class="synSpecial">()</span> <span class="synType">-&gt;</span>groupBy<span class="synSpecial">(</span><span class="synConstant">&quot;category_id&quot;</span><span class="synSpecial">)</span> <span class="synType">-&gt;</span><span class="synIdentifier">count</span><span class="synSpecial">()</span>; </pre> <p>しかし、これでは <code>3</code> が取得されます。上記の記述ではshopテーブルを <code>category_id</code> でGROUP BYした結果の1行目の値のみが取得されてしまうためです(今回の場合では <code>category_id = 1</code>のレコード数)。</p> <p>shop_categoryテーブルから <code>category_id</code> ごとのレコード数を取得するためには以下のように <code>DB::raw</code> を利用して生の<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>を併用する必要があります。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> ShopCategory<span class="synStatement">::</span>query<span class="synSpecial">()</span> <span class="synType">-&gt;</span>select<span class="synSpecial">(</span>DB<span class="synStatement">::</span>raw<span class="synSpecial">(</span><span class="synConstant">'category_id, COUNT(*) AS count'</span><span class="synSpecial">))</span> <span class="synType">-&gt;</span>groupBy<span class="synSpecial">(</span><span class="synConstant">&quot;category_id&quot;</span><span class="synSpecial">)</span> <span class="synType">-&gt;</span>get<span class="synSpecial">()</span>; </pre> <p>上記の記述によりGROUP BYごとにCOUNTすることが可能となります。</p> <h1 id="関連テーブルと結合するレコードを取得">関連テーブルと結合するレコードを取得</h1> <p><code>category_id = 2</code> の shop_category のレコードと <code>shop_id</code> で結合する shop テーブルのレコードを取得するとします。</p> <p>まずは <code>Shop</code> モデルに <code>ShopCategory</code> モデルとの <a href="https://readouble.com/laravel/8.x/ja/eloquent-relationships.html">リレーション</a> である <code>category</code> メソッドを定義します。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\Adapter\Gateway\Dao\Eloquent\Model; <span class="synPreProc">use</span> Illuminate\Database\Eloquent\Model; <span class="synType">class</span> Shop <span class="synType">extends</span> Model <span class="synSpecial">{</span> <span class="synType">protected</span> <span class="synStatement">$</span><span class="synIdentifier">table</span> <span class="synStatement">=</span> <span class="synConstant">'shop'</span>; <span class="synType">protected</span> <span class="synStatement">$</span><span class="synIdentifier">primaryKey</span> <span class="synStatement">=</span> <span class="synConstant">'shop_id'</span>; <span class="synType">public</span> <span class="synPreProc">function</span> category<span class="synSpecial">()</span><span class="synStatement">:</span> HasMany <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>hasMany<span class="synSpecial">(</span>ShopCategory<span class="synStatement">::</span><span class="synType">class</span>, <span class="synConstant">'shop_id'</span>, <span class="synConstant">'shop_id'</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>リレーションを定義した上でクエリは以下のように記述します。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> Shop<span class="synStatement">::</span>query<span class="synSpecial">()</span> <span class="synType">-&gt;</span>whereHas<span class="synSpecial">(</span><span class="synConstant">&quot;category&quot;</span>, <span class="synPreProc">function</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">query</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">query</span><span class="synType">-&gt;</span>where<span class="synSpecial">(</span><span class="synConstant">&quot;category_id&quot;</span>, <span class="synConstant">&quot;=&quot;</span>, <span class="synConstant">2</span><span class="synSpecial">)</span>; <span class="synSpecial">})</span> <span class="synType">-&gt;</span>get<span class="synSpecial">()</span>; </pre> <p><code>whereHas</code> メソッドは第一引数に指定した名前のリレーションを持つことを条件とします。さらに第二引数に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%ED%A1%BC%A5%B8%A5%E3">クロージャ</a>を定義することができ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%ED%A1%BC%A5%B8%A5%E3">クロージャ</a>内でリレーションに条件を追加することができます。今回の場合は <code>shop.shop_id = shop_category.shop_id</code> の条件に <code>AND shop_category.category_id = 2</code> を追加しています。</p> <p>これにより関連テーブルと結合するレコードを取得が可能となります。</p> <h1 id="おわりに">おわりに</h1> <p><strong>仲間を募集しております</strong></p> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集 採用・求人情報 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> doj_suzuki_cecil 社内の Slack の絵文字の使用頻度を集計してみた hatenablog://entry/13574176438037744481 2021-11-30T10:26:45+09:00 2021-11-30T17:04:47+09:00 はじめに こんにちは!株式会社デザインワン・ジャパンでエキテンの開発を担当しているサービス開発部の寺井です。 弊社は、社内チャットツールとして Slack を採用しています。IT系の人なら馴染み深いものかもしれないですが、発言に対して絵文字でリアクションすることができます。 上記画像のように、デフォルトで用意されている以外にもカスタムで作成し使用することができ、リアクションとして "私も" と "いい判断" という絵文字が付けられています(発言内にも "まじ参加" というカスタム絵文字が使われています)。 カスタム絵文字が執筆時点で 914 個も作成されていて、一体どの絵文字が使われているのか… <h3>はじめに</h3> <p style="box-sizing: border-box; margin: 0px 0px 0.7em; color: #293030; font-family: helvetica, 'Segoe UI', 游ゴシック体, YuGothic, '游ゴシック Medium', 'Yu Gothic Medium', 游ゴシック, 'Yu Gothic', メイリオ, Meiryo, sans-serif; font-size: 17px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">こんにちは!株式会社デザインワン・ジャパンでエキテンの開発を担当しているサービス開発部の寺井です。</p> <p>弊社は、社内チャットツールとして Slack を採用しています。IT系の人なら馴染み深いものかもしれないですが、発言に対して絵文字でリアクションすることができます。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211130/20211130150227.png" alt="f:id:doj_rterai:20211130150227p:plain" width="384" height="174" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>上記画像のように、デフォルトで用意されている以外にもカスタムで作成し使用することができ、リアクションとして "私も" と "いい判断" という絵文字が付けられています(発言内にも "まじ参加" というカスタム絵文字が使われています)。<br /><br /></p> <p>カスタム絵文字が執筆時点で 914 個も作成されていて、一体どの絵文字が使われているのか気になったので集計してみることにしました。 </p> <h3>データを貯める</h3> <p>まず最初にやることはデータを貯めることです。</p> <h4>Event Subscription の設定</h4> <p>Slack には Event Subcription という機能があります。直訳するとイベントの購読、つまり何かしらのアクションがあった際、そのイベントの通知をサーバに送ることができます。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129162326.png" alt="f:id:doj_rterai:20211129162326p:plain" width="1200" height="662" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>Event Subscriptions を On にし、通知を受け取りたい Web サーバの URL を設定します。下のグレー色の文字で書かれている通り、イベントは HTTP POST で通知されます。</p> <h4>サーバの準備</h4> <p>順番は前後しましたが、イベントを購読する側の Web サーバを立てます。今回は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%EC%A5%C3%A5%C9%A5%B7%A1%BC%A5%C8">スプレッドシート</a>に Web アプリケーションとして公開できる機能があるため、それを使います。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129162307.png" alt="f:id:doj_rterai:20211129162307p:plain" width="888" height="946" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>Current web app URL にある URL を先ほど Slack 側の Request URL に設定します(version 37 まで修正していたのか)。</p> <h4>疎通させる</h4> <p>実は上記の設定だけではイベントを購読することはできません。注意深い人は気付いているかもしれないですが、先ほどの画像のところに、 URL を設定後にその URL 宛に challenge parameter を送るからそのまま送り返せ、と書かれてているのです。これは初回だけ行えば大丈夫です。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129163843.png" alt="f:id:doj_rterai:20211129163843p:plain" width="1200" height="662" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>チャレンジが成功するとめでたく疎通が取れたことになります!このままだと、誰でも使える Web サーバなので、イベント購読の際に同時に送信される token を見て不特定多数の人に対しては使えないようにします(重要)。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129164413.png" alt="f:id:doj_rterai:20211129164413p:plain" width="1200" height="134" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>あとはコードをちょちょいと書いて完了です。JS は専門外なので動けばいいや精神です(訳: まさかりは投げないでください)。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129165034.png" alt="f:id:doj_rterai:20211129165034p:plain" width="1200" height="379" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h3>データを集計</h3> <p>データが溜まるのを待ちます。と言いつつも動きがあるので一時間ほど放置しているだけでもある程度データが貯まっていました。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129165644.png" alt="f:id:doj_rterai:20211129165644p:plain" width="653" height="108" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p> </p> <h4>Tableau で可視化</h4> <p>どのように可視化するかですが、弊社では Tableau というBI ツールを導入しており、様々なデータソースに対して可視化ができます。今回使っている<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%EC%A5%C3%A5%C9%A5%B7%A1%BC%A5%C8">スプレッドシート</a>にも対応しています。</p> <p>ちょこっと簡単に可視化してみました。グラフは、今週リアクションをつけた回数を人別に累計で表示し、トップ10だけのデータを表示させました(画像は月曜日取得したものなので、実質一日ですが...)。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129170104.png" alt="f:id:doj_rterai:20211129170104p:plain" width="1200" height="499" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>そして本題である、どの絵文字が一番使われているかです(集計期間は2021年10月の一ヶ月)。気になる一位は...?</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129170923.png" alt="f:id:doj_rterai:20211129170923p:plain" width="1037" height="280" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129171340.png" alt="f:id:doj_rterai:20211129171340p:plain" width="718" height="78" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>一番左の "ありがとうございます" が1位、次に "かしこまりました" が2位でした!その2つが2トップという結果になりました。そして6位の eyes 以外はカスタム絵文字であることがわかりました。<br /><br /></p> <p>次に気になったのが、 "ありがとうございます" を押された回数が多い人は誰だろうかと集計してみました。私は4位でした。そこそこ感謝されているようでよかったです。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129171907.png" alt="f:id:doj_rterai:20211129171907p:plain" width="1018" height="436" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>色々データを可視化して見て面白いなぁと感じたのは、時間別で押された回数をみると、昼休みである12時台が凹んでいてちゃんと可視化されているなぁと、感心しました。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_rterai/20211129/20211129174134.png" alt="f:id:doj_rterai:20211129174134p:plain" width="981" height="457" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h3>仲間を募集しております</h3> <p style="box-sizing: border-box; margin: 0px 0px 0.7em; color: #293030; font-family: helvetica, 'Segoe UI', 游ゴシック体, YuGothic, '游ゴシック Medium', 'Yu Gothic Medium', 游ゴシック, 'Yu Gothic', メイリオ, Meiryo, sans-serif; font-size: 17px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">募集中の職種については以下を御覧ください。</p> <h2 class="entry-title" style="overflow-wrap: break-word; word-break: break-all; font-size: 17px; margin: 0px 0px 2px; line-height: 1.3; max-height: 47px; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; color: #000000; font-family: -apple-system, 'system-ui', 'Segoe UI', Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><a href="https://www.wantedly.com/companies/designone/projects" target="_blank" style="overflow-wrap: break-word; word-break: break-all; outline: 0px; color: #3d4245; text-decoration: underline;" rel="noopener">株式会社デザインワン・ジャパンの募集 採用・求人情報 - Wantedly</a></h2> <p> </p> doj_rterai 【Laravel】Blade Componentsとサブビューの対比 hatenablog://entry/13574176438033588345 2021-11-24T17:05:52+09:00 2021-11-24T17:05:52+09:00 概要説明 Blade Componentsとは Class Based Components Anonymous Components ソースコード例 共通ファイル サブビューによる実装 Blade Componentsによる実装 Blade Componentsの所感 仲間を募集しております 参考URL 概要説明 こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(@suzuki_cecil_)です。 リニューアルプロジェクトではLaravelを採用しており、ビューはBlade Componentsを活用して実装していま… <ul class="table-of-contents"> <li><a href="#概要説明">概要説明</a></li> <li><a href="#Blade-Componentsとは">Blade Componentsとは</a><ul> <li><a href="#Class-Based-Components">Class Based Components</a></li> <li><a href="#Anonymous-Components">Anonymous Components</a></li> </ul> </li> <li><a href="#ソースコード例">ソースコード例</a><ul> <li><a href="#共通ファイル">共通ファイル</a></li> <li><a href="#サブビューによる実装">サブビューによる実装</a></li> <li><a href="#Blade-Componentsによる実装">Blade Componentsによる実装</a></li> </ul> </li> <li><a href="#Blade-Componentsの所感">Blade Componentsの所感</a></li> <li><a href="#仲間を募集しております">仲間を募集しております</a></li> <li><a href="#参考URL">参考URL</a></li> </ul> <h1 id="概要説明">概要説明</h1> <p>こんにちは!株式会社デザインワン・ジャパンで口コミサービス エキテンのリニューアルを担当しているサービス開発部の鈴木セシル(<a href="https://twitter.com/suzuki_cecil_">@suzuki_cecil_</a>)です。</p> <p>リニューアルプロジェクトではLaravelを採用しており、ビューはBlade Componentsを活用して実装しています。今回の記事ではリニューアルプロジェクトを通して体感したBlade ComponentsとBlade Templatesのサブビューと対比をまとめて記事にしたいと思います。</p> <p>注)今回はサブビューとの対比を目的としているため、slotとしてのBlade Componentsの利用に関する説明は省略させていただいております。</p> <h1 id="Blade-Componentsとは">Blade Componentsとは</h1> <p>Blade ComponentsはLaravel 7から登場したビューの構成要素で、その名の通り<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>の実装に特化したBladeファイルです。従来のBlade Templatesをサブビューとして扱うのとBlade Componentsの違いは以下の通りです。</p> <ul> <li><p>サブビュー:他のBlade TemplatesをBlade Templatesの一部として取り込みます。取り込まれる側のBladeもBlade Templatesであるため、ControllerからそのBladeで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>することも可能です。</p></li> <li><p>Blade Components:Blade ComponentsをBlade Templatesの一部として取り込みます。Bladeファイルは resources/views/components <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに集約され、またControllerからBlade Componentsで<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></li> </ul> <p>Blade ComponentsはClass Based ComponentsとAnonymous Componentsの2種類の実装方法があります。</p> <h2 id="Class-Based-Components">Class Based Components</h2> <p>Class Based ComponentsはBladeファイルおよびクラスファイル(以下<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>クラス)から構成されます。Bladeファイルからは<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>クラスのプロパティやメソッドを参照することが可能なため、Bladeファイルで特定のデータを扱いたい場合に利用することを想定しています。</p> <h2 id="Anonymous-Components">Anonymous Components</h2> <p>Anonymous ComponentsはClass Based Componentsとは異なりBladeファイルのみから構成されます。Bladeファイルはpropsにより呼び出し元のBlade TemplatesやBlade Componentsから受け取るデータを定義します。Bladeファイルで汎用的なデータを扱いたい場合に利用することを想定しています。</p> <h1 id="ソースコード例"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>例</h1> <p><a href="https://www.ekiten.jp/cat_seitai/tokyo/shinjukuku/">エキテン</a>を模倣した以下のページをサブビューによる実装、Blade Componentsによる実装ごとに<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><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20211118/20211118192434.png" alt="f:id:doj_suzuki_cecil:20211118192434p:plain" width="1102" height="812" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="共通ファイル">共通ファイル</h2> <p>以下に列挙する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>はサブビューによる実装、Blade Componentsによる実装それぞれで使用される共通のファイルとなります。</p> <p><details><summary>app/Http/Controllers/SamplePageController.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></summary></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\Http\Controllers; <span class="synPreProc">use</span> App\View\Model\Shop; <span class="synType">class</span> SamplePageController <span class="synType">extends</span> Controller <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synPreProc">function</span> index<span class="synSpecial">()</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">shops</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>initShops<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">nearlyShops</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>initNearlyShops<span class="synSpecial">()</span>; <span class="synStatement">return</span> view<span class="synSpecial">(</span><span class="synConstant">'sample_page'</span>, <span class="synSpecial">[</span><span class="synConstant">&quot;shops&quot;</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">shops</span>, <span class="synConstant">&quot;nearlyShops&quot;</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">nearlyShops</span><span class="synSpecial">])</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> initShops<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">array</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synSpecial">[</span> <span class="synPreProc">new</span> Shop<span class="synSpecial">(</span><span class="synConstant">1</span>, <span class="synConstant">&quot;エキテン整骨院&quot;</span>, <span class="synConstant">&quot;1番目の店舗です&quot;</span>, <span class="synSpecial">[</span><span class="synConstant">&quot;接骨・整骨&quot;</span>, <span class="synConstant">&quot;整体&quot;</span><span class="synSpecial">])</span>, <span class="synPreProc">new</span> Shop<span class="synSpecial">(</span><span class="synConstant">2</span>, <span class="synConstant">&quot;エキテン接骨院&quot;</span>, <span class="synConstant">&quot;2番目の店舗です&quot;</span>, <span class="synSpecial">[</span><span class="synConstant">&quot;接骨・整骨&quot;</span>, <span class="synConstant">&quot;整体&quot;</span><span class="synSpecial">])</span>, <span class="synPreProc">new</span> Shop<span class="synSpecial">(</span><span class="synConstant">3</span>, <span class="synConstant">&quot;エキテン治療院&quot;</span>, <span class="synConstant">&quot;3番目の店舗です&quot;</span>, <span class="synSpecial">[</span><span class="synConstant">&quot;マッサージ&quot;</span>, <span class="synConstant">&quot;整体&quot;</span><span class="synSpecial">])</span>, <span class="synSpecial">]</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> initNearlyShops<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">array</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synSpecial">[</span> <span class="synPreProc">new</span> Shop<span class="synSpecial">(</span><span class="synConstant">4</span>, <span class="synConstant">&quot;エキテン整体院&quot;</span>, <span class="synConstant">&quot;1番目の条件に近い店舗です&quot;</span>, <span class="synSpecial">[</span><span class="synConstant">&quot;整体&quot;</span><span class="synSpecial">])</span>, <span class="synPreProc">new</span> Shop<span class="synSpecial">(</span><span class="synConstant">5</span>, <span class="synConstant">&quot;エキテン鍼灸院&quot;</span>, <span class="synConstant">&quot;2番目の条件に近い店舗です&quot;</span>, <span class="synSpecial">[</span><span class="synConstant">&quot;鍼灸&quot;</span>, <span class="synConstant">&quot;マッサージ&quot;</span>, <span class="synConstant">&quot;整体&quot;</span><span class="synSpecial">])</span>, <span class="synSpecial">]</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p></details></p> <p><details><summary>app/View/Model/Shop.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></summary></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\View\Model; <span class="synType">class</span> Shop <span class="synSpecial">{</span> <span class="synType">private</span> <span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">id</span>; <span class="synType">private</span> <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">name</span>; <span class="synType">private</span> <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">introduction</span>; <span class="synType">private</span> <span class="synType">array</span> <span class="synStatement">$</span><span class="synIdentifier">genreNames</span>; <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span><span class="synType">int</span> <span class="synStatement">$</span><span class="synIdentifier">id</span>, <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">name</span>, <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">introduction</span>, <span class="synType">array</span> <span class="synStatement">$</span><span class="synIdentifier">genreNames</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>id <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">id</span>; <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span><span class="synIdentifier">name</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">name</span>; <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>introduction <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">introduction</span>; <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>genreNames <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">genreNames</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> getUrl<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">string</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synConstant">&quot;/shop_</span><span class="synSpecial">{</span><span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>id<span class="synSpecial">}</span><span class="synConstant">/&quot;</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> getName<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">string</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span><span class="synIdentifier">name</span>; <span class="synSpecial">}</span> <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">string</span> <span class="synComment"> */</span> <span class="synType">public</span> <span class="synPreProc">function</span> getIntroduction<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">string</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>introduction; <span class="synSpecial">}</span> <span class="synComment">/**</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">string</span> <span class="synComment"> */</span> <span class="synType">public</span> <span class="synPreProc">function</span> getGenreNames<span class="synSpecial">()</span><span class="synStatement">:</span> <span class="synType">string</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> <span class="synIdentifier">implode</span><span class="synSpecial">(</span><span class="synConstant">&quot;/&quot;</span>, <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>genreNames<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p></details></p> <h2 id="サブビューによる実装">サブビューによる実装</h2> <p>以下に列挙する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>はサブビューによる実装をした場合となります。</p> <pre class="code" data-lang="" data-unlink>/app ┣Http/ ┃ ┗Controllers/ ┃ ┃ ┗SimplePageController.php ┃ ┗View/ ┃   ┗Model/ ┃     ┗Shop.php ┗resources/   ┗views/     ┣sample_page.blade.php ・・・ControllerからレンダリングされるBlate Templates     ┗shop_panel.blade.php ・・・sample_page.blade.phpから読み込まれるサブビュー</pre> <p><details><summary>resources/views/sample_page.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></summary></p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">h1</span><span class="synIdentifier">&gt;</span>店舗一覧<span class="synIdentifier">&lt;/</span><span class="synStatement">h1</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span>新宿区のおすすめ整体院<span class="synIdentifier">&lt;/</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span> @foreach($shops as $shop) @include(&quot;shop_panel&quot;, [&quot;shop&quot; =<span class="synError">&gt;</span> $shop]) @endforeach <span class="synIdentifier">&lt;</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span>条件に近い整体院<span class="synIdentifier">&lt;/</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span> @foreach($nearlyShops as $nearlyShop) @include(&quot;shop_panel&quot;, [&quot;shop&quot; =<span class="synError">&gt;</span> $nearlyShop]) @endforeach </pre> <p></details></p> <p><details><summary>resources/views/shop_panel.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></summary></p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span>{{ $shop-<span class="synError">&gt;</span>getIntroduction() }}<span class="synIdentifier">&lt;/</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;&lt;</span><span class="synStatement">a</span><span class="synIdentifier"> </span><span class="synType">href</span><span class="synIdentifier">=</span><span class="synConstant">&quot;{{ $shop-&gt;getUrl() }}&quot;</span><span class="synIdentifier">&gt;</span><span class="synUnderlined">{{ $shop-&gt;getName() }}</span><span class="synIdentifier">&lt;/</span><span class="synStatement">a</span><span class="synIdentifier">&gt;&lt;/</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span>{{ $shop-<span class="synError">&gt;</span>getGenreNames() }}<span class="synIdentifier">&lt;/</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> </pre> <p></details></p> <h2 id="Blade-Componentsによる実装">Blade Componentsによる実装</h2> <p>以下に列挙する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>はBlade Componentsによる実装をした場合となります。</p> <pre class="code" data-lang="" data-unlink>/app ┣Http/ ┃ ┣Controllers/ ┃ ┃ ┗SimplePageController.php ・・・ コントローラー ┃ ┗View/ ┃   ┣Components/ ┃     ┗ShopPanel.php ・・・ shop-panel.blade.phpのコンポーネントクラス ┃   ┗Model/ ┃     ┗Shop.php ┗resources/   ┗views/     ┣components/ ・・・Blade Componentsが集約されるディレクトリ     ┃ ┣anker_link.blade.php ・・・ shop-panel.blade.phpから読み込まれるAnonymous Components     ┃ ┗shop-panel.blade.php ・・・ sample_page.blade.phpから読み込まれるClass Based Components     ┗sample_page.blade.php ・・・ ControllerからレンダリングされるBlade Templates</pre> <p><details><summary>resources/views/sample_page.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></summary></p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">h1</span><span class="synIdentifier">&gt;</span>店舗一覧<span class="synIdentifier">&lt;/</span><span class="synStatement">h1</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span>新宿区のおすすめ整体院<span class="synIdentifier">&lt;/</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span> @foreach($shops as $shop) <span class="synIdentifier">&lt;</span>x-shop-panel<span class="synIdentifier"> :shop=</span><span class="synConstant">&quot;$shop&quot;</span><span class="synIdentifier">/&gt;</span> @endforeach <span class="synIdentifier">&lt;</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span>条件に近い整体院<span class="synIdentifier">&lt;/</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span> @foreach($nearlyShops as $nearlyShop) <span class="synIdentifier">&lt;</span>x-shop-panel<span class="synIdentifier"> :shop=</span><span class="synConstant">&quot;$nearlyShop&quot;</span><span class="synIdentifier">/&gt;</span> @endforeach </pre> <p></details></p> <p><details><summary>app/View/Components/ShopPanel.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></summary></p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\View\Components; <span class="synPreProc">use</span> App\View\Model\Shop; <span class="synPreProc">use</span> Illuminate\View\Component; <span class="synType">class</span> ShopPanel <span class="synType">extends</span> Component <span class="synSpecial">{</span> <span class="synType">public</span> <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">name</span>; <span class="synType">public</span> <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">url</span>; <span class="synType">public</span> <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">introduction</span>; <span class="synType">public</span> <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">genreNames</span>; <span class="synType">public</span> <span class="synPreProc">function</span> <span class="synStatement">__construct</span><span class="synSpecial">(</span>Shop <span class="synStatement">$</span><span class="synIdentifier">shop</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span><span class="synIdentifier">name</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">shop</span><span class="synType">-&gt;</span>getName<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>url <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">shop</span><span class="synType">-&gt;</span>getUrl<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>introduction <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">shop</span><span class="synType">-&gt;</span>getIntroduction<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>genreNames <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">shop</span><span class="synType">-&gt;</span>getGenreNames<span class="synSpecial">()</span>; <span class="synSpecial">}</span> <span class="synType">public</span> <span class="synPreProc">function</span> render<span class="synSpecial">()</span> <span class="synSpecial">{</span> <span class="synStatement">return</span> view<span class="synSpecial">(</span><span class="synConstant">'components.shop-panel'</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p></details></p> <p><details><summary>resources/views/components/shop-panel.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></summary></p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span>{{ $introduction }}<span class="synIdentifier">&lt;/</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span>x-anker<span class="synIdentifier">_link :</span><span class="synType">url</span><span class="synIdentifier">=</span><span class="synConstant">&quot;$url&quot;</span><span class="synIdentifier"> :</span><span class="synType">text</span><span class="synIdentifier">=</span><span class="synConstant">&quot;$name&quot;</span><span class="synIdentifier">/&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">h2</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span>{{ $genreNames }}<span class="synIdentifier">&lt;/</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> </pre> <p></details></p> <p><details><summary>resources/views/components/anker_link.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></summary></p> <pre class="code lang-html" data-lang="html" data-unlink>@props([ 'url', 'text' ]) <span class="synIdentifier">&lt;</span><span class="synStatement">a</span><span class="synIdentifier"> </span><span class="synType">href</span><span class="synIdentifier">=</span><span class="synConstant">&quot;{{ $url }}&quot;</span><span class="synIdentifier">&gt;</span><span class="synUnderlined">{{ $text }}</span><span class="synIdentifier">&lt;/</span><span class="synStatement">a</span><span class="synIdentifier">&gt;</span> </pre> <p></details></p> <p>1店舗分の情報を持つパネル(shop-panel )をClass Based Componentsで実装し、アンカーリンク(anker_link)をAnonymous Componentsで実装しています。</p> <p>※リニューアルプロジェクトのコーディング規約ではClass Based ComponentsはBladeファイル名を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B1%A5%D0%A5%D6">ケバブ</a>ケース、Anonymous ComponentsはBladeファイル名をスネークケースで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>することで区別しております</p> <p>呼び出し元からは <code>&lt;x-shop-panel :shop="$nearlyShop"/&gt;</code> のように <code>x-{Bladeファイル名}</code> タグで読み込みます。その際に変数の受け渡しが可能です。</p> <p>Anonymous Componentsの場合は <code>@props</code> で受け取る値を定義します。Class Based Componentsの場合はBladeファイルに渡されるのではなく、<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%E9%A5%AF">ラク</a>タに渡され、Bladeファイルからはコンスト<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>今回の場合は<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%E9%A5%AF">ラク</a>タでビューモデルからプロパティの生成を行っています。</p> <p>またBlade ComponentsのBladeファイルは <code>resources/views/components</code> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ内に作られるため、他のBlade Templatesとは明示的に分けることが可能です。</p> <h1 id="Blade-Componentsの所感">Blade Componentsの所感</h1> <p>Blade Componentsを活用することでレイアウト(Blade Templates)と<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>(Blade Components)を明示的に分けながらBladeの実装が行えています。当初はレイアウトと<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>を従来通りBlade Templatesとして実装し、単にレイアウトと<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%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リで分割するという話もあがりましたが、新しくエンジニアがプロダクトにジョインした場合などを考えると可能な限りLaravelのルールに準じた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成にできている方がいいということでBlade Componentsを採用することになりました。</p> <p>またClass Based Componentsを活用することでビューに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B5%A5%A4">アサイ</a>ンしたViewModelのメソッドをBladeファイルからではなく<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>クラスから参照できるようになったため、PHPStormなどの<a class="keyword" href="http://d.hatena.ne.jp/keyword/IDE">IDE</a>のナビゲーション機能が効かないメソッド呼び出しをする必要がなくなりました。副次的な効果にはなりますがこれもBlade Componentsを採用した恩恵です。</p> <p>これらのことからリニューアルプロジェクトでBlade Componentsを採用したのは正解だったと思います。</p> <h1 id="仲間を募集しております">仲間を募集しております</h1> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集 採用・求人情報 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> <h1 id="参考URL">参考URL</h1> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Freadouble.com%2Flaravel%2F8.x%2Fja%2Fblade.html" title="Bladeテンプレート 8.x Laravel" 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://readouble.com/laravel/8.x/ja/blade.html">readouble.com</a></cite></p> doj_suzuki_cecil AWS Certified Cloud Practitioner 勉強法 hatenablog://entry/13574176438030532231 2021-11-10T17:48:27+09:00 2021-11-10T17:48:27+09:00 概要説明 AWS Certifiedとは 役割別認定 基礎コース アソシエイト プロフェッショナル 専門知識認定 Cloud Practitionerの勉強法 勉強法要約 AWS認定資格試験テキスト AWS認定 クラウドプラクティショナー Udemy この問題だけで合格可能!AWS 認定クラウドプラクティショナー 模擬試験問題集(7回分455問) 試験結果 おわりに 仲間を募集しております 参考URL 概要説明 こんにちは!株式会社デザインワン・ジャパンでエキテンのリニューアルを担当しているサービス開発部の鈴木セシル(@suzuki_cecil_)です。 このたびサーバーサイドエンジニアである… <ul class="table-of-contents"> <li><a href="#概要説明">概要説明</a></li> <li><a href="#AWS-Certifiedとは">AWS Certifiedとは</a><ul> <li><a href="#役割別認定">役割別認定</a><ul> <li><a href="#基礎コース">基礎コース</a></li> <li><a href="#アソシエイト">アソシエイト</a></li> <li><a href="#プロフェッショナル">プロフェッショナル</a></li> </ul> </li> <li><a href="#専門知識認定">専門知識認定</a></li> </ul> </li> <li><a href="#Cloud-Practitionerの勉強法">Cloud Practitionerの勉強法</a><ul> <li><a href="#勉強法要約">勉強法要約</a></li> <li><a href="#AWS認定資格試験テキスト-AWS認定-クラウドプラクティショナー">AWS認定資格試験テキスト AWS認定 クラウドプラクティショナー</a></li> <li><a href="#Udemy-この問題だけで合格可能AWS-認定クラウドプラクティショナー-模擬試験問題集7回分455問">Udemy この問題だけで合格可能!AWS 認定クラウドプラクティショナー 模擬試験問題集(7回分455問)</a></li> </ul> </li> <li><a href="#試験結果">試験結果</a></li> <li><a href="#おわりに">おわりに</a></li> <li><a href="#仲間を募集しております">仲間を募集しております</a></li> <li><a href="#参考URL">参考URL</a></li> </ul> <h1 id="概要説明">概要説明</h1> <p>こんにちは!株式会社デザインワン・ジャパンでエキテンのリニューアルを担当しているサービス開発部の鈴木セシル(<a href="https://twitter.com/suzuki_cecil_">@suzuki_cecil_</a>)です。</p> <p>このたびサーバーサイドエンジニアである私が <a href="https://aws.amazon.com/jp/certification/certified-cloud-practitioner/">AWS Certified Cloud Practitioner</a> に合格しましたので、受験しようかと考えている方の参考になればと思い合格までの過程を記事にすることとしました。</p> <h1 id="AWS-Certifiedとは"><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Certifiedとは</h1> <p><strong>まず<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Certifiedをご存知でない方のために<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Certifiedの紹介をいたします。既にご存知の方はこの段落は飛ばしてもらえればと思います。</strong></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/AWS">AWS</a>に携わる方の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A5%E3%A5%EA%A5%A2%A5%D1%A5%B9">キャリアパス</a>として<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Certifiedが提示されています。提示されたプログラムを体系的に学習および受験することでそれぞれのキャリアに応じた知識とスキルが習得できるようになっており、また<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>の知識とスキルを有している証明として活用されます。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Certifiedは以下の通り分類されます。</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/d/doj_suzuki_cecil/20211107/20211107144414.png" alt="f:id:doj_suzuki_cecil:20211107144414p:plain" width="1200" height="664" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>認定試験一覧(<a href="https://aws.amazon.com/jp/certification/">AWS &#x8A8D;&#x5B9A; &ndash; AWS &#x30AF;&#x30E9;&#x30A6;&#x30C9;&#x30B3;&#x30F3;&#x30D4;&#x30E5;&#x30FC;&#x30C6;&#x30A3;&#x30F3;&#x30B0;&#x8A8D;&#x5B9A;&#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30E0; | AWS</a>より引用)</p> <p></figcaption></figure></p> <h2 id="役割別認定">役割別認定</h2> <h3 id="基礎コース">基礎コース</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>の全体的な理解の証明となる試験群です。また専門知識認定を受けるための条件でもあります。基礎コースにはCloud Practitionerのみ属します。求められる能力は以下の通りです。</p> <p><strong>Cloud Practitionerで求められる能力</strong> (<a href="https://d1.awsstatic.com/ja_JP/training-and-certification/docs-cloud-practitioner/AWS-Certified-Cloud-Practitioner_Exam-Guide.pdf">試験ガイド</a>より引用)</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>の価値を説明する</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> 責任共有モデルを理解し、説明する</li> <li>セキュリティのベストプ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ティスを理解する</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>のコスト、経済性、請求方法を理解する</li> <li>コンピューティング、ネットワーク、データベース、ストレージなど、<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> の中核サービスを説明し、位置付ける</li> <li>一般的<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/AWS">AWS</a> のサービスを特定する</li> </ul> <h3 id="アソシエイト">アソシエイト</h3> <p>技術的役割別における<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>の理解の証明となる試験群です。アソシエイトには Solutions Architect, SysOps Administrator, Developerが属します。</p> <p><strong>Solutions Architectで求められる能力</strong> (<a href="https://d1.awsstatic.com/ja_JP/training-and-certification/docs-sa-assoc/AWS-Certified-Solutions-Architect-Associate_Exam-Guide.pdf">試験ガイド</a>より引用)</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> のコンピューティング、ネットワーキング、ストレージ、管理、データベースサービス を使用した実務経験</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</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>提示された技術要件を満たす <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> のサービスを特定する能力</li> <li>適切に設計されたソリューションを <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> で構築するためのベストプ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ティスに関する理解</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> のグローバルインフラスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>チャに関する理解</li> <li>従来のサービスに関連する <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> のセキュリティサービスと機能に関する理解</li> </ul> <p><strong>SysOps Administratorで求められる能力</strong> (<a href="https://d1.awsstatic.com/ja_JP/training-and-certification/docs-sysops-associate/AWS-Certified-SysOps-Administrator-Associate_Exam-Guide.pdf">試験ガイド</a>より引用)</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</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>の実践経験が 1 年以上</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> でのワークロードのデプロイ、管理、および運用に関する経験</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Well-Architected Framework に関する理解</li> <li><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> <a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a> の実践経験</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> ネットワークとセキュリティサービスに関する理解</li> <li>セキュリティコン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%ED%A1%BC%A5%EB">トロール</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D7%A5%E9%A5%A4%A5%A2%A5%F3%A5%B9">コンプライアンス</a>要件の実装に関する実践経験</li> </ul> <p><strong>Developerで求められる能力</strong> (<a href="https://d1.awsstatic.com/ja_JP/training-and-certification/docs-dev-associate/AWS-Certified-Developer-Associate_Exam-Guide.pdf">試験ガイド</a>より引用)</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> のサービスの <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CLI">CLI</a>、ソフトウェア開発キット (<a class="keyword" href="http://d.hatena.ne.jp/keyword/SDK">SDK</a>) を使用してアプリケーションを記述する</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> のサービスの主要な機能を特定する</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> の責任共有モデルを理解する</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%D1%C2%B3%C5%AA%A5%A4%A5%F3%A5%C6%A5%B0%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3">継続的インテグレーション</a>と継続的デリバリー (CI/CD) パイプラインを使用してアプリケーションを <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> にデプロイする</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> のサービスを使用し、やり取りする</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ネイティブアプリケーションの基礎知識を応用してコードを記述する</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> セキュリティのベストプ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ティスを利用してコードを記述する (例えば、コード内のシークレットキーとアクセスキーの代わりに IAM ロールを使用する)</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> でコードモジュールを作成、保守、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>する</li> </ul> <h3 id="プロフェッショナル">プロフェッショナル</h3> <p>技術的役割別における<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>の最高レベルの理解の証明となる試験群です。プロフェッショナルには Solutions Architect, DevOps Engineerが属します。</p> <p><strong>Solutions Architectで求められる能力</strong> (<a href="https://d1.awsstatic.com/ja_JP/training-and-certification/docs-sa-pro/AWS-Certified-Solutions-Architect-Professional_Exam-Guide.pdf">試験ガイド</a>より引用)</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Well-Architected Framework の 5 本の柱を説明し、適用する</li> <li>ビジネス目標をアプリケーション要件と<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>要件に反映させる</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</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>を使用してハイブリッド<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>を設計する</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%D1%C2%B3%C5%AA%A5%A4%A5%F3%A5%C6%A5%B0%A5%EC%A1%BC%A5%B7%A5%E7%A5%F3">継続的インテグレーション</a>と継続的デリバリー (CI/CD) プロセスを設計する</li> </ul> <p><strong>DevOps Engineerで求められる能力</strong> (<a href="https://d1.awsstatic.com/ja_JP/training-and-certification/docs-devops-pro/AWS-Certified-DevOps-Engineer-Professional_Exam-Guide.pdf">試験ガイド</a>より引用)</p> <ul> <li>高度に自動化されたインフラスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>チャの構築に関する経験</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A5%DA%A5%EC%A1%BC%A5%C6%A5%A3%A5%F3%A5%B0%A5%B7%A5%B9%A5%C6%A5%E0">オペレーティングシステム</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> </ul> <h2 id="専門知識認定">専門知識認定</h2> <p>特定の技術分野における高度なスキルの証明となる試験群です。専門知識認定にはAdvanced Networking, Data Analytics, Database, Machine Learning, Securityが属します。</p> <h1 id="Cloud-Practitionerの勉強法">Cloud Practitionerの勉強法</h1> <h2 id="勉強法要約">勉強法要約</h2> <p><strong>勉強期間</strong></p> <p>試験申し込みから試験当日までの約2ヶ月程度</p> <p><strong>勉強時間合計</strong></p> <p>15時間程度</p> <p><strong>教材</strong></p> <ul> <li><a href="https://www.amazon.co.jp/AWS%E8%AA%8D%E5%AE%9A%E8%B3%87%E6%A0%BC%E8%A9%A6%E9%A8%93%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88-AWS%E8%AA%8D%E5%AE%9A-%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%BC-%E5%B1%B1%E4%B8%8B-%E5%85%89%E6%B4%8B/dp/4797397403">AWS認定資格試験テキスト AWS認定 クラウドプラクティショナー</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20211107/20211107161404.png" alt="f:id:doj_suzuki_cecil:20211107161404p:plain" width="354" height="499" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li><a href="https://www.udemy.com/course/aws-4260/">Udemy この問題だけで合格可能!AWS 認定クラウドプラクティショナー 模擬試験問題集(7回分455問)</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20211107/20211107161602.png" alt="f:id:doj_suzuki_cecil:20211107161602p:plain" width="480" height="270" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li><a href="https://docs.aws.amazon.com/ja_jp/">AWS 公式ドキュメント</a></li> </ul> <h2 id="AWS認定資格試験テキスト-AWS認定-クラウドプラクティショナー"><a href="https://www.amazon.co.jp/AWS%E8%AA%8D%E5%AE%9A%E8%B3%87%E6%A0%BC%E8%A9%A6%E9%A8%93%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88-AWS%E8%AA%8D%E5%AE%9A-%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%BC-%E5%B1%B1%E4%B8%8B-%E5%85%89%E6%B4%8B/dp/4797397403">AWS認定資格試験テキスト AWS認定 クラウドプラクティショナー</a></h2> <p><strong>勉強時間合計</strong></p> <p>3時間程度(公式ドキュメントによる補完を含む)</p> <p><strong>勉強方法</strong></p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Kindle">Kindle</a>版を購入し、通勤時間や休憩時間といったちょっとした時間を活用して少しずつ読書を進める(1日あたり約15分程度)</li> <li>各章の最後に練習問題があるため実際に問題を解いてみる</li> <li>練習問題で分からなかった問題に関しては本書の該当項目読み直しまたは公式ドキュメントに目を通して理解度を補完</li> </ul> <p><strong>所感</strong></p> <ul> <li>内容は分かりやすいため初学者向けの内容に仕上がっていると思います</li> <li>一方で試験範囲をカバーしきれていないため本書を足掛かりに他の教材でも勉強をする必要があると思います</li> </ul> <h2 id="Udemy-この問題だけで合格可能AWS-認定クラウドプラクティショナー-模擬試験問題集7回分455問"><a href="https://www.udemy.com/course/aws-4260/">Udemy この問題だけで合格可能!AWS 認定クラウドプラクティショナー 模擬試験問題集(7回分455問)</a></h2> <p><strong>勉強時間合計</strong></p> <p>12時間程度(公式ドキュメントによる補完を含む)</p> <p><strong>勉強方法</strong></p> <ul> <li>休日にまとまった時間を確保して実際の試験を想定して基本レベル①、基本レベル②を受験(私の場合、初回の正答率はそれぞれ70%程度でした)</li> <li>基本レベル①、基本レベル②で不正解だった問題、理解が怪しかった問題に関しては解説および公式ドキュメントに目を通すことで理解度を補完</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>対応であること、中断機能を活用して隙間時間に基本レベル①、基本レベル②の問題を少しずつ解くことで復習</li> <li>応用レベル①、応用レベル②、応用レベル③も基本レベル①、基本レベル②同様に問題解答および復習</li> </ul> <p><strong>所感</strong></p> <ul> <li>基本レベルは本試の一般的な出題相当もしくは少し難しめの難易度設計になっているかと思いました</li> <li>応用レベルは本試の難易度高めの出題相当もしくはそれ以上の難易度設計になっているかと思いました</li> <li>本試の出題内容にもよりますが基本レベル相当の問題が解答できるようになれば合格ラインに達するかと思います</li> </ul> <h1 id="試験結果">試験結果</h1> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20211107/20211107171139.png" alt="f:id:doj_suzuki_cecil:20211107171139p:plain" width="1200" height="283" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_suzuki_cecil/20211107/20211107171251.png" alt="f:id:doj_suzuki_cecil:20211107171251p:plain" width="1200" height="285" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>スコアはギリギリでしたが合格ラインに達することはできました。各セクションごとの理解度では請求と料金のみ改善が必要でした。請求と料金に関しての反省としては出題の割合が低いこともありUdemyの模試においても出題数が少なく請求と料金における問題の予習が不足いたのではと思います。</p> <h1 id="おわりに">おわりに</h1> <p>ギリギリではありましたが主に<a href="https://www.amazon.co.jp/AWS%E8%AA%8D%E5%AE%9A%E8%B3%87%E6%A0%BC%E8%A9%A6%E9%A8%93%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88-AWS%E8%AA%8D%E5%AE%9A-%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%BC-%E5%B1%B1%E4%B8%8B-%E5%85%89%E6%B4%8B/dp/4797397403">AWS認定資格試験テキスト AWS認定 クラウドプラクティショナー</a>および<a href="https://www.udemy.com/course/aws-4260/">Udemy この問題だけで合格可能!AWS 認定クラウドプラクティショナー 模擬試験問題集(7回分455問)</a>による勉強でCloud Practitionerに合格することができました。</p> <p>今後の展望としてはアソシエイトの中でも特にサーバーサイドエンジニアの領域であるDeveloperに挑戦したいと思います。</p> <h1 id="仲間を募集しております">仲間を募集しております</h1> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの募集 採用・求人情報 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> <h1 id="参考URL">参考URL</h1> <ul> <li><a href="AWS%20%E8%AA%8D%E5%AE%9A">https://aws.amazon.com/jp/certification/</a></li> <li><a href="https://www.amazon.co.jp/AWS%E8%AA%8D%E5%AE%9A%E8%B3%87%E6%A0%BC%E8%A9%A6%E9%A8%93%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88-AWS%E8%AA%8D%E5%AE%9A-%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%BC-%E5%B1%B1%E4%B8%8B-%E5%85%89%E6%B4%8B/dp/4797397403">AWS認定資格試験テキスト AWS認定 クラウドプラクティショナー</a></li> <li><a href="https://www.udemy.com/course/aws-4260/">Udemy この問題だけで合格可能!AWS 認定クラウドプラクティショナー 模擬試験問題集(7回分455問)</a></li> </ul> doj_suzuki_cecil エキテンリニューアルにおけるクリーンアーキテクチャのクラス構成 hatenablog://entry/13574176438019388199 2021-10-07T14:30:22+09:00 2021-10-07T14:30:22+09:00 概要説明 クリーンアーキテクチャとは クリーンアーキテクチャにおける4つの層 個人的な見解 リニューアルプロジェクトにおけるクリーンアーキテクチャの構成 クラス構成図 入力 Controller Converter UsecaseInput 永続 Usecase Factory DTO Query QueryGateway Dao Transformer 出力 UsecaseOutputImpl UsecaseOutput Presenter ViewModel クリーンアーキテクチャを導入した所感 クリーンアーキテクチャのメリット クリーンアーキテクチャのデメリット 仲間を募集しております … <ul class="table-of-contents"> <li><a href="#概要説明">概要説明</a></li> <li><a href="#クリーンアーキテクチャとは">クリーンアーキテクチャとは</a><ul> <li><a href="#クリーンアーキテクチャにおける4つの層">クリーンアーキテクチャにおける4つの層</a></li> <li><a href="#個人的な見解">個人的な見解</a></li> </ul> </li> <li><a href="#リニューアルプロジェクトにおけるクリーンアーキテクチャの構成">リニューアルプロジェクトにおけるクリーンアーキテクチャの構成</a><ul> <li><a href="#クラス構成図">クラス構成図</a></li> <li><a href="#入力">入力</a><ul> <li><a href="#Controller">Controller</a></li> <li><a href="#Converter">Converter</a></li> <li><a href="#UsecaseInput">UsecaseInput</a></li> </ul> </li> <li><a href="#永続">永続</a><ul> <li><a href="#Usecase">Usecase</a></li> <li><a href="#Factory">Factory</a></li> <li><a href="#DTO">DTO</a></li> <li><a href="#Query">Query</a></li> <li><a href="#QueryGateway">QueryGateway</a></li> <li><a href="#Dao">Dao</a></li> <li><a href="#Transformer">Transformer</a></li> </ul> </li> <li><a href="#出力">出力</a><ul> <li><a href="#UsecaseOutputImpl">UsecaseOutputImpl</a></li> <li><a href="#UsecaseOutput">UsecaseOutput</a></li> <li><a href="#Presenter">Presenter</a></li> <li><a href="#ViewModel">ViewModel</a></li> </ul> </li> </ul> </li> <li><a href="#クリーンアーキテクチャを導入した所感">クリーンアーキテクチャを導入した所感</a><ul> <li><a href="#クリーンアーキテクチャのメリット">クリーンアーキテクチャのメリット</a></li> <li><a href="#クリーンアーキテクチャのデメリット">クリーンアーキテクチャのデメリット</a></li> </ul> </li> <li><a href="#仲間を募集しております">仲間を募集しております</a></li> <li><a href="#参考URL">参考URL</a></li> </ul> <h1 id="概要説明">概要説明</h1> <p>弊社にて運用している口コミサービス エキテンはサービス開始の2007年から14年間運用を続けております。</p> <p>10年以上運用されているサービスというと技術的負債が溜まりに溜まりがちになるかと思いますが、エキテンも例に漏れずサービスの成長に比例して技術的負債が少しずつ積もっていきました。例えばエキテンは大きく分けてユーザー様が店舗を探すサービス、店舗の管理者様が自ページを管理するサービス、エキテン運用者がエキテンの管理をするサービスの3つから構成されており、これらのサービスが密結合しているためサービスへの改修が他のサービスへの改修に影響するといったことが日常茶飯事です。</p> <p>そんな技術的負債を返済するということを目的の1つとしてリニューアルプロジェクトが発足しました。今回の記事ではリニューアルプロジェクトにおけるクリーン<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> <h1 id="クリーンアーキテクチャとは">クリーン<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>とは</h1> <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>はRobert C. Martin(Uncle Bob)が2012年に提唱した<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/d/doj_suzuki_cecil/20211005/20211005194722.png" alt="f:id:doj_suzuki_cecil:20211005194722p:plain" width="772" height="567" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>同心円の部分はクラスを以下の4つの層に分け、それらの層の依存関係を説明したものとなります。右下の図は依存関係に則ったクラス図となっております。</p> <h3 id="クリーンアーキテクチャにおける4つの層">クリーン<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>における4つの層</h3> <ul> <li>Enterprise Business Rules(モデル層) <ul> <li>システムでソフトウェア化したいモノ(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>モデル)に注目し、その制約や構造、ビジネス的な振る舞いを記述する層。他の層への依存は許可されない。</li> </ul> </li> <li>Application Business Rules(サービス層) <ul> <li>システムを利用するユーザーの動作(制御)を記述する層。「入力」「永続」「出力」を抽象的に定義し、それらとモデル層のモデルたちを組み合わせることで、ビジネス的に達成したい処理を記述する。後述するInterface Adapters、 Frameworks &amp; Driversへの依存は許可されない。</li> </ul> </li> <li>Interface Adapters(アダプター層) <ul> <li>サービス層で抽象的に定義された「入力」「永続」「出力」をポートととらえ、アダプターとしてそれらを実装する層。後述するFrameworks &amp; Driversへの依存は許可されない。</li> </ul> </li> <li>Frameworks &amp; Drivers(インフラ層) <ul> <li><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>やライブラリ、データストアなどシステムの外界に存在する要素が存在する層。</li> </ul> </li> </ul> <h3 id="個人的な見解">個人的な見解</h3> <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>はSOLID原則である「単一責任の原則」、「オープン・クローズドの原則」、「リスコフの置換原則」、「インターフェース分離の原則」、「依存性逆転の原則」に則り、依存性・凝集度・結合度が整然とした<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>実際、『Clean Architecture 達人に学ぶソフトウェアの構造と設計』で以下のように述べられています。</p> <blockquote><p>図の円は概要を示したものである。したがって、この4つ以外は認めないというルールはない。</p></blockquote> <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>の構成が必ずしも図の円に従ったものではないことを先に明言させていただきます。</p> <h1 id="リニューアルプロジェクトにおけるクリーンアーキテクチャの構成">リニューアルプロジェクトにおけるクリーン<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>の構成</h1> <h2 id="クラス構成図">クラス構成図</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>のクラス構成図がこちらになります。</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/d/doj_suzuki_cecil/20211006/20211006101122.png" alt="f:id:doj_suzuki_cecil:20211006101122p:plain" width="1200" height="751" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クラス構成図</figcaption></figure></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> <h2 id="入力">入力</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/d/doj_suzuki_cecil/20211006/20211006104121.png" alt="f:id:doj_suzuki_cecil:20211006104121p:plain" width="1200" height="398" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クラス構成図(入力部)</figcaption></figure></p> <h3 id="Controller">Controller</h3> <p>Interface Adaptersに属します。</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>にはLaravelを使っており、Laravelのサー<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B9%A5%B3">ビスコ</a>ンテナによってメソッドの引数で自動的かつ再起的にDIの解決を行ってくれるためConverterを引数に受け取れるようになっています。</p> <p>Controllerの責務はConverterを引数にUsecaseを実行し、Usecaseから返されたUsecaseOutputを引数にPresenterを実行するだけなのでメソッドの処理は基本的には2行で完結します。</p> <h3 id="Converter">Converter</h3> <p>Interface Adaptersに属します。</p> <p>HTTPリク<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/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</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> Laravelの <code>Illuminate\Http\Request</code> をインジェクトしています。そのためInterface AdaptersがFrameworks &amp; Driversに依存しており、クリーン<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> <h3 id="UsecaseInput">UsecaseInput</h3> <p>Application Business Rulesに属します。</p> <p>Usecase(Application Business Rules)がConverter(Interface Adapters)に依存することを回避するために依存性逆転を目的としたインターフェースです。</p> <h2 id="永続">永続</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/d/doj_suzuki_cecil/20211006/20211006104321.png" alt="f:id:doj_suzuki_cecil:20211006104321p:plain" width="1200" height="873" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クラス構成図(永続部)</figcaption></figure></p> <h3 id="Usecase">Usecase</h3> <p>Application Business Rulesに属します。Factoryから取得したデータを基にサービス固有の処理を行う<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>処理の結果はUsecaseOutputとしてControllerに返却します。</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/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>の段落で記載させていただいてます。</p> <h3 id="Factory">Factory</h3> <p>Application Business Rulesに属します。</p> <p>後述するQueryから返されたEntityを適切な粒度に集約し<a class="keyword" href="http://d.hatena.ne.jp/keyword/DTO">DTO</a>としてUsecaseに返す<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 id="DTO"><a class="keyword" href="http://d.hatena.ne.jp/keyword/DTO">DTO</a></h3> <p>Application Business Rulesに属します。</p> <p>Factoryで集約したEntityをを保持する<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 id="Query">Query</h3> <p>Application Business Rulesに属します。</p> <p>後述するQueryGatewayのインターフェースです。Factory(Application Business Rules)がQueryGateway(Interface Adapters)に依存することを回避するために依存性逆転を目的としています。</p> <p>一般的にRepositoryと呼ばれますが、ここではコマンドクエリ分離原則に従いそれぞれQueryとCommandと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>してあります。</p> <h3 id="QueryGateway">QueryGateway</h3> <p>Interface Adaptersに属します。</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/Gateway">Gateway</a>ではDaoとTransformerを利用してDBをはじめとするデータストアから取得したデータ(主にEloquent)をEntityとして返します。</p> <h3 id="Dao">Dao</h3> <p>Interface Adaptersに属します。</p> <p>主にLaravelのEloquentやDatabaseManagerを利用して実際の<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</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>です。データストアから取得したデータはEloquentModelやCollection、stdClassとして<a class="keyword" href="http://d.hatena.ne.jp/keyword/Gateway">Gateway</a>に返します。</p> <p>Laravelの <code>Illuminate\Database\Eloquent\Model</code> をインジェクトしているため、Converter同様にInterface AdaptersがFrameworks &amp; Driversに依存しておりますが同様の理由から許容しております。</p> <h3 id="Transformer">Transformer</h3> <p>Interface Adaptersに属します。Daoで取得したEloquentModelやCollection、stdClassとEntityの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A5%C3%A5%D4%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> <h2 id="出力">出力</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/d/doj_suzuki_cecil/20211006/20211006105211.png" alt="f:id:doj_suzuki_cecil:20211006105211p:plain" width="1200" height="769" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クラス構成図(出力部)</figcaption></figure></p> <h3 id="UsecaseOutputImpl">UsecaseOutputImpl</h3> <p>Application Business Rulesに属します。</p> <p>Usecaseの結果を集約する<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 id="UsecaseOutput">UsecaseOutput</h3> <p>Application Business Rulesに属します。</p> <p>UsecaseOutputImplのインターフェースです。</p> <h3 id="Presenter">Presenter</h3> <p>Interface Adaptersに属します。</p> <p>UsecaseOutputをViewModelへ加工し、主に<a class="keyword" href="http://d.hatena.ne.jp/keyword/Json">Json</a>形式でResponseを返却する<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>Laravelの <code>Illuminate\Http\JsonResponse</code> をインジェクトしているため、ConverterやDao同様にInterface AdaptersがFrameworks &amp; Driversに依存しておりますが同様の理由から許容しております。</p> <h3 id="ViewModel">ViewModel</h3> <p>Interface Adaptersに属します。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Json">Json</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> <h1 id="クリーンアーキテクチャを導入した所感">クリーン<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>を導入した所感</h1> <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>の導入により感じたメリット・デメリットの記載で記事を締めたいと思います。</p> <h2 id="クリーンアーキテクチャのメリット">クリーン<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> <ol> <li><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>の責務が明確である</li> <li><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>間の依存関係が整理され、また循環依存が発生しない</li> <li>テストが容易になる</li> <li><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>が明確になり、また特定のクラスがFatになるといったことが起こりにくくなる</li> <li>メンバー間でクラス設計の共通認識が取りやすい</li> </ol> <h2 id="クリーンアーキテクチャのデメリット">クリーン<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> <ol> <li>クラス数が多くなるため<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BE%C1%B0%B6%F5%B4%D6">名前空間</a>の設計を誤ると悲惨になる</li> <li>Application Business RulesがFrameworks &amp; Driversに依存しないため特にUsecaseにおいてCollectionなど<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>の強力な機能を利用できない</li> </ol> <h1 id="仲間を募集しております">仲間を募集しております</h1> <p>募集中の職種については以下を御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの採用/求人一覧 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> <h1 id="参考URL">参考URL</h1> <ul> <li><a href="https://gist.github.com/mpppk/609d592f25cab9312654b39f1b357c60">&#x30AF;&#x30EA;&#x30FC;&#x30F3;&#x30A2;&#x30FC;&#x30AD;&#x30C6;&#x30AF;&#x30C1;&#x30E3;&#x5B8C;&#x5168;&#x306B;&#x7406;&#x89E3;&#x3057;&#x305F; &middot; GitHub</a></li> </ul> doj_suzuki_cecil オンプレサーバーにAmazon Linux 2のVMを構築しよう hatenablog://entry/13574176438009525900 2021-09-30T14:00:00+09:00 2021-09-30T12:02:47+09:00 はじめに こんにちは!株式会社デザインワン・ジャパンでインフラ業務を担当している情報戦略部の冨田(@komitta)です。 本日よりデザインワン・ジャパンのテックブログを開設いたしました。 業務で培った内容をアウトプットし、世の中の発展につながることを目的としてゆるく続けていければと思っております。 はじめに 概要説明 Proxmoxについて Amazon Linux 2のVMを構築する Ansible によるプロビジョニング ディレクトリ構成 ./ansible/dev ./images/DockerFile ./docker-compose.yml おわりに 仲間を募集しております 参考U… <h1 id="はじめに">はじめに</h1> <p>こんにちは!株式会社デザインワン・ジャパンでインフラ業務を担当している情報戦略部の冨田(<a href="https://twitter.com/komitta">@komitta</a>)です。</p> <p>本日よりデザインワン・ジャパンのテックブログを開設いたしました。 業務で培った内容をアウトプットし、世の中の発展につながることを目的としてゆるく続けていければと思っております。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#概要説明">概要説明</a></li> <li><a href="#Proxmoxについて">Proxmoxについて</a></li> <li><a href="#Amazon-Linux-2のVMを構築する">Amazon Linux 2のVMを構築する</a></li> <li><a href="#Ansible-によるプロビジョニング">Ansible によるプロビジョニング</a><ul> <li><a href="#ディレクトリ構成">ディレクトリ構成</a></li> <li><a href="#ansibledev">./ansible/dev</a></li> <li><a href="#imagesDockerFile">./images/DockerFile</a></li> <li><a href="#docker-composeyml">./docker-compose.yml</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> <li><a href="#仲間を募集しております">仲間を募集しております</a></li> <li><a href="#参考URL">参考URL</a></li> </ul> <h1 id="概要説明">概要説明</h1> <p>弊社にて運営している口コミサービス エキテンを始めとしたサービスの大半は<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>は使った分だけ課金される従量課金制のため、<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>にサーバーを構築した場合起動した分だけコストが発生いたします。</p> <p>そのため数は少ないですが以下要望が社内であがっておりました。</p> <ul> <li>検証用サーバーがほしいけどなるべくコストはかけたくない</li> <li>使用頻度の少ない社内<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/AWS">AWS</a>上のサーバーコストを削減したい</li> </ul> <p>そんな(ニッチな)需要に応えるため社内のオンプレのサーバーに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a> 2の<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>を構築しました。という記事になります。</p> <h1 id="Proxmoxについて">Proxmoxについて</h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>を動かすためのHypervisor環境としてオンプレサーバーには<a href="https://www.proxmox.com/en/]">Proxmox</a>を導入しました。 Hypervisorの有名どころでいうと<a class="keyword" href="http://d.hatena.ne.jp/keyword/VMware">VMware</a>のESXiや<a class="keyword" href="http://d.hatena.ne.jp/keyword/Microsoft">Microsoft</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Hyper-V">Hyper-V</a>とかが挙げられますが、あえて今回はマイナー(?)な製品を入れております。</p> <p>前職でもESXi を使っていたのでESXiでも良かったのですが、どうせなら触ったことのないものを試したいという思いもありこちらを選びました。</p> <p>触ってみてよかった点は</p> <ul> <li>ベースのOSが<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Debian">Debian</a>系)の<a class="keyword" href="http://d.hatena.ne.jp/keyword/KVM">KVM</a>のため、トラブル時に<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>の操作コマンドで対応がしやすい<a href="#f-70f86237" name="fn-70f86237" title="ESXiの場合、独自UNIXのコマンド体系だったため調査がしづらかった印象がありました。(あくまでも主観です)">*1</a></li> </ul> <p>です。(<del>正直、<a class="keyword" href="http://d.hatena.ne.jp/keyword/KVM">KVM</a>ならなんでもよかった説</del>) 基本<a class="keyword" href="http://d.hatena.ne.jp/keyword/GUI">GUI</a>管理コンソール画面から操作は行うのですがOSに<a class="keyword" href="http://d.hatena.ne.jp/keyword/SSH">SSH</a>ログインして直接ファイル変更による操作も可能だったためすごく楽な印象でした。(ここについては当然メリデメあると思います)</p> <h1 id="Amazon-Linux-2のVMを構築する"><a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a> 2の<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>を構築する</h1> <p>本題です。Proxmoxの構築についてはここでは触れません。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>の構築は先人の方がまとめてくださった下記記事に沿って作成<a href="#f-8ef664f0" name="fn-8ef664f0" title="AWSからオンプレサーバーでのAmazon Linux 2 の構築手順についてマニュアルがあり、マニュアルに記載しているseed.isoを使った起動を行うこともできますが、Proxmoxには管理画面からCloud-Initが使用できるためこちらの記事を参考に作成させていただきました。">*2</a>しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fdisksystem%2Fitems%2Fdccaef2e0930dce4abbe" title="ProxmoxVEでAmazonLinuxを構築する - Qiita" 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://qiita.com/disksystem/items/dccaef2e0930dce4abbe">qiita.com</a></cite></p> <p>Cloud-Initで作成するユーザーに<a class="keyword" href="http://d.hatena.ne.jp/keyword/SSH">SSH</a>の公開鍵を設定することで<a class="keyword" href="http://d.hatena.ne.jp/keyword/SSH">SSH</a>ログインが可能になります。 弊社ではAnsibleを使ったOSのプロビジョニングを行っているため、Ansibleが実行できるよう<a class="keyword" href="http://d.hatena.ne.jp/keyword/SSH">SSH</a>公開鍵を設定および<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>を設定しました。 <figure class="figure-image figure-image-fotolife" title="Cloud-Init設定画面"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/doj_tomi/20210930/20210930120216.png" alt="f:id:doj_tomi:20210930120216p:plain" width="931" height="290" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Cloud-Init設定画面</figcaption></figure></p> <h1 id="Ansible-によるプロビジョニング">Ansible によるプロビジョニング</h1> <p>ローカル環境からプロビジョニングを行うための<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成を以下に記載します。 dockerコンテナよりAnsibleのplaybookを流すことができるように、Ansibleの構成ファイルとdocker-compose.ymlファイルを同じ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに設置しました。</p> <h2 id="ディレクトリ構成"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成</h2> <pre class="code" data-lang="" data-unlink>. ├── ansible │   ├── dev(インベントリファイル) │   ├── group_vars │   ├── roles(ロールの中身は省略) │   │   ├── common-systemd(OSの共通設定用Role) │   │   └── mysql(mysqlインストール用Role) │   └── site.yml(実行するplaybook。roleを記載) ├── docker-compose.yml └── images ├── Dockerfile ├── config └── id_rsa</pre> <h2 id="ansibledev">./ansible/dev</h2> <p>インベントリファイルにホスト名を記載しています。</p> <pre class="code" data-lang="" data-unlink>[dev:children] pve [pve] dev-pve-01-a</pre> <h2 id="imagesDockerFile">./images/DockerFile</h2> <p>Ansibleを実行するためのDocker Imageを作成します。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/ssh">ssh</a>クライアントとAnsibleをインストールし、ここでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>に設定した公開鍵のペアである<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C8%EB%CC%A9%B8%B0">秘密鍵</a><a href="#f-9d61d4fa" name="fn-9d61d4fa" title="Git等でコード管理している場合は、秘密鍵等の機密情報を直接アップロードしないよう注意が必要です。">*3</a>の格納を行います。</p> <pre class="code" data-lang="" data-unlink>FROM debian:10-slim WORKDIR /usr/local/src # ssh client &amp; Ansible install RUN apt update &amp;&amp; apt install openssh-server ansible -y &amp;&amp; mkdir &#34;/root/.ssh/&#34; COPY config &#34;/root/.ssh/&#34; COPY id_rsa &#34;/root/.ssh/&#34; RUN chown 600 /root/.ssh/id_rsa CMD [&#34;/bin/bash&#34;]</pre> <h2 id="docker-composeyml">./docker-compose.yml</h2> <p>Ansible<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リをコンテナ上でbind マウントさせることで、ローカル上でAnsibleのplaybookを更新してすぐに反映できるようにしております。</p> <p>またextra_hostsにインベントリファイルに記載した名前(OSのホスト名)と<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>に設定した<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>を記載します。 こうすることでコンテナの/etc/hostsにOSのホスト名と<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%A2%A5%C9%A5%EC%A5%B9">IPアドレス</a>が記載されるため、インベントリファイルに記載したホスト名に対してplaybookを流すことができるようにしました。</p> <pre class="code" data-lang="" data-unlink>version: &#39;3&#39; services: ansible: build: ./images/ tty: true working_dir: /usr/local/src volumes: - ./ansible:/usr/local/src extra_hosts: - &#34;dev-pve-01-a:192.168.1.103&#34;</pre> <p>あとはdocker compose コマンドよりAnsibleクライアントコンテナを起動させ、docker compose execよりansible-playbook コマンドを使用することで<a class="keyword" href="http://d.hatena.ne.jp/keyword/VM">VM</a>のプロビジョニングが実行されます。</p> <pre class="code" data-lang="" data-unlink>$ docker compose up -d $ docker compose exec ansible ansible-playbook -i dev site.yml PLAY [all] ************************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************* ok: [dev-pve-01-a] TASK [common-systemd : ホスト名を dev-pve-01-a に設定] ************************************************************************* ok: [dev-pve-01-a] TASK [common-systemd : 言語が日本語設定か確認] ************************************************************************************ ~~~~略~~~~~ TASK [mysql : rootの.my.cnfの編集] ***************************************************************************************** ok: [dev-pve-01-a] PLAY RECAP ************************************************************************************************************* dev-pve-01-a : ok=35 changed=0 unreachable=0 failed=0 </pre> <h1 id="おわりに">おわりに</h1> <p>Dockerコンテナによる開発環境が充実してきた昨今ではオンプレでのサーバー構築を行うことは少なくなってきてると思いますが、 オンプレサーバー機器を所有している場合、このようにEC2に構築した<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon">Amazon</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a> 2環境と同様の環境を用意することができます。</p> <p>正直一発目の投稿内容にしては需要が少ないであろうニッチな話題になってしまいましたが、オンプレ好きにとっては作っててすごく楽しかったです。</p> <h1 id="仲間を募集しております">仲間を募集しております</h1> <p>募集中の職種については以下を御覧ください。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fdesignone%2Fprojects" title="株式会社デザインワン・ジャパンの採用/求人一覧 - Wantedly" 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/designone/projects">www.wantedly.com</a></cite></p> <h1 id="参考URL">参考URL</h1> <ul> <li><p><a href="https://qiita.com/disksystem/items/dccaef2e0930dce4abbe">ProxmoxVE&#x3067;AmazonLinux&#x3092;&#x69CB;&#x7BC9;&#x3059;&#x308B; - Qiita</a></p></li> <li><p><a href="https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/amazon-linux-2-virtual-machine.html">Amazon Linux 2 &#x3092;&#x4EEE;&#x60F3;&#x30DE;&#x30B7;&#x30F3;&#x3068;&#x3057;&#x305F;&#x30AA;&#x30F3;&#x30D7;&#x30EC;&#x30DF;&#x30B9;&#x3067;&#x306E;&#x5B9F;&#x884C; - Amazon Elastic Compute Cloud</a></p></li> </ul> <div class="footnote"> <p class="footnote"><a href="#fn-70f86237" name="f-70f86237" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ESXiの場合、独自<a class="keyword" href="http://d.hatena.ne.jp/keyword/UNIX">UNIX</a>のコマンド体系だったため調査がしづらかった印象がありました。(あくまでも主観です)</span></p> <p class="footnote"><a href="#fn-8ef664f0" name="f-8ef664f0" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><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> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a> 2 の構築手順について<a href="https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/amazon-linux-2-virtual-machine.html">マニュアル</a>があり、マニュアルに記載しているseed.isoを使った起動を行うこともできますが、Proxmoxには管理画面からCloud-Initが使用できるためこちらの記事を参考に作成させていただきました。</span></p> <p class="footnote"><a href="#fn-9d61d4fa" name="f-9d61d4fa" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">Git等でコード管理している場合は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C8%EB%CC%A9%B8%B0">秘密鍵</a>等の機密情報を直接アップロードしないよう注意が必要です。</span></p> </div> doj_tomi