<![CDATA[クラウドとフロントエンドの未来を考えるBlog]]>http://github.com/dylang/node-rssGatsbyJSWed, 11 Oct 2023 21:25:02 GMT<![CDATA[University of London BSc Computer Science 1年目の指南書]]>https://whnyab.com/uol-freshman/https://whnyab.com/uol-freshman/Sun, 01 Oct 2023 22:00:00 GMT<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 557px; " > <a class="gatsby-resp-image-link" href="/static/2e1631b92dc4866e1deef6f5c9d670bd/30d00/title.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 63.51351351351351%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADnklEQVQ4yx3QaTPcBwCA8X9n+rLfoNPORCTGijMhVBxxZEQdLVFTPUK1lFbEkZHEkXWsax1ZFGWw2F3LrrVHV9YRLItlj7BIVNOm05m2U/Udnk69eN7+XjzCRwmBpMb5kxTlS2rCNe6kxZKVfpOMlBjSk6O5k3aTzJRoMpNvkJkcQXp8EJm3gvk8NYyCrCjufxmPuCQD67MJTv98hXArSkRc6PskRnjT2fwY3awKy8I8azYbNvsOWw4XdqebXbcLz+EB6hkVQ8ODyOWjTEyMMTkpRz46yI7VyL9/HyOEXw+hq6WahpoyKouykTaUIR/pY1arxrJgYW1jA7vThefoiJPXJyxZrRgsC+jNFrQGEyMTCvr7+3ixs8hffxwgiB+VI2soY7irFsnDfJ5K7jOtHMRo0LK6toLdscv+4QG//PYrb35/Q15BIbGJt0m8nUJcYhIRN2JISkxgUd3Nq3Ulwg+tVVR/8QF5qaGU5n9Mw4O79HWJ0cwo0Bt0LD1fZtW6xtLzJVZWl4mIjuU9r8uIAkK4EnQVUUAw3j5+JCXEYBmXICiHpDwpSMLr4kXSk2OpuZdNaVEOPw7IqK59TEeXFFmvjKc93aimJgmPisVbFIh/SNh5l0QB+PoH4XX5CqGhYQgr5mk+SQpFeOttIsMCqS/7jMqSu8hH+pFKW5lUTjKhmESjncZs0hEc5IfPpQsEB/gQGuhDeIgvkVdFxFz3JyZMhGB9pqWvuYI40TtkxvvT/aSY7sZyphVDzJv1bG7Z8Hj2OD075eWejYGaHIZqc5iQ5KJu/Qp917eY+0qw9N9jebAUQVadx6HHzVDHQ1qq8uipL6a/tRKjZozN9SX2950cvfSwsbmOc+Mntqab2ZpuYXe2Hae2FadGgksrwa1tYl/XgpAR8S6PCjNRjg1QmptCTeGHNFV9TX11GSNDvWi1U+j1GkymOQ53F7HPtrGtaWNrqhm7WsK2qh67SoxDLcataULoqcigIusa36X68WnMBb7PjmRY+gD1qIwF0wwO+zonx4ecnf3Dz7vz2JQNrCsascrFWMfqsI3Xsa0SsztVh0NZizDTko+u/RsMnYVoWnLpLU9joL4Q3XgPW6vzHO07eH18gMdmwjnXcd6Oph3HbDsuXTsv9FJ2VGLsimrcM/UIM5K8c9DYXYSpu4iF3mJmW/8fXsjqVBt2Qz9WRTObylr2TJ24jDI2lM245jpw6zvYM3aegy5NI0fmTv4DJNqntWq4NMsAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="title" title="title" src="/static/2e1631b92dc4866e1deef6f5c9d670bd/30d00/title.png" srcset="/static/2e1631b92dc4866e1deef6f5c9d670bd/12f09/title.png 148w, /static/2e1631b92dc4866e1deef6f5c9d670bd/e4a3f/title.png 295w, /static/2e1631b92dc4866e1deef6f5c9d670bd/30d00/title.png 557w" sizes="(max-width: 557px) 100vw, 557px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>UoL(University Of London) の BSc Computer Science に入学して 1 年が経ちました。 1 年目は UoL のシステムに慣れることが本筋の学習と同じくらい大事だったりします。 周りを見ていると、だいたいみなさん同じところでつまづいているように思えたので、1 年目の指南書を書いてみました。</p> <p>なお、入学までに取り組んだ内容については<a href="https://whnyab.com/uol/">こちら</a>にまとめています。</p> <h2>Disclaimer</h2> <hr> <ul> <li>この記事はあくまで個人の経験に基づいたものです。公式の情報は UoL が提供している情報を参照してください。</li> <li>本記事は PBA(Performance Based Admission)入学者を対象に書いています。Standard Entry で入学できた方は一部、参考にならない部分があるかもしれません。PBA と Standard Entry の違いについては<a href="https://whnyab.com/uol/">前回</a>の記事にまとめているので、そちらを参照してください。</li> </ul> <h2>UoL でのモジュールの進め方</h2> <hr> <p>UoL での学習はモジュールという単位で履修が可能です。モジュールは履修科目にあたります。 一つのモジュールは 22 週間で構成されており、4 月 ~ 9 月、10 月 ~ 3 月の半年間(1 セメスター)をかけて受講します。これらのモジュールはすべて<a href="https://www.coursera.org/">Coursera</a>プラットフォーム上で受講することになります。卒業には 22 モジュールと Final Project(卒業研究)の合格が必要です。</p> <p>モジュールの評価は主に以下の 3 つの要素で構成されています。(モジュールごとに比重は異なります)</p> <ol> <li>Coursera 上で行われる Assignment</li> <li>Mid-term Assignment</li> <li>Final Exam</li> </ol> <p>1 は Coursera 上で行われるミニテストのようなものです。モジュールにもよりますが、だいたい 2weeks 毎にテストが行われます。テストは複数回受けることができますが、締切を過ぎると受けることができなくなるので注意が必要です。モジュールにもよりますが評価全体に対する比重はそこまで大きくないです。</p> <p>2 はセメスターの中間で行われる課題です。レポート形式の課題が出されることが多いです。評価全体に対する比重は大体 20%~50%で、提出期限はおおよそ 1 ヶ月です。推奨される時間は 20 時間程度ですが、課題の内容によってはそれ以上かかることもあります。</p> <p>3 は多くのモジュールで最も比重が高いです。Final Exam は専用のアセスメントツールを用いてオンライン上で行われます。</p> <p>モジュールの合格には Mid-term と Final Exam の加重平均が 40%を超える必要があります。 また、Mid-term Assignment と Final Exam の両方で 35 点の足切り点が設けられています。</p> <p>1 セメスターで履修するモジュールは 4 つまでです。1 年間で最大 8 つのモジュールを履修することになります。</p> <h2>入学から受講開始までの流れ</h2> <hr> <h4><u> オリエンテーションを受ける</u></h4> <p>入学が決定すると、セメスターが開始される前に、Coursera 上でオリエンテーションを受講します。このオリエンテーションで UoL の基本的な事項について学びます。4~6 時間くらいで完走できます。</p> <h4><u> 受講登録を行う</u></h4> <p>入学を許可されると専用の学生ポータルのアカウントが発行されます。 このポータルサイトから履修登録(モジュールの登録)と学費の支払いを行います。 学費はモジュールごとの支払いになります。モジュールの登録を行わない場合、学費は発生しません。</p> <p>慣れるまでこのポータルサイトの使い方に戸惑うことが多いので、早めに登録を済ませておくと良いです。 なお、モジュール開始後も 14 日以内であればモジュールのキャンセルが可能です。本業との兼ね合いでどうしても履修登録をキャンセルしたい場合は、この期間内にキャンセルを行うようにしましょう。</p> <p>PBA で入学した場合、はじめのセメスターでは 2 科目しか履修することができません。「Introduction to Programming I」と「Discrete Mathematics」or「Computional Mathematics」を選択します。この 2 科目の履修登録を行うこと &#x26; Mid-term Assignment でそれぞれ 50 点以上を獲得することが次の科目を履修できる条件になります。この条件は必ず満たすようにしましょう。</p> <h2>注意点 / Tips</h2> <hr> <h4><u> RPL(Recognition of Prior Learning)を最大限活用する</u></h4> <p>UoL には RPL と呼ばれる事前学習プログラムが存在します。これは、他の大学で取得した単位や、UoL 指定の Certificate を UoL の単位として移管することができるプログラムです。RPL にはいくつかのメリットがあります</p> <ul> <li> <p>1 セメスター 4 モジュールの制限外であるため、通常のペースより早く単位を取得できる</p> </li> <li> <p>モジュールの費用がかからないため学費を安く抑えることができる</p> </li> <li> <p>Mid-term Assignment, Final Exam が免除されるため、単位取得にかける時間が減る</p> <p>2023.10 現在で BSc Computer Science では以下の科目が RPL に対応しています。</p> </li> <li> <p><a href="https://www.coursera.org/professional-certificates/google-it-support">Google IT Support Professional Certificate</a></p> </li> <li> <p><a href="https://www.coursera.org/professional-certificates/ibm-data-analyst">IBM Data Science Professional Certificate</a></p> </li> <li> <p><a href="https://www.coursera.org/professional-certificates/ibm-data-science">IBM AI Engineering Professional Certificate</a></p> </li> </ul> <p>Google IT Support Professional Certificate は 1 年次に受講する「How Computer Work」の受講が免除されます。PBA の場合、はじめのセメスターで 2 科目しか履修できないため、合わせてこのプログラムを受講することをおすすめします。</p> <p>IBM Data Science Professional Certificate、IBM AI Engineering Professional Certificate は 3 年次に受講する「Machine learning and neural networks」と「Data science」が免除されます。ただし、3 年次に選択するコースによっては、卒業要件にこの 2 科目が含まれていない場合があるため注意が必要です。3 年次に 「Data Science コース」、もしくは 「Machine Learning コース」を選択する場合は上記 2 科目を RPL として適用することができます。</p> <p>詳細は<a href="https://www.london.ac.uk/applications/how-apply/recognition-prior-learning/recognition-and-accreditation-prior-learning-3">こちら</a>を参照してください。</p> <h4><u> Progression Rule を理解する</u></h4> <p>見落とされがちですがとても重要なルールです。 学生はすべてのモジュールを自由に選択できるわけではなく、受講できるモジュールにはある程度の制限があります。これを Progression Rule と呼びます。このルールを理解するにはいくつかの前提を抑える必要があります。</p> <p>BSc Computer Science は 22 モジュールと卒業研究に合格することで卒業が可能です。 この 22 モジュールは以下のようにレベル分けされています。</p> <p>Level4: 8 modules <br/> Level5: 8 modules <br/> Level6: 6 modules <br/> Final Project <br/></p> <p>また、前述した通り、RPL を活用することで、Level4 に属するモジュールを 1 つ(How Computer Work)、Level6 に属するモジュールを 2 つ(Machine learning and neural networks, Data Science)免除することができます。</p> <p>Progression Rule では以下の事項が定められています。 以下に特に重要なルールを抜粋します。</p> <ol> <li>PBA 入学の場合、初めのセメスターでは Level4 にあたる「Introduction Programming I」と「Discrete Mathematics」or 「Computional Mathematics」のどちらかを履修する必要がある。PBA で入学した学生が、他の Level4 の科目を履修するには、この 2 科目を履修登録すること &#x26; Mid-term Assignment でそれぞれ 50 点以上を取得する必要がある。(前述した通り)</li> <li>正式な学生として認められるには、「Introduction Programming I」と「Discrete Mathematics」「Computional Mathematics」の 3 科目に合格する必要がある。</li> <li>Level5 の科目を受講するには、PBA の最初のセメスターで登録した科目への合格と「Introduction Programming II」の科目を受講登録する必要がある。</li> <li>Level6 の科目を受講するには、Level5 の科目を 3 科目以上合格している必要があり、かつ「Software Design and Development」「Object Oriented Programming」のうちのどちらかに合格している必要がある</li> <li>Final Project に参加するには、Level5 の科目の全てに合格している必要がある</li> </ol> <p>また、もう一つ理解すべき重要な事項として、<strong>前セメスターの合否が確定するのは次のセメスターの履修登録後になる</strong>点があります。 例えば、ルール 4 の「Level6 の科目を受講するには、Level5 の科目を 3 科目以上合格している必要がある」の条件を満たすためには、Level6 の受講を開始する 2 つ前のセメスターで Level5 の科目を 3 科目以上受講している必要があります。これは、次のセメスターの履修登録の段階で前セメスターで受験した Final Exam の結果がまだ確定していないためです。このルールは万が一単位を落としてしまった場合(再履修が必要になった場合)にも大きく影響します。</p> <p>例えば、2023.4~2023.9 月に履修した科目が不合格になった場合、その結果が確定されるのは 2023.10 の履修登録が終わった後なので、この科目を再履修するには、2024.4 のセメスター開始を待つ必要があります。次の Level への進級条件となっている科目が不合格になった場合、<strong>卒業までに 1 年間の遅れが生じることになります。</strong></p> <h4><u> どの順番でモジュールを履修するか</u></h4> <p>Progression Rule により進級の条件が細かく定められているため、最短で卒業するための順番はほぼ固定されます。最短卒業を目指さない場合でも、進級条件になっている科目(*)は優先的に取るようにしましょう。 以下、最短で卒業するための履修順序の例です。※ Machine Learning コースを選択した場合</p> <p>1st セメスター:</p> <ul> <li>Introduction Programming I (Level4) ※</li> <li>Discrete Mathematics (Level4) ※</li> <li>Google IT Support Professional Certificate (RPL)</li> </ul> <p>2nd セメスター:</p> <ul> <li>Introduction to Programming II (Level4) ※</li> <li>Computational Mathematics (Level4)</li> <li>Fundamentals of Computer Science (Level4)</li> <li>Algorithms and Data Structures I (Level4) ※</li> </ul> <p>3rd セメスター:</p> <ul> <li>Web Development (Level4)</li> <li>Object Oriented Programming (Level5) ※</li> <li>Algorithms and Data Structures II (Level5)</li> <li>Graphics Programming (Level5)</li> </ul> <p>4th セメスター:</p> <ul> <li>Software Design and Development (Level5) ※</li> <li>Databases, Networks and the Web (Level5)</li> <li>Programming with Data (Level5)</li> <li>Computer Security (Level5)</li> <li>IBM Data Science Professional Certificate (RPL)</li> <li>IBM AI Engineering Professional Certificate (RPL)</li> </ul> <p>5th セメスター:</p> <ul> <li>Agile Software Projects (Level5)</li> <li>Databases and advanced data techniques (Level6)</li> <li>Artificial intelligence (Level6)</li> <li>Natural language processing (Level6)</li> </ul> <p>6th セメスター:</p> <ul> <li>Intelligent signal processing (Level6)<br/> (このタイミングでは Level5 の科目全ての合格が確定していないため、Final Project に参加することはできない)</li> </ul> <p>7th セメスター:</p> <ul> <li>Final Project</li> </ul> <h4><u> PBA で入学した場合、最短でも卒業に 3.5 年かかる</u></h4> <p>Standard Entry の場合最短で 3 年での卒業が可能ですが、PBA 入学の場合 7th セメスター(3.5 年)を必要とします。</p> <h2>その他、個人的なアドバイス</h2> <hr> <h4><u> 難易度をばらつかせる</u></h4> <p>難易度の高いモジュールを一つのセメスターに詰め込むと、そのセメスターが非常に辛くなります。 非公式ではありますが、各モジュールの<a href="https://docs.google.com/spreadsheets/d/1vyRqV4BVxZx9nVJvLJtUYI19aAgChu-4aPunoVS7uAg/edit#gid=1889223376">難易度表</a>が有志により公開されています。 この難易度表は自分の肌感とも合致しているので、参考にしてみてください。 また、モジュールによっては特定のプログラミング言語が指定されている場合があります。1 つのセメスターで異なるプログラミング言語を学ぶことは学習コストが高くなるため、なるべく同じプログラミング言語のモジュールを同じセメスターに詰め込むようにしましょう。(大学側も同じセメスターに同じプログラミング言語のモジュールを履修することを推奨しています)</p> <p>※ 先ほどの履修順序の例では、可能な限り難易度が平準化されるようにしています。</p> <h4><u> モジュール開始前に過去問に目を通す</u></h4> <p>各モジュールの過去問は<a href="https://github.com/world-class/binary-assets/tree/master/modules">Github</a>上に公開されています。 過去問を活用することで各モジュールの最終到達点のイメージが明確になるので、モジュールの開始時から参考することをお勧めします。</p> <h4><u> 日本語の文献にも目を通す(英語苦手な人向け)</u></h4> <p>Final Exam は英語で行われるため、最終的には英語で学んだことを英語でアウトプットできる必要があります。 ただし、私のように英語が苦手な人は先にある程度日本語でインプットしておいた方が英語での理解力が上がると思います。</p> <p>以下、私が参考にした日本語の文献です。(Level4)</p> <p><a href="https://www.amazon.co.jp/dp/4274228207">マグロウヒル大学演習 離散数学(改訂 2 版): コンピュータサイエンスの基礎数学</a> Discrete Mathematics, Computational Mathematics, Fundamentals of Computer Science は大体この本一冊でカバーできます。離散数学の本はいくつか手を出しましたが、この本が一番良かったです。</p> <p><a href="https://www.amazon.co.jp/dp/4320125614">計算理論の基礎 [原著第 3 版] 1.オートマトンと言語</a><br/> <a href="https://www.amazon.co.jp/dp/4320125622">計算理論の基礎 [原著第 3 版] 2.計算可能性の理論</a> Fundamentals of Computer Science の主要テーマである「オートマトン」「チューリングマシン」の領域を上記の書籍より広くカバーしています。モジュールの内容より幾分か発展的な内容ではありますが、より理解が深まるのでお勧めです。</p> <h4><u> 学ぶ深さに濃淡をつける(すでにエンジニアのキャリアがある人向け)</u></h4> <p>すでに IT エンジニアとしてのキャリアがある人にとっては、Introduction Programming や Algorithm and Data Structure などのモジュールは少々物足りないかもしれません。すでに十分な知識がある分野については、1 ヶ月くらいですべてのカリキュラムを終えてしまって、他のモジュールに時間を割く方が良いかもしれません。自分の場合、Introduction ProgrammingI,II や Algorithm and Data Structure, Computional Mathematics に関しては特に新しく学ぶことはなく、退屈を通り越してイライラすることが多かったです。。一方で、Discrete Mathematics, Fundamentals of Computer Science は非常に興味深く、新しく学ぶ概念も多かったためこちらを重点的に学習しました。</p> <h4><u> Final Exam に使用されるツールに慣れる</u></h4> <p>Final Exam は自宅で受験することができます。この試験には UoL から提供される専用のツールを使用します。</p> <p>このツールは試験中に画面共有、Web カメラ、マイクによる監視を行います。また、試験中には他のアプリケーションを起動することができません。 カンニングや、許可されていない電子デバイスの使用、その他不正と疑われる動きが検知されると不正行為として Flag が立てられます。(Flag を建てられると即アウトではなく、実際に不正が行われたかどうかは後から人間の目によって確認されます) 試験は 4 時間と長時間になるため、トイレ休憩や飲食は許可されています。</p> <p>試験開始の 1 ヶ月前にはこのツールを使用した Mock Test を受けることができるので、試験開始前にこのツールに慣れておくと良いです。 ※ 2023.10 時点での情報です。今後変更される可能性があります。</p> <h2>Q&#x26;A</h2> <hr> <h4><u> Q. 働きながらでもやっていける?</u></h4> <p>A. 最短卒業を目指すのであればそれなりに大変。過去問をうまく活用する等効率よく学習することが重要になると思う。すでにエンジニアとしてのキャリアがある人は、少なくとも Level4 はそこまで大変ではないかもしれない。(数学は別)</p> <h4><u> Q. 英語が苦手でもやっていける?</u></h4> <p>A. 講義動画には英語のスクリプトが付いているので、最悪 DeepL に突っ込めば日本語でも読めるのでなんとでもなる。 ただし、Final Exam は Translater を使えないので、最終的には英語で学んだことを英語でアウトプットできる必要がある。 たまに問題文の英語が理解できないことがあった。導入過程を英語で説明しないといけない問題も出るので、そういった問題に対してはいくつか自分の中での定型文を作っておくと良い。</p> <p>他のメンバーと一緒に協力して作品を作るモジュールもあるので、そういった場合は英語でコミュニケーションを取る必要がある。</p> <p>Team Project があるモジュール</p> <ul> <li>Web Development (Level4)</li> <li>Agile Software Projects (Level5)</li> </ul> <p>※ 特に Agile Software Projects は口頭ベースの議論が多く日本人は苦労するようだ・・</p> <h4><u> Q. 学費はどれくらいかかる?</u></h4> <p>A.2023.10 に 4 モジュール履修した時は、634,700 円だった。卒業までに 350 万円くらいかかると思う。円安辛い・・</p> <h4><u> Q. 週何時間くらい勉強してる?</u></h4> <p>A.モジュール、人による。講義を聞くだけなら 1 モジュールあたり 2 時間/週くらい。加えて、Mid-term Assignment は 10~15 時間くらい。Final Exam は 1 モジュールあたり 20 時間くらいだった。おそらく Level5 になるともっと時間がかかると思う。</p> <h4><u> Q. 楽しい?</u></h4> <p>A.楽しい。自分は次のセメスターから Level5 の科目を履修できるので楽しみ。</p> <h4><u> Q. 大学に入って感じたデメリットは?</u></h4> <p>A. 英語や、業務に関係する勉強の時間が取りにくくなった。何をしてても頭の片隅に常に締め切りがちらつく。円安で学費が辛い。</p> <h2>まとめ</h2> <p>大体みなさん同じようなところでつまづいているように思えたので、1 年目の指南書を書いてみました。 この記事が少しでも参考になれば幸いです。</p> <p>何か質問があれば Twitter 等 で DM いただければお手伝いできると思います。 もし本記事の内容で間違いがあれば、ご連絡いただけると助かります。 > UoL 在籍のみなさん</p><![CDATA[働きながらUniversity of Londonでコンピュータサイエンスを学ぶ]]>https://whnyab.com/uol/https://whnyab.com/uol/Mon, 03 Oct 2022 22:00:00 GMT<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <a class="gatsby-resp-image-link" href="/static/3de2861e38663b9576539968d30745f6/8f0f9/top.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACqklEQVQozwGfAmD9AFFLTiswPE9RW0o8O0UxMGFicVdicmdbW3FgXGdZWFtGRIeDgW1kZDYnJ09FRVFJSEc/Pj84NDArKCgmJABLQkAxKylRSkpCLCpbR0Zxd4Z3hZeNeXWQgHR3c2t6Y11xd4SFiI9OOjhuaGl6eXpDMjI9Li9STUs0LisAU0pIUEhHWk1Kc2Veg3pxgXx3hoF+el5XiWNWe15RgmJXYWFniIuRb1JKZV1hfImYcl1YYUQ8aW5zUEpOAF9aWWxnY25lYX1vZpKFdn91Z4Z6b4d8dpaEfJqAdp6Eeo97b5SOh35wanpqZWpnZYVuZ39MOmZmbYeJjQA8PUFXWFxjS0NTNSuSiYSJh4OIhYSMjI6FhoiQkJCdm5euqJ+qopaKg3eJe3R0Y15pWFOFYlV3YVdjWVYAREZRWFlhYEhAZEY5urKshISEcnB0mpuinp2eoJ6fp6mupKKiwL64gXp4SDg0hH97gHd0ZllVbGBdYllXAEkuJF5ANVo6MHFQRcbAvYGDiY+PkoSAe25pYHpyaKKfmLCvrunn44Z/fTokIF5ganJucnFBMXtaT1laWwBwYFd5YlZXOC14W1HFw8OAgYVrbnQmLD48Q1goLkBMTE65t7X+/fqYlJU4IBtcTU1bUVRmQjeFW0tdW10AQkNBYV9eLxwZiHFmvL28enl6ZWlxW2JybnSDXGN1T1Zjg4WH3NzXp6WlLxsZZkk3UDsxKyEhWTsya0xDABobH1tUUzkgGZ2HfbS2tnx8gF5lc2BncXJ3f3Z/iU1SV4iKjObk4Lm1tTYpKD86OTo5OxodJRURFWtiSQAyNjlgVlYyGBKqnZR7fX5cW2BATWEhJCcfHh8rLC8rLjKMjpH49vG8u7ouJigaGyAbGyAPERUZFh92cEfgjghg4kovZQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="top" title="top" src="/static/3de2861e38663b9576539968d30745f6/fcda8/top.png" srcset="/static/3de2861e38663b9576539968d30745f6/12f09/top.png 148w, /static/3de2861e38663b9576539968d30745f6/e4a3f/top.png 295w, /static/3de2861e38663b9576539968d30745f6/fcda8/top.png 590w, /static/3de2861e38663b9576539968d30745f6/efc66/top.png 885w, /static/3de2861e38663b9576539968d30745f6/8f0f9/top.png 1045w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>10 月から University of London の Bachelor of Science in Computer Science programme にオンラインで入学することになりました。目指した経緯と入学までにやったことを書き綴っています。</p> <p>思いつきで書き殴った文章なので有意義なことは書いていません。</p> <p><del>2023.10 追記</del> 入学から 1 年が経過したので、以下の記事を追加しました。 <a href="https://whnyab.com/uol-freshman/">University of London BSc Computer Science 1 年目の指南書</a></p> <h2>なぜコンピューターサイエンスを学ぼうと思ったか</h2> <ul> <li>流行のフレームワークやライブラリを追っかけるようなことばかりやっていて本質的な技術力が伸びていないように感じたから</li> <li>ある程度実務経験を積んだ上で改めて CS を体系的に学ぶことでエンジニアの自力を伸ばせると期待しているから</li> <li><strong>単純にやってみたかったから</strong></li> </ul> <h2>なぜ修士ではなく学士?</h2> <p>自分の場合は研究よりも CS を体系的に学んでみたいというモチベーションが強かったので修士ではなく学士を選択しました。</p> <p>一応、修士入学の可能性も考慮して研究計画を作ってみたりもしたのですが特に研究したいテーマも無かったので薄っぺらい内容しか書けませんでした。(やるとしても Bachelar 取ってから)</p> <h2>なぜ University of London を選んだか</h2> <p>大学は以下の観点で選びました。</p> <ul> <li>CS コースがあること</li> <li>完全オンラインで履修できること</li> <li>どうせやるなら英語で学びたかった</li> </ul> <p>加えてイギリスの大学は 1 年目から専門科目を学ぶことができて最短で 3 年で卒業できることから最終的に UoL を選択しました。</p> <p>もう一つの候補としてアメリカの <a href="https://www.uopeople.edu">University of the People</a> がありました。こちらは卒業まで最短でも 4 年かかること、専門科目以外の科目も履修する必要があることから University of the People は選択しませんでした。</p> <p>余談ですが、この University Of the People は UoL と比較して学費が圧倒的に安いのが特徴です。元々は貧困国の人たちにも高等教育を提供する目的で提供されているカリキュラムなので、ほぼ無償に近い金額で CS の学士を取得することができます。また、英語力が requirement に達していない場合でも、9 週間の語学カリキュラムを受講して Final Exam をパスすることで入学が許可されます。日本ではほとんど知名度がありませんが、経済的な理由で大学に進学できない人やエンジニアに Job チェンジしたい人たちにとって有力な選択肢になり得ると思いました。</p> <div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 56.2111801242236%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem" > <iframe src="https://www.youtube.com/embed/6kH-uYwt0qs" title="シャイ・レシェフ: 超・低コストでとれる大学の学位" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" style=" position: absolute; top: 0; left: 0; width: 100%; height: 100%; "></iframe> </div> <h2>出願までにやったこと</h2> <ul> <li>英語</li> <li>必要書類の準備</li> </ul> <h3>英語</h3> <p>自分の場合、英語の requirement を突破するための準備が 99%を占めていました。 University of the People と異なり UoL の入学には英語力の証明が必要となるので、英語に自信がない人はある程度準備が必要になります。</p> <p>とはいえ、Master コースと比較すると求められる基準はそこまで高くないので地道に準備をすれば達成可能なレベルだと思います。</p> <p><a href="https://www.london.ac.uk/applications/how-apply/english-requirements#proficiency-tests-2564">English requirements</a> <br/>*2022.10 時点で TOEFL:87, IELTS: overall 6, with 5.5 in each sub-test</p> <p>イギリスの大学だからなのか TOEFL より IELTS の方が明らかに基準が低いように思えたので IELTS に絞って対策を行いました。</p> <h3>必要書類の準備</h3> <p>出願にあたっては身分証明書、英語の卒業証明書、CV(英文履歴書)、志望動機、英語力の証明書などが必要になります。</p> <p><strong>身分証明書:</strong> パスポート等</p> <p><strong>志望動機:</strong> 250words でよかったのでサクッと書いてネイティブチェックだけ通しました。正直そんなに重要視されてないように思います。</p> <p><strong>英語の卒業証明書:</strong> 英語の卒業証明書を発行してくれない場合は専門の翻訳業者に依頼する必要があります。その場合、成績証明書の取り寄せから翻訳完了まで 2 週間以上かかる可能性があるので卒業証明書に関しては特に早めに準備しておいた方が良いです。</p> <p><strong>英文履歴書:</strong> いわゆる CV というやつです。ネットには CV のテンプレートや書き方のノウハウが転がっていますが、個人的には下手に自分で書くよりお金払って専門の業者にお願いした方が良いです。</p> <p>大体 1 万円~2 万円くらい払えばかなり良いクオリティで仕上げてくれます。後述する Performance Based 選考の場合 CV は重要な選考資料になるため、よほど CV を書くのに慣れている、もしくは過去に作成したものがあるといった方以外はプロにお任せしましょう。(日本語の履歴書は自分で書く必要があります)</p> <h2>選考基準について</h2> <p>いわゆる学力試験や面接みたいなものはありません。2022.10 現在、以下の 2 種類の採点基準があります。</p> <ul> <li><strong>Standard Entry:</strong> 過去に卒業した成績証明書の成績から判断</li> <li><strong>Performance Based:</strong> IT 業界における過去の職務実績で判断</li> </ul> <p>どちらかの採点基準で合格をもらえれば晴れて入学が許可されます。自分の場合は Standard Entry では不合格でしたが Performance Based で合格することができました。過去の成績(特に数学)に自信がない方は CV に自分の実績を証明できる根拠をしっかり書き込みましょう。自分の場合は過去に執筆した書籍や、AWS 時代のパブリックスピーキングの Youtube リンクなどを記載しました。</p> <p>なお、Standard Entry で不合格になった場合は、初年度取得できる単位数が制限されてしまうため可能な限り Standard Entry で合格した方が良いです。(カリキュラムに関しては今後変更があるかもしれません)</p> <h2>実際働きながらやっていけるの?</h2> <p>まだカリキュラムが始まっていなのでなんとも言えません。フルで単位を取得しようとした場合、推奨学習時間が 28h/week となっているのでかなりタフであることは間違いなさそうです。当然授業は全て英語で行われるので、英語が苦手な人は 1~2 割増しくらいの時間を覚悟した方が良さそうです。</p><![CDATA[[祝GA!] Aurora Serverless v2 の特徴まとめ]]>https://whnyab.com/auroraserverless-v2/https://whnyab.com/auroraserverless-v2/Sun, 24 Apr 2022 03:48:03 GMT<p>2022.4.22 に Aurora Serverless v2 が GA された。 以前から気になっていた機能なので特徴について気になった点をまとめてみた。</p> <h3>Disclaimer</h3> <ul> <li> <p>2022.4.22 時点での情報です。最新の情報は<a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.html">公式ドキュメント</a>を参照してください。</p> </li> <li> <p>本記事では網羅性はあまり意識していません。繰り返しになりますが、詳細は<a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.html">公式ドキュメント</a>を参照してください。</p> </li> </ul> <h3>Aurora Serverless v2 の主な機能概要</h3> <ol> <li>負荷に応じてインスタンスタイプが自動的にスケールアップ</li> <li>Provisioned インスタンスと Serverless インスタンスを混在して一つのクラスタに配置できる</li> <li>v1 と比較してよりきめ細やかな Option が指定可能(インスタンスタイプ、Global Database の指定等)</li> </ol> <p>※ Disclaimer でも触れた通り網羅性は意識していません</p> <h3>1. 負荷に応じてインスタンスタイプが自動的にスケールアップ</h3> <ul> <li>0.5 ACU 刻みで MIN ,MAX キャパシティの指定が可能</li> <li><b>スケールアップ/ダウン時のユーザ影響なし</b></li> <li>インスタンスの入れ替わりが起こるのではなく、実際に稼働しているインスタンスがトランザクションを受け付けながらスケールアップが行われる(in place scaling) <ul> <li>新しくインスタンスが追加されるわけではないため、キャッシュがウォーミングされていないといった状態を回避しやすい</li> </ul> </li> <li>スケールアップのスピード(scaling rate)はそのタイミングの ACU に依存する。大きい ACU であればあるほどジャンプできる ACU 数が増える</li> <li>Buffer Pool などのパラメータも動的に変更してくれる <ul> <li>mysql の max_connection は MAX で設定される ACU の max_connection が設定される</li> </ul> </li> </ul> <p>Aurora のアーキテクチャの特徴として、コンピュート層とストレージ層が分離されているためこういったスケール戦略が可能らしい ↓</p> <p><figure class="gatsby-resp-image-figure" style=""> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <a class="gatsby-resp-image-link" href="/static/daba70ab3d5ce702e3fcbd6b263b952c/eb2af/architecture.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 50.67567567567568%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAACTElEQVQoz3WSO2/TUBiGncZ2msSJHRLHSejFuTi3JkFxnKC0tCVVQ3NpaYOqgBBIMCDBysDEAkJioBKXjQWJia0LKkxMwD9h4Qd0QAwPsiN1AYZHOkfn03ue831HME2T6XTKaDik3+8zmUzo9XocHBywv7/v7VdXVxmPx956NByxt7dHpVJBEIS/CYgy6ZhBUtU9DC1JKmawqC8QV88RVhRCoZCHqqnoho4Wi6FEFAKhAHN+P1JAQg4FEHw+hHA4TMow0HWdTOY86XT63zcLAqmEQa/TIxpSsBbzjNZHhALzXKx32OleISCJCLIss7JSw7Ztmk2XJtVqFcuyvGeVSiUC8rwXeNM+5NfRKYNKj8f9R/w+OqWWLnJ89wM/n/7AUFW3TiASiRCLxdA0zcO1VVWVZDLpIfpFL3CruM7Xh59p5YpMmn2O778nHVN4sHmbV9NnaBF5Flgul3Ech0aj4Znadgu7aXuWiUSCYDBEUPbTtSrY2ctsLOyytjCgaWyzsXyVTmaHlj6glMzNAlVVI5VKEY/HyWQyZ2au7VkPfQIvrk95fuuEJ2vfeXntC+8OP/F6cMKb7Y+87X9jMzt2a30sLS2Ry+W8fhUKBfL5POVyhaJVpFaroSgKPt8cN4YWk+kW2Y6DM+pyZ2rTbaTZ3iiwe29Io1aYGbpDcFoOnXabTqeD47TJmiaWVcRcNj3DOZ9A1lymWm/QqLutabN2qUfLblFv2GRLF0jp8VmgKIq405ZECUmSkCX5v1/HJRqNeihKmGhUJRgMnp3/AWtDIoU3Zy+JAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="architecture.png" title="architecture.png" src="/static/daba70ab3d5ce702e3fcbd6b263b952c/fcda8/architecture.png" srcset="/static/daba70ab3d5ce702e3fcbd6b263b952c/12f09/architecture.png 148w, /static/daba70ab3d5ce702e3fcbd6b263b952c/e4a3f/architecture.png 295w, /static/daba70ab3d5ce702e3fcbd6b263b952c/fcda8/architecture.png 590w, /static/daba70ab3d5ce702e3fcbd6b263b952c/efc66/architecture.png 885w, /static/daba70ab3d5ce702e3fcbd6b263b952c/eb2af/architecture.png 954w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span> <figcaption class="gatsby-resp-image-figcaption">architecture.png</figcaption> </figure></p> <h3>2. Provisioned インスタンスと Serverless インスタンスを混在して一つのクラスタに配置できる</h3> <ul> <li>v2 への移行には新たにクラスタを起動する必要はなく、既存のクラスタに Serverless v2 インスタンスを混在することができる <ul> <li>※ ただし Aurora の version は serverless v2 が対応しているものを使用する必要がある</li> </ul> </li> <li>段階的な移行の手段として、v2 インスタンを徐々に増やしていくといった方式が可能</li> <li>Custom endpoint の機能を用いて特定の Reader endpoint にだけ Serverless v2 を割り当てるといった運用も可能</li> </ul> <h3>3. v1 と比較してよりきめ細やかな Option が指定可能(インスタンスタイプ、Global Database の指定等)</h3> <ul> <li>v1 と異なり Read Replica や Global Database にも対応</li> <li>v1 では ACU のスケールは 2 倍ずつしか設定できなかったが、v2 ではきめ細かい ACU サイズが指定可能</li> <li>Global Database 内の設定でも Provisioned と Serverless を混在させることが可能 <ul> <li>フェールオーバー先のリージョンに Serverless インスタンスを配置することで、トータルのコストを抑えることができる</li> </ul> </li> </ul> <h3>Aurora Serverless v2 の使い所</h3> <ul> <li> <p>トラフィックが予想できないようなユースケース</p> </li> <li> <p>特定の時間帯のみ起動するアプリケーション</p> </li> <li> <p>Writer だけ、もしくは Reader だけ動的にスケールアップ/ダウンさせたい場合</p> <ul> <li>分析用クエリだけは Aurora Serverless v2 を使用する等</li> </ul> </li> <li> <p>逆に安定してトラフィックが予想できる場合は Provisioned インスタンスを用いる</p> </li> </ul> <p>refer to: <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.html#aurora-serverless-v2.use-cases">Aurora Serverless v2 use cases</a></p> <h3>Aurora Serverless v2 使用時の注意点</h3> <h4>対応 version が限られている</h4> <ul> <li>MySQL <ul> <li>Aurora MySQL 3.03.0 or higher</li> <li>MySQL 8.0</li> </ul> </li> <li>PostgreSQL <ul> <li>Aurora PostgreSQL 13.6.0 以上</li> <li>PostgreSQL 13</li> </ul> </li> </ul> <p>refer to: <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.upgrade.html#serverless-v2-upgrade-paths-ams">Upgrade paths for MySQL-compatible clusters to use Aurora Serverless v2</a></p> <h4>ユーザ利用領域以外の ACU も考慮が必要</h4> <ul> <li>0.5ACU から設定可能だが Engine や Tools が使用する ACU についても考慮する必要あり</li> <li>例えば Performance Insights を使用した場合最低でも 2ACU 使用されるため、ACU の minimum はそれよりいくつか大きいサイズを指定する必要がある</li> </ul> <p>refer to: <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.setting-capacity.html#aurora-serverless-v2.min_capacity_considerations">Choosing the minimum Aurora Serverless v2 capacity setting for a cluster</a></p> <h3>Aurora serverless v1 と v2 との比較(一部抜粋)</h3> <p>詳しくは以下リンクを参照</p> <p>refer to: <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.upgrade.html#aurora-serverless.comparison">Comparison of Aurora Serverless v2 and Aurora Serverless v1</a></p> <table> <thead> <tr> <th>Feature</th> <th>Aurora serverless v2</th> <th>Aurora serverless v1</th> </tr> </thead> <tbody> <tr> <td>最小 ACU(MySQL)</td> <td>0.5</td> <td>停止状態: 0<br>起動状態: 1</td> </tr> <tr> <td>最小 ACU(PostgreSQL)</td> <td>0.5</td> <td>停止状態: 0<br>起動状態: 1</td> </tr> <tr> <td>最大 ACU(MySQL)</td> <td>128</td> <td>256</td> </tr> <tr> <td>最大 ACU(PostgreSQL)</td> <td>128</td> <td>384</td> </tr> <tr> <td>フェールオーバー</td> <td>Yes</td> <td>Best effort</td> </tr> <tr> <td>グローバルデータベース</td> <td>Yes</td> <td>No</td> </tr> <tr> <td>クラスタの停止</td> <td>手動停止が必要</td> <td>自動で停止するオプションも可能</td> </tr> <tr> <td>バッファキャッシュ</td> <td>動的にバッファサイズが変化</td> <td>サイズ変更後にキャッシュの rewarm が発生してしまう</td> </tr> <tr> <td>価格(ACU Hour ・Tokyo resion)</td> <td>$0.2</td> <td>$0.1</td> </tr> <tr> <td>Data API</td> <td>非対応 😢</td> <td>対応</td> </tr> <tr> <td>Query editor</td> <td>非対応 😢</td> <td>対応</td> </tr> <tr> <td>Performance Insights</td> <td>対応</td> <td>非対応</td> </tr> <tr> <td>RDS Proxy</td> <td>対応</td> <td>非対応</td> </tr> </tbody> </table> <ul> <li>v1 は開発用、v2 は Production ready</li> <li>v1 が廃止されるわけでは無いらしい</li> <li>単価は v2 の方が高く見えるが、v1 と比較して ACU サイズのオプションが多いため、結果的にコスト安になるケースが多いと思われる</li> </ul> <h3>所感</h3> <ul> <li>既存の Aurora の機能がほぼ使えるので Production でも十分に使用できそう。</li> <li>Data API や Query editor など個人的に便利だった機能がなくなっているのは残念。(Amplify の RDS との Integration が v1 の DataAPI を前提としていたので今後の動向が気になる)</li> <li>v1 ではスケール時にクエリリクエストが待たされてしまう(connection が切れるわけではない)点が最大の Blocker だったと思うので、ユーザ影響なしにスケールができるのはすごい(語彙力)</li> <li>個人的には今年最大のアップデート</li> </ul> <p>社内でも段階的に使用できないか検証していきたい。</p><![CDATA[AWS Japanを退職しました]]>https://whnyab.com/bye_aws/https://whnyab.com/bye_aws/Fri, 01 Apr 2022 22:12:03 GMT<p>3 年弱勤めた AWS を 2022 年 2 月付けで退職しました。</p> <p>退職エントリを書くつもりはなかったのですが、自分にとって特に思い入れの強い会社だったので少しだけ書こうと思います。</p> <h3>あなたは誰?</h3> <p>主に AWS、フロントエンドを主軸にソフトウェア開発をしています。</p> <h3>AWS では何をやっていたの?</h3> <p>Prototyping Engineer (旧 Prototyping Specialist Solution Architect) というロールでお客様のクラウド導入支援を行なっていました。特に AWS 上で新たにサービスを立ち上げるお客様に対してアプリケーションのプロトタイピングを構築する部隊に所属していました。また、<a href="https://aws.amazon.com/jp/amplify/">Amplify</a>という AWS が提供する mBaaS のエバンジェリスト的な活動もやっていました。</p> <h3>なんで辞めたの?</h3> <p>会社を辞めた理由は 100%ポジティブなものです。ネガティブな要素は思いつきませんでした。それくらい素晴らしい会社でした。AWS を卒業される方は皆口を揃えて同じことお仰られますが、自分も例に洩れずです。</p> <p>じゃあなんで辞めたかというと、プロダクト開発に戻りたい気持ちが強くなったからです。</p> <p>先程も述べた通り、Prototyping Engineer は短期間でお客様にプロトタイピングを提供することに特化したロールです。お客様ごとに異なる制約(ビジネス上の制約、組織の制約、エンジニアのスキルセットの制約..etc)を考慮して最適なアーキテクチャを提案し、短期間で最低限動くものを提供するという経験は間違いなく一生の宝になるくらい素晴らしいものでした。</p> <p>業種、業態によりエンジニアのカルチャーや制約が本当に多種多様で、わずか数年という短期間で十社以上のお客様に対しプロトタイピングプログラムを提供できたことはエンジニアキャリアを数倍のスピード感で駆け抜けたような爽快感がありました。</p> <p>一方で、Prototyping Engineer というロールは、長期的にプロダクトに対しオーナーシップを持つことができないという歯痒さもありましました。 お客様と一緒にプロトタイピングを組み上げる期間はどんなに長くても 3 ヶ月程度であり、それ以降はお客様がオーナーシップを持ってプロダクトを育てていくことになります。我々の役割は、お客様が自走できるよう、ソフトウェアの雛形を作成し、その知見をお伝えをするところまでであり、それ以降の成果物は自分達の手を離れることになります。様々なお客様のワークロードに関与できることができる一方、プロダクトの最後までオーナーシップを持つことができないというのはどこか物足りなさ(寂しさ?)を感じるようになりました。</p> <h3>SA を知ったきっかけ</h3> <p>そもそも自分が Solution Architect(以下、SA)というロールに興味を持った経緯についても触れたいと思います。 SA というロールを知ったのは、2018 年 10 月にオープンした<a href="https://aws.amazon.com/jp/startups/lofts/tokyo-loft/?events-loft-tokyo-cards.sort-by=item.additionalFields.startDateOrder&#x26;events-loft-tokyo-cards.sort-order=desc&#x26;awsf.event-type=*all&#x26;awsf.event-date=event-date%23all&#x26;awsf.event-location=*all&#x26;awsf.level=*all&#x26;awsf.event-category=*all&#x26;awsf.tech-category=*all&#x26;awsf.industry=*all&#x26;awsf.event-audience=*all">AWS Startup Loft Tokyo</a>でした。</p> <p>ここは、AWS が提供するコワーキングスペースで AWS アカウントを持っているエンジニアの方であれば誰でも利用できるという場所です。(※2022.4 現在、COVID-19 の影響で一時的に閉館しています) Loft には「Ask An Expert」という無料で SA に相談ができるブースがあるのですが、ここにいた SA の方々が本当にすごかったんです。AWS に関する質問であればどんなトピックに対しても打ち返してくる。あまりになんでも答えてくれるので、あえて、Redshift Spectrum と Amplify、ECS という全く異なる分野の質問を一度に相談したことがあるのですが、どのトピックに関しても涼しい顔で的確なアドバイスをくれたのです。当時 AWS エンジニア歴 2 年目くらいの僕にとっては衝撃的な体験でした。</p> <p>興奮冷めやまぬうちに Twitter で SA ロールの Hiring 説明会に関するツイートが流れてきたので、迷わず応募し、なんやかんやあって Associate Solution Architect としてオファーをいただきました。</p> <p>僕の SA としての理想像はあの Loft での体験が原点になっていた気がします。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 60.13513513513513%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAADZklEQVQozx3O20+bBQBA8e9JjdEpDtgmCxtIgTLqCt0o5bbRMgalhZVCtdBSbr1zZ4BMrsLoCpSCBIFBqCQsuEliYVxkxkHE6JwJIzpN3DKiRpM9+Oj7Me4fOL8jNJg0eFrLGGoy0O3S0VyRS7spG09dAUujTrYCPWwGrjHVbaLVdBH9hXi06bGoFSKSY8NptxZjyE+lWPkudp0CoV4rxaU9Q0NJCi69HGuRnLbKPMrVKZTmSLFoZPS5rzDdV8nCDQeLXgdd1jzSJBFkJMdQkCmhTJNBpiyOyJPhCN6abOY78phpzWWiPgdf3SUGbCoG3AV86NYzNNjNe3oNR14SOBn6MgppNCmJp5GJI4iPPsaxo69z/OhriCLDeCciFMGSEUf1xRjslxJoKJRRr0ulwXiJxppSnLYKHvz6FM+wB1FUNJdVakSR4Zw68SYS0duYNXKMeSlUaNNQycVE/R+8EHuC7LgQzKpEfO0WPG21dF1143LVcV4moXnYR0NvP2djxRhLrURFhBEe8uqLqxx5PNWFaeiUSSSLI4k8/haCOOwIipgQShQiWswabAYVgw1lfHz9AzpsOtpqtQw49FQq40iXnkJ8Opz89ARaTNl0VuUy12Xk/mwL2XLJC0RY7K9mZaSGDb+djXEHwRELX/nMbHoMHCzWEfRWsPOJk/vzrezf6ebJ3QF2F9rYW2jl0XIH25Mu7k23kJUkIvSNVxC+m2nix08b+XbGxc9LTRx+0cnuQiPzg1X8e3ibZ3tTPF4f5afgAI9XP+Jmn4WdQAf7n/dwd7KJ9TEr66O1BK6q8VqVCKu9Rr4et7A1YmK2rZBbQ1XsLHbww50eft/x8/ybcf7e9bM918rzvXH+OVjgz4dzrE42MdtVyVLv+2z7LOzPVrI/50JY6y7ly2EjW14jzSXnKVKIqMiV4LxyjvbyTPrtl5nqNLA+6eS3jUF+2fCwMuZi6bqV5SErgTY1y106gv061rw2hLVrWnZ9eg6mDYw5sjAqz1BbIMWmTaa2IAlzroQyVQJlOYm4ilMJ9JoJjtpZ8zvYmnDydNnN4Wd1PJwyc8/vRHgwYeDRlJ5ni+XcbFRiyIrHqj6Lu0iGq+gcVk0ylflSqvKluHWp3PbUsDnuZN1vJzhUzpNbDv5aqeOPZQffT9v5D15YF6SF4LElAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="title" title="title" src="/static/6b187ce39a9c14bb6dded4d695ec2fdc/fcda8/title.png" srcset="/static/6b187ce39a9c14bb6dded4d695ec2fdc/12f09/title.png 148w, /static/6b187ce39a9c14bb6dded4d695ec2fdc/e4a3f/title.png 295w, /static/6b187ce39a9c14bb6dded4d695ec2fdc/fcda8/title.png 590w, /static/6b187ce39a9c14bb6dded4d695ec2fdc/efc66/title.png 885w, /static/6b187ce39a9c14bb6dded4d695ec2fdc/c83ae/title.png 1180w, /static/6b187ce39a9c14bb6dded4d695ec2fdc/e9fa0/title.png 2292w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <div style="text-align: center; display: flex; align-content: flex-end; justify-content: space-around;"> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">AWSソリューションアーキテクトのお仕事をご紹介するWebセミナーを企画しています。ちょっとでも興味のある方はカモン!<a href="https://t.co/1QtqF8ejJU">https://t.co/1QtqF8ejJU</a></p>&mdash; Masato Kobayashi (@maccho_j) <a href="https://twitter.com/maccho_j/status/1063249673828200448?ref_src=twsrc%5Etfw">November 16, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </div> <h3>AWS で得たこと</h3> <p>チープな回答になりますが、多分この会社に入らなければ一生出会わなかったであろう優秀な人たちと一緒に仕事ができたことでしょうか。</p> <p>AWS に入社しなければ井の中の蛙のままエンジニア人生を終えていたかもしれませんし、5 年後、10 年後に彼らのような素晴らしいエンジニアになるためにはどうすれば良いかを常に考えながら仕事ができるようになったことは、自分のエンジニアキャリアを何ステップも前進させてくれたように思います。</p> <h3>次何やるの?</h3> <p>とある Fintech 企業にて Software Engineer として働きます。 その他にも色々とやりたいことがあるので、それに向けて準備を進めていこうと思っています。</p> <h3>最後に</h3> <p>だいたい自分は 3 年くらいで転職を繰り返していて退職に対してはそこまで抵抗がないタイプの人間だったのですが、AWS の退職は最後まで悩みました。 それくらい Amazon のカルチャーが自分に fit していて今回の退職を後悔することもあるかもしれないです。</p> <p>それでも会社を去る決意ができたのは、同僚達のような素晴らしいエンジニアとして成長するために AWS を離れて再びプロダクト開発の道に戻ることが自分にとって長期的に頑張れる気がしたからです。先にも述べた通り、AWS という環境で得たものは優秀な同僚との出会いだなとつくづく感じます。</p> <p>将来、やりたい事の巡り合わせと Amazon という環境が一致したときに再びオファーレターを貰えるくらいにエンジニアとして腕を磨いていきたいと思います。</p> <p><b>And that is why it is always Day1!</b></p><![CDATA[AWS Amplify × Next.js で Server Side RenderingのデプロイおよびCI/CD環境を構築する]]>https://whnyab.com/amplify-ssr-cicd/https://whnyab.com/amplify-ssr-cicd/Wed, 19 May 2021 13:46:03 GMT<p>本記事では、AWS Amplify と Next.js を使って SSR 構成のデプロイ、および CI/CD の構築について考えていきたいと思います。</p> <p><strong>※2021/05/19 更新 Amplify Console が SSR ホスティングをサポートしたので記事の内容を更新しました</strong></p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Amplify ConsoleがNext.jsで作成されたアプリのSSRに対応しました🎉<br>去年9月にAmplify LibrariesがNext.js/Nuxt.jsのSSR対応したので、SSRがAmplifyで完結できるようになります!<a href="https://t.co/igYF9Vz61a">https://t.co/igYF9Vz61a</a><a href="https://twitter.com/hashtag/AWSAmplifyJP?src=hash&amp;ref_src=twsrc%5Etfw">#AWSAmplifyJP</a></p>&mdash; Jaga@AWS Amplify (@jagaimogmog) <a href="https://twitter.com/jagaimogmog/status/1394804794833788930?ref_src=twsrc%5Etfw">May 18, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <p>(参考) <a href="https://aws.amazon.com/jp/blogs/mobile/host-a-next-js-ssr-app-with-real-time-data-on-aws-amplify/">https://aws.amazon.com/jp/blogs/mobile/host-a-next-js-ssr-app-with-real-time-data-on-aws-amplify/</a></p> <h1><strong>はじめに</strong></h1> <p>AWS をある程度触ったことがあり、主要なサービスについては最低限理解されている前提で進めます。AWS を全く触ったことない方は、AWS 上で SSR 構築する際の課題や方針などについてなんとなく理解できれば良いなくらいの期待値で読んでいただけると幸いです。</p> <h1><strong>SPA/SSR のおさらい</strong></h1> <p>既に SPA,SSR の技術に精通されている方は、本章を読み飛ばしていただいても構いません。</p> <h4>SPA(Single Page Application)</h4> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.91891891891892%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAACOElEQVQoz02S204aURSGN6NVpFgEFUGbVmvREQY5CsjZMsMAggfOeAKj1sZUmzb2kNReedfEqz5En6DP9zWDvejFn7X2TtZa35/8wlM+ZXX7DKO+Lg3w1i6Qq2csaifItXMW1FOCrY9MpQ4RyyqSUkSsFjB5VSSfxohSHP4Z1eQrIqZTTRzJR82m28xlOzgzbaZTLZzpBr++bPLnZ4bfd+t8HcjMRFUCeh1XsoI9WuRZWB3KFirgiLxBGNesG7uMh6qIlQLCs4WQVYSvxNh6kXgugzeeJZzOsxTJ4lEP+HH/gCtWwx4sI5ZSSJ4ckldjJpxHCJ9OtP2eWOcag9ZYbInsYFJKCJ/GTFXBsa9g3/NhLftxZQrIeg3X0Qfcl3fYTm6xda+Z2qzg2CwjhLeIsndJqHHFVKKOyaczFtxmxF9G8utMtvy4+xs4jwPYa0EkWUeEVET3G08G98zePDB2+H3oSET2HwlNio5kEMkaYtWwa7zLjAZ0xqM7SOstxpQGzuU6trVtnsbLmAIlxGKa+cQuFr+OWNMQkT2E0Rhk7nyPl+oxtkSd0UAFc6iGOVzFHNMYj6pYEjoTyRLmeImJRAVLqoM102My28Oa7mJNd7AkW4+Wp5NNcv1bKu/uyA8+E25e4d25wLf7luTBJzY6N6xUzrDHGzzfOhzKmW4xl2mzkO/hznaYz3Wxxf5ZngjXeKX3hzk0MmksWyr2Wdg6INS8Qq6e80I9wpXr8lI7HkbLmDNg/peRlL9LuyZhMBwF0AAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="spa" title="spa" src="/static/7e2aa3274a014de701908d3dcd9cbe1b/fcda8/spa.png" srcset="/static/7e2aa3274a014de701908d3dcd9cbe1b/12f09/spa.png 148w, /static/7e2aa3274a014de701908d3dcd9cbe1b/e4a3f/spa.png 295w, /static/7e2aa3274a014de701908d3dcd9cbe1b/fcda8/spa.png 590w, /static/7e2aa3274a014de701908d3dcd9cbe1b/efc66/spa.png 885w, /static/7e2aa3274a014de701908d3dcd9cbe1b/c83ae/spa.png 1180w, /static/7e2aa3274a014de701908d3dcd9cbe1b/8affb/spa.png 1254w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>SPA では、初回アクセス時に、アプリケーションの動作に必要な Javascript ファイルや CSS といった Asset を端末に一括ダウンロードします。単一のページを Javascript 側の処理で表示を切り替えることで高速な画面遷移を実現します。また、画面の一部を非同期に描画、更新することができ、ダイナミックな画面表示を実現することができます。</p> <p>一度画面を表示してしまえば、以降のページの切り替えやデータのやり取りは非常に高速に行うことができる反面、初回アクセス時にある程度まとまった Asset を一括で端末にダウンロードするという特性上、初回のページ表示が遅くなる、もしくは、画面のレンダリングが完了するまで未完成の状態のページが画面に表示される場合があります。こういった挙動が SEO や OGP の観点で不利になることが知られています。<strong>(注 1)</strong></p> <h4>SSR(Server Side Rendering)</h4> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 41.891891891891895%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB+UlEQVQoz0WQS08aYRiFPwS5qGkDQTO22kS8ITDMACpWYBAQhrlxEWxAqsELmtpFtV236aJp0qT/+WlmXHTx5M3Jm3OSc4RUHbHZnrLVnrLRviVenyE1H4hWb0kYd2yb1+wPn5lP9xGbBmLbQqR0RKqFSGiIROWFnToi3ULMyW3mFROx3WS5oHHeVRj2coz6KomjIguZBoGaTHS8S3SUJjJIIvIaIq8TuPlF6P43/oc/+KxHRK6DeHf6kdLkiVhlwk6pzN/nMj8ey3z/dIR8ahBWmsSMCq+7GnFHY6VWIpzWCR5aBAbfWDh7Ym32k7nyGKHaiDe1C47GX5CqF/hTGmrnjvX6FYFcj/jxgHihQuTAQKQ7hOQu0d0eEaVJ8FBHZEx8yQZvy4OXCYpDhFv3VbFPON9hMW8S29dZPbZZLdmsvHdxWCpaLBYNQgc6ItdAKA2E3HqpqDoIxf6PSDYJZE2Wyx9I6FPWGpdIJxPWT69YrU2QTi6QqmMkbUxIdZjPWgRk0/MEFZNwziasWt71Z9oIsddkab9LqjOjcvmVlDMj23+gcP4Zd9+1+sT77Vg37Fo3ZLr35AaPxEvn+LMmrl/stfBl2gTdDV0RUm22jGs29Sl7zp1ncEPS3Xs2WleeTtq3LB30iB0PvTZuYKTg4JcN/FmDoGp5/ANx3QSuQf+ArQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ssr" title="ssr" src="/static/45a43b137ddace5fd0339ea15c51dc7b/fcda8/ssr.png" srcset="/static/45a43b137ddace5fd0339ea15c51dc7b/12f09/ssr.png 148w, /static/45a43b137ddace5fd0339ea15c51dc7b/e4a3f/ssr.png 295w, /static/45a43b137ddace5fd0339ea15c51dc7b/fcda8/ssr.png 590w, /static/45a43b137ddace5fd0339ea15c51dc7b/efc66/ssr.png 885w, /static/45a43b137ddace5fd0339ea15c51dc7b/c83ae/ssr.png 1180w, /static/45a43b137ddace5fd0339ea15c51dc7b/01a87/ssr.png 1288w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>SPA の問題を解決するために出てきたアーキテクチャが Server Side Rendering(SSR) です。</p> <p>SSR では、先ほどの初回表示の問題を解決するために、API サーバとクライアントの間に、ページを生成するためのレンダリングサーバを用意します。</p> <p>SPA では実際に画面を構築する作業はクライアントで行なっていましたが、SSR ではこの作業をレンダリングサーバに移譲しています。これにより、クライアントは既に構築済みのページを受け取ることができるので、初回画面の表示が高速化される場合があります。<strong>(注 2)</strong> また、未完成の状態のページが画面に表示されることがなくなるため、SEO の観点からも SPA と比較して有利であると言われています。</p> <p>SPA の時と同様にクライアントから直接 API を呼び出すといったことも可能ですので、一部の画面を SSR にして、他の画面を SPA のようにクライアントで画面を構築することも可能です。</p> <p>SSR のデメリットとしては、SPA の時と比較して、レンダリングサーバというインフラのコンポーネントが一つ増えることになるので、インフラの実装、運用コストが増えます。また、レンダリングをクライアントサイドで行うのか、レンダリングサーバで行うのかという実装の住み分けが必要になるため、アプリケーションの実装コスト(設計の難易度)が増えることがあります。</p> <p>これらのコストと比較して SPA、SSR のどちらで実装を行うのかを検討する必要があります。例えば SEO がそもそも必要ない場合(社内向けシステムなど)であれば、構成がシンプルな SPA が選択される場合が多いです。</p> <h1><strong>AWS Amplify について</strong></h1> <p>SSR に限らず、AWS 上に Web アプリケーションを構築する際に避けては通れないのが <a href="https://aws.amazon.com/jp/amplify/">AWS Amplify</a>(以下、Amplify)です。</p> <p>Amplify は、1. サーバーレスなバックエンドをセットアップするための Amplify CLI、2.フロントエンドで利用できる UI コンポーネント、3.クライアントサイドから AWS のバックエンドに接続する際に直感的な IF を提供する Amplify Library、4.CI/CD やホスティングのためのコンソール、を含む Web およびモバイルアプリ開発のためのフレームワークです。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.08108108108109%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACWElEQVQoz0WRyY7TQBRF8yNM6nh22bGdOI7jObHTdBI6acYWC9RiaNQC0SwQwwewQGLBihULPgAJwR+w4LcOqgo0i6tXZeudd9+t3uOTYz59eENZz5nNW4qyou062rbDMC003cQwTHTDVOd/0o3/kv9N01C1d3cz5/XZEXkac70ZU+dj2jqjrVMmgU4e9Rm4OpHQSQONLOxTDvtEnk4odDzHwJIwU0JNev1+n+d3E57eiknEZZbJJZroEmV4mVV6jVm8h2PpeLZG4GpEYleFrV/INKS7nXqGJfj2VvD5yRWqIuP2fsxBM2K/SWirhDKLSbOCtlvQLa5T1jOqekbdNDSzOWVVM51OybIM13XpGabNp5cz3p0UTIcW9zqHg9xmO/O4ORdsapdNbZMMBUEY4vseg8EA3/fxPA/HcbAsS0mtLAN+8eyUV+dPCX2HRTWkyUI2Bw1NVTCdpuRZShzHysloNFLn4XBIFEXYto2MTdf1HdC2Hb6+nvD+NGWWhdzfd9g2LqvKZxBEqkk1/wWE0e6b1Hg8VlUOkW4ltOfYNh8fmXx529HtL2kXB6zWh6wPtyxXa5bLFZvNhqZp6LpOZZemqRogQUEQEIYhQojdo+i6wZOzcx48PCX1r3Kn3mOZ6axyjcN8j4EwMS37IieZmVxNNmuapu7S2cXK/b7G79+/+PnjO+NRxO2jFYu24nDZcWPZUeQZ3WLBveNjNtst6/Wa7dGRclsUhXIrH0gOUEA5dTgaqSxcVzAIQlwhEJ5PHCckSaKa8jynLEsmk4nKUUKkpLN/MLnyH+qqcGeUW2tMAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="about amplify" title="about amplify" src="/static/1848605133a02908c1d00a0fb2f93767/fcda8/about_amplify.png" srcset="/static/1848605133a02908c1d00a0fb2f93767/12f09/about_amplify.png 148w, /static/1848605133a02908c1d00a0fb2f93767/e4a3f/about_amplify.png 295w, /static/1848605133a02908c1d00a0fb2f93767/fcda8/about_amplify.png 590w, /static/1848605133a02908c1d00a0fb2f93767/efc66/about_amplify.png 885w, /static/1848605133a02908c1d00a0fb2f93767/c83ae/about_amplify.png 1180w, /static/1848605133a02908c1d00a0fb2f93767/8963a/about_amplify.png 1918w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Amplify を用いることで、AWS のバックエンドの構築、構築したバックエンドに接続するためのクライアントサイドの実装を非常に直感的に行うことが可能です。</p> <p>実装の例を挙げると..</p> <ul> <li><em>Amazon Cognito を用いた認証、認可、Social Provider を用いた OAuth 認証</em></li> <li><em>GraphQL のマネージドサービスである AWS AppSync を用いたデータの CRUD 処理</em></li> <li><em>きめ細やかな権限設定を伴うファイルのアップロード/ダウンロード</em></li> <li><em>MQTT を用いた WebSocket 通信</em></li> <li><em>AWS が提供する各種 AI サービスとのシームレスな統合(例えば、ネガポジ判定や画像アップロードして Object Detection の結果を返すといった API などが簡単に利用できます)</em></li> </ul> <p>Amplify CLI を用いることで、これらの機能の実装をほぼゼロコストで構築することができます。専門のインフラエンジニア、バックエンドエンジニアを抱えることができない小規模な開発組織においては Amplify は非常に強力なツールになり得ます。</p> <p>また、Web アプリケーションのホスティングも非常に簡単で、Amplify Console と呼ばれるホスティングサービスを用いることで、数クリックで Github や Bitbucket といったソースリポジトリの連携、CI/CD パイプラインを構築することができます。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 41.21621621621622%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABqElEQVQoz0WSu47UMBiF8whQUVBS8GIg6ECCgoZLA4+wEi0FBSWgLVaiZNl2kXakGWkmm8wyk8nkZjuxE+fyIZudwdIv27+t43OOTyCk5M9mQ1mWDMOAlJIwviGO16xWIUmSoJTy/UMJIZBSUYQz9le/yMMZeVFQVRVB17aoPKVVFdM0oeqaxWLBbDbj8vI38/ncg7rabrekaeoBHREHlpx/Jb26YJfuKYqCwL2Y5zl93zOOI8Zoskow8n+4ftu2WGsZ+uFY7s7kzsGr01oTOKlxHHtZjmFd13S95dv3U16/fc+bdx/4eX7hgW1vkUYgjfSz0CVSl4imZBgH/6hnuFwuPV0H6Lxx4+WLV9y9/5A79x5wcvLR96QWxPk1O5mwrTZsqhtSuWNdROiuoWs7AudFFMce0Ek7MPz0+QuPnz7n0ZNnnJ79YOIfe91opnHyWo02dJ2lt/3RlsDp3mcZQinfdHupFLrt6KcJO04orTHGkGWZ/xinynkeRRGr8JpVtKYQgtYYAme0NgZrey+5aZrbWEhEVflSt3FRqvYsD/Fxa5eKY6yE5C/35lQMSBKXRAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="amplify console" title="amplify console" src="/static/899dfa30372b7e1496826b60dd1a6bb1/fcda8/amplify_console.png" srcset="/static/899dfa30372b7e1496826b60dd1a6bb1/12f09/amplify_console.png 148w, /static/899dfa30372b7e1496826b60dd1a6bb1/e4a3f/amplify_console.png 295w, /static/899dfa30372b7e1496826b60dd1a6bb1/fcda8/amplify_console.png 590w, /static/899dfa30372b7e1496826b60dd1a6bb1/efc66/amplify_console.png 885w, /static/899dfa30372b7e1496826b60dd1a6bb1/d1d24/amplify_console.png 987w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>Amplify の各機能の詳細に関しては 2020/5 に公開された BlackBelt Online セミナーを見れば体系的に学べると思います。</p> <div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 80.66666666666666%; position: relative; height: 0; overflow: hidden; margin-bottom: 1.0725rem" > <iframe src="//www.slideshare.net/slideshow/embed_code/key/LpBAUn0RbmJvkq?startSlide=1" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " allowfullscreen=""> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/AmazonWebServicesJapan/20200520-aws-black-belt-online-seminar-aws-amplify-234663231" title="20200520 AWS Black Belt Online Seminar AWS Amplify" target="_blank">20200520 AWS Black Belt Online Seminar AWS Amplify</a> </strong> from <strong><a href="//www.slideshare.net/AmazonWebServicesJapan" target="_blank">Amazon Web Services Japan</a></strong> </div> </div> <h1><strong>Amplify と SSR</strong></h1> <p>2020.09.15 に Amplify Library が Server Side Rendering(以下、SSR) に対応しました。これまでの Amplify Library では、セッション情報やクライアントの設定情報をサーバサイドに引き継ぐ仕組みが存在しなかったため、Amplify Library は事実上クライアントサイドのみの実装に限られてきました。</p> <p>今回のアップデートにより、Next.js でいうところの <code class="language-text">getServerSideProps</code> メソッド に Amplify のクライアントの情報を引き継ぐことができるようになります。</p> <p>以下、<a href="https://aws.amazon.com/jp/blogs/mobile/ssr-support-for-aws-amplify-javascript-libraries/">AWS 公式 Blog</a> の抜粋</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> withSSRContext <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'aws-amplify'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> listMovies <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../src/graphql/queries'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Movies</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> movies <span class="token punctuation">}</span> <span class="token operator">=</span> props<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>div<span class="token operator">></span> <span class="token punctuation">{</span>movies <span class="token operator">&amp;&amp;</span> movies<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">movie</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>div key<span class="token operator">=</span><span class="token punctuation">{</span>movie<span class="token punctuation">.</span>id<span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator">&lt;</span>h3<span class="token operator">></span><span class="token punctuation">{</span>movie<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>h3<span class="token operator">></span> <span class="token operator">&lt;</span>span<span class="token operator">></span><span class="token punctuation">{</span>movie<span class="token punctuation">.</span>description<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">></span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// ↓ Server Side で実行されるソース</span> <span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getServerSideProps</span><span class="token punctuation">(</span><span class="token parameter">context</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 今回新しく追加された、withSSRContext により、クライアントの情報をServer Sideで引き継ぐことが可能</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token constant">API</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">withSSRContext</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> movieData<span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment">// クライアントのセッション情報(この場合Cognitoのtoken情報など)を引き継いでAppSyncにクエリを発行</span> movieData <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token constant">API</span><span class="token punctuation">.</span><span class="token function">graphql</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">query</span><span class="token operator">:</span> listMovies<span class="token punctuation">,</span> <span class="token literal-property property">authMode</span><span class="token operator">:</span> <span class="token string">'AMAZON_COGNITO_USER_POOLS'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'movieData: '</span><span class="token punctuation">,</span> movieData<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'error fetching movies: '</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">movies</span><span class="token operator">:</span> movieData <span class="token operator">?</span> movieData<span class="token punctuation">.</span>data<span class="token punctuation">.</span>listMovies<span class="token punctuation">.</span>items <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h1><strong>Amplify が SSR のホスティングをサポート</strong></h1> <p>2021/05/19 に Amplify Console が<a href="https://aws.amazon.com/jp/blogs/mobile/host-a-next-js-ssr-app-with-real-time-data-on-aws-amplify/">Next.js の SSR ホスティングをサポート</a>しました。</p> <p>これまで<a href="https://www.serverless.com/">Serverless Framework</a>と呼ばれる Amplify CLI とは別のコマンドラインツールを用いる必要がありましたが、Amplicy Console を用いて静的サイトの時と同じ手順でホスティングを行うことが可能です。</p> <p>SSR のホスティングには特別な操作は必要しません。静的サイトとしてホスティングするのか、SSR としてホスティングを行うかは Amplify Console 側で自動判定してくれます。<a href="https://docs.aws.amazon.com/amplify/latest/userguide/server-side-rendering-amplify.html#redeploy-ssg-to-ssr">公式ドキュメント</a> によると、どうやら<code class="language-text">package.json</code>の中身から SSR でホスティングすべきかどうかを判断しているようです。</p> <p>デプロイされる構成としては以下のようになります。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.83783783783784%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABe0lEQVQoz22SsU4bQRCG/Tw0qRFVqtR5GRSJgpIiPABFpMRBSAiJgoKGNEhGUaQoKMKxRQy2heE4h/jWOR/H3e3u7BfdLgErsNJoZ2Z3//+fnWkAOMeT5f5L1vF8zs3dmc83xBhAGLYzjnZm6Lv6ofaHvbjN9tcN0kIFBKDqdrnb2oY0fZa4YfJboGD//Skrr09QlxNsdg1as/flI6/evmCU9D2gwxB/aPJz8SXFYBgARRBx3jygZ9WaeDwhihKSRAUlZUW784Pj78ec9/s4a5AyJ1GKeBwx/TN9orD2G85JiLIxbtTy5eMEazSmqnBWMFojVYmzBZLkSHsERh5ATvslw8gEhTWzx/u8yc3aAi69Qm4VMrtBsgky++13q8a1bn41DxgsvcFexECJzTOWV89Zf6ceS86LkvjshEFrl+voMvyH76o8mgRFV9+6dJr7ZJMAkE4VB596HLYGZFkaAK01aHFUDsq6zH/j8MwoWd8asPcExk9Jbdr7fwHDsF9Mcw7k9wAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="architecture" title="architecture" src="/static/1a3f01c8ebb1b2c6f94b51be271d9c3e/fcda8/architecture.png" srcset="/static/1a3f01c8ebb1b2c6f94b51be271d9c3e/12f09/architecture.png 148w, /static/1a3f01c8ebb1b2c6f94b51be271d9c3e/e4a3f/architecture.png 295w, /static/1a3f01c8ebb1b2c6f94b51be271d9c3e/fcda8/architecture.png 590w, /static/1a3f01c8ebb1b2c6f94b51be271d9c3e/5fd3e/architecture.png 594w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>SPA の時と同様に静的アセットは ストレージサービスである Amazon S3 に配置され、CDN である Amazon CloudFront から高速に配信されます。動的コンテンツのレンダリングには Lambda@Edge が用いられており、上記コードの例では、Lambda@Edge から GraphQL のマネージドサービスである AWS AppSync に対してクエリが発行され、認証認可には Amazon Cognito が用いられています。</p> <h1><strong>Amplify Console で SSR をホスティングした場合、CloudFront の設定を変更することが可能に</strong></h1> <p>Amplify Console で静的サイトをホスティングした時のもう一つの変更点として、CloudFront が自アカウントで操作可能な点があります。</p> <p>静的サイトのホスティングの場合、AWS サービスチームの CloudFront にホスティングされていたため、ユーザは CloudFront の管理を意識することはありませんでした。ユーザの管理対象が減るというメリットがある反面、CloudFront に対して独自の設定を行うことができないというデメリットもありました。</p> <p>Amplify Console で SSR を実施する場合、CloudFront はご自身が使用されているアカウントに構築されます。これにより、例えば Amplicy Console でホスティングされたサイトに AWS WAF の設定を追加するといったことができるようになります。</p> <p>Enterprise のお客様などで、自社ネットワークからのみサイトにアクセスさせたいといったユースケースにマッチしそうです。</p> <p>なお、静的サイトに関しては引き続きサービスチームの CloudFront にホスティングされるためご注意ください。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 38.513513513513516%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABuElEQVQoz02R627TUBCE/agIFUiUpi3gCiQQPAh/4EGQEKKIf1QmaaL60rS+JbZjx3YS28fH/pAdGjHSaLWrndnROcrJ6JLT1x8Yqx8ZXrxncP6OJwOVwdkbXoxVTl++ZXh+yWCs8nz0qp+dDC96Ds4O82cjtdc8HaooaZrSSMn/CJcO0cojjUPCYIXv+6xWK7bbLUJUCCEQ1aFKWdM0Td/LWqCIxAVaZNPQti27ouLX9IErzeLnn3uMhY1pGBiGSXe826nrGillz7IsqarqGEZJ7Af2mtYvdsj2gk9fLT5/W/Dlu4PurAmWHr6/xPN88jzvk3Z1t9sRBAHr9bo3LYoCJV76VJ59vCDrGt9eYOlzdP2G2/mM6WSCaejMplMM/RbLNPvUSRyTpRs2SYLnuriOg5LleW/0mFDUEjOs0OYLZtcGTrzjxon5vYjQ7BgrSJncuWiWjeFFGG7EfZDib0r8pEBJkqRzOxp2D1xKKIVEXN8hwgyRlZThlircUid7qnVOGWwooox9sqUSDXULdQPK5scVTZrxL2Zv2H3SI1pR025ymnVKEyTIKKWJswOzHe2+6HUHTctfEQtB9V/zfqgAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ssr hosting" title="ssr hosting" src="/static/aacc013e5ced4037d319e3abd099e30a/fcda8/ssr_hosting.png" srcset="/static/aacc013e5ced4037d319e3abd099e30a/12f09/ssr_hosting.png 148w, /static/aacc013e5ced4037d319e3abd099e30a/e4a3f/ssr_hosting.png 295w, /static/aacc013e5ced4037d319e3abd099e30a/fcda8/ssr_hosting.png 590w, /static/aacc013e5ced4037d319e3abd099e30a/efc66/ssr_hosting.png 885w, /static/aacc013e5ced4037d319e3abd099e30a/6be49/ssr_hosting.png 1160w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h1><strong>まとめ</strong></h1> <p>Amplify の仕組みを用いて、AWS 上に Next.js × SSR を構築する一例についてご紹介しました。 Amplify Library が SSR に対応したことで、AWS 上で SSR のアプリケーションを構築する選択肢が取りやすくなったと思います。</p> <p>以上です!</p> <br/> <p><strong>補足:</strong></p> <ul> <li> <p>注 1. 近年では多くのクローラが Javascript を実行するようになりました。そのため、数年前に問題視されていたほど、SEO の観点で SPA が不利になることは少なくなりました。ただし、クローラは全ての Javascript を実行することを保証していないため、高い SEO が求められる場合は SSR や JAMStack といった構成を検討する必要があります。</p> </li> <li> <p>注 2.* Web のパフォーマンスに関しては様々な指標が存在します。Web のパフォーマンス改善を議論する場合は、アプリケーションの特性を理解し、どの指標を改善すべきなのかを正確に把握する必要があります。一般的に、SSR にすることで SPA より描画速度が高速になると言われることが多いですが、ケースによっては SSR にすることでユーザ体験を損なう場合もあります。(Server Side Rendering の挙動を確認する を参照) 自社のアプリケーションの課題、特性、SPA や SSR の持つ特性を正しく理解し、技術選定を行うことが大切です。</p> </li> </ul><![CDATA[Incremental Static Regenerationをコンテナで運用するときの注意点]]>https://whnyab.com/isr_container_deploy/https://whnyab.com/isr_container_deploy/Sun, 27 Dec 2020 22:12:03 GMT<p>こちらの記事を参考に Incremental Static Regeneration(以下、ISR)を コンテナでデプロイしたときの挙動について気になった点があったのでまとめてみました。</p> <p>なお、本記事では ISR の詳細ついては言及しません。それらについては元記事を参照いただければと思います。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">アドベントカレンダー用の記事を書いた。結果は…<br><br>はてなブログに投稿しました <a href="https://twitter.com/hashtag/%E3%81%AF%E3%81%A6%E3%81%AA%E3%83%96%E3%83%AD%E3%82%B0?src=hash&amp;ref_src=twsrc%5Etfw">#はてなブログ</a><br>Next.jsのIncremental Static RegenerationをVercel以外でやってみる - Sweet Escape<a href="https://t.co/gGt8nVnPiE">https://t.co/gGt8nVnPiE</a></p>&mdash; Keisuke Nishitani (@Keisuke69) <a href="https://twitter.com/Keisuke69/status/1336562587148808192?ref_src=twsrc%5Etfw">December 9, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <h3>ISR をコンテナイメージで運用する際の注意点</h3> <p>Next.js の ISR では、<code class="language-text">getStaticProps</code>の戻り値に<code class="language-text">revalidate</code>オプションを付与すると、指定された秒数、クライアントにキャッシュされたコンテンツが返却されるようになります。このキャッシュはアプリケーションが動作するコンテナ内に保持されます。</p> <p>以下の例であれば、最後にページを動的に作成してから 最低 10 秒間は静的なページをクライアントに返却します。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getStaticProps</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> date <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> current <span class="token operator">=</span> date<span class="token punctuation">.</span><span class="token function">toLocaleString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span> current<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">revalidate</span><span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token comment">// 10秒に1回ページを最新化する</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>なお、revalidate は各コンテナごとにバラバラのタイミングで実施される点に注意が必要です。ロードバランサなどで複数のコンテナにルーティングされる場合、リクエストが流れるコンテナによって異なるコンテンツが返却される可能性があります。</p> <p>一つのアプリケーションで一つのコンテナしか動いていない場合にはこの問題は発生しませんが、そのようなアプリケーションはあまり多くないと思います。 また、この問題は revalidate の間隔が長くなればなるほど顕著になります。</p> <p>例えば、<code class="language-text">revalidate: 300(5分)</code>を指定したアプリケーションが「コンテナ A」、「コンテナ B」 という二つのコンテナ上で動いている例を考えます。初回のリクエストがロードバランサを経由して「コンテナ A」にルーティングされ、その 2 分 30 秒後に「コンテナ B」にルーティングされたとしましょう。 この場合、次回 revalidate のタイミングは「コンテナ A」は 2 分 30 秒後で「コンテナ B」は 5 分後になります。この期間にバックエンドでコンテンツの更新が行われた場合、最大 2 分 30 秒間、「コンテナ A」と「コンテナ B」で異なるコンテンツをクライアントに返却してしまうことになります。ロードバランサがラウンドロビンでリクエストをバランシングしている場合、ページをリロードするたびに表示内容が変わったり、ユーザによって表示されるコンテンツが異なることが想定されます。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.83783783783784%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABv0lEQVQoz22Ob2vTUBSHL/hJ/UC+8YUgVCradxbL6jYmbNONDWEW3dSsbRaXdk3aJmmSm7VN7s2/RxrBVz5w4Md5+B2OyMJrUBakd7AdUWuPHel6ReIPIV+AnoNyqdUCqMlzheeOIA+gWEG+os5XUJcItS8I9wRRX5DvC+rhU2qgenhGfiBIPgiC94LsUMDnJ9R5RJXOKI4FjweCVV+wPhRUp4J6YyBSu8Xa7kDQg+QdZDfNh2gTotcQdEgnLbbWc0q3DRSAgugt+G3UtAVeG6I3UEpE9yxEZuBE0LuQjGcaf/GA5aw5/p4ydkEDJ9/WXPxKCcOYhRdycp1y/jOjBC4NzcfBmq2qEK/2bomSLeZDxIvuDwZDD2dqMTAcXvZuubxxUVrTORrTPb1nbFoYQ5PuJ5v++W+qMufoy6TxyUYhdLbhf+Rak8iIuiqp6woZheQ6++cfZUhV6iYnMoTirxOTyRTP8/H9gDiW+L7fCCkT7m2bOI6RUjKdTgmCAKVUM6Z5x647m80YjU1m9oQ4ihBZljWFJElI07TJO8IwbI4YhsHV1Vds28ayLFzXpSgKlstls9t5x3Fw53Pm8zl/AJxvQwkonbjIAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="revalidate" title="revalidate" src="/static/283ac18857d5b7b859a5497e85b5e519/fcda8/revalidate.png" srcset="/static/283ac18857d5b7b859a5497e85b5e519/12f09/revalidate.png 148w, /static/283ac18857d5b7b859a5497e85b5e519/e4a3f/revalidate.png 295w, /static/283ac18857d5b7b859a5497e85b5e519/fcda8/revalidate.png 590w, /static/283ac18857d5b7b859a5497e85b5e519/6114d/revalidate.png 623w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>AWS Fargate を用いて検証</h3> <p>AWS が提供するコンテナオーケストレーションサービスである、<a href="https://aws.amazon.com/jp/fargate/">AWS Fargate</a>を用いて上記の挙動を再現してみましょう。</p> <p>Dockerfile とアプリケーションは元記事のものを拝借します。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Index</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> current <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token operator">&lt;</span>div<span class="token operator">></span>現在時刻は<span class="token punctuation">{</span>current<span class="token punctuation">}</span>です。<span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getStaticProps</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> date <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> current <span class="token operator">=</span> date<span class="token punctuation">.</span><span class="token function">toLocaleString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span> current<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">revalidate</span><span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token instruction"><span class="token keyword">FROM</span> node:current-alpine <span class="token keyword">AS</span> base</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /base</span> <span class="token instruction"><span class="token keyword">COPY</span> package*.json ./</span> <span class="token instruction"><span class="token keyword">RUN</span> yarn install &amp;&amp; yarn cache clean</span> <span class="token instruction"><span class="token keyword">COPY</span> . .</span> <span class="token instruction"><span class="token keyword">FROM</span> base <span class="token keyword">AS</span> build</span> <span class="token instruction"><span class="token keyword">ENV</span> NODE_ENV=production</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /build</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">base</span></span> /base ./</span> <span class="token instruction"><span class="token keyword">RUN</span> yarn build</span> <span class="token instruction"><span class="token keyword">FROM</span> node:current-alpine <span class="token keyword">AS</span> production</span> <span class="token instruction"><span class="token keyword">ENV</span> NODE_ENV=production</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">build</span></span> /build/package*.json ./</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">build</span></span> /build/.next ./.next</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">build</span></span> /build/public ./public</span> <span class="token instruction"><span class="token keyword">RUN</span> yarn add next &amp;&amp; yarn cache clean</span> <span class="token instruction"><span class="token keyword">EXPOSE</span> 3000</span> <span class="token instruction"><span class="token keyword">CMD</span> yarn start</span></code></pre></div> <p>ユーザがアクセスしたタイミングで現在時刻が表示されるシンプルなアプリケーションです。 <code class="language-text">revalidate: 10</code>が指定されているため、10 秒間は何度アクセスしても表示時刻が変化しないことを期待します。</p> <p>なお、今回は ECS に簡単にコンテナをデプロイできる<a href="https://aws.amazon.com/jp/blogs/news/introducing-aws-copilot/">AWS Copilot</a>を用いました。</p> <p>Next.js のトップディレクトリで<code class="language-text">copilot init</code>コマンドを発行し、Dockerfile を指定するだけでコンテナ実行に必要な VPC、ECS クラスタ、ALB、及びアプリケーションのエンドポイント URL を作成してくれます。めちゃくちゃ簡単です。</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ copilot init Application name: isr-test <span class="token comment"># アプリケーション名を指定</span> Service type: Load Balanced Web ServiceX Sorry, your reply was invalid: Value is required Service name: isr-test <span class="token comment"># サービス名を指定</span> Dockerfile: ./Dockerfile <span class="token comment"># Dockerfileを指定</span> Ok great, we<span class="token string">'ll set up a Load Balanced Web Service named isr-test in application isr-test listening on port 3000. ✔ Created the infrastructure to manage services under application isr-test. ✔ Wrote the manifest for service isr-test at copilot/isr-test/manifest.yml Your manifest contains configurations like your container size and port (:3000). ✔ Created ECR repositories for service isr-test. All right, you'</span>re all <span class="token builtin class-name">set</span> <span class="token keyword">for</span> <span class="token builtin class-name">local</span> development. Deploy: Yes <span class="token punctuation">(</span>省略<span class="token punctuation">..</span>.<span class="token punctuation">)</span> f40183c0ca23: Pushed 8107d4305b97: Pushed 920b98a6ce66: Pushed 53a4abbb293c: Pushed a40220d40eaf: Pushed 0fcbbeeeb0d7: Pushed 4e09dfc: digest: sha256:82c4b9b191ee37e520502eb3d007f4d565f7316698fcc67fe46901e8206d408c size: <span class="token number">2203</span> <span class="token comment"># アプリケーションのエンドポイントURLが発行される。</span> ✔ Deployed isr-test, you can access it at http://isr-t-Publi-1MHPV70ZK9M4Z-802069023.ap-northeast-1.elb.amazonaws.com.</code></pre></div> <p><br/><br/></p> <p>発行された URL にアクセスすると、想定通りにアプリケーションが動作していることがわかります。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 25%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAjUlEQVQY063Hyw6CMBRF0f7/xznUmGiflFpa0FKGwDZ25kQn3GRlnyv85cpZngg5M5eZPE6M48SQMs9XocwLd9Wh3eM/GxClFOZaqXUhDgnfB3wIWNdhnGu9SYXUpvUuNcZ17f9UaYsytjXlCSGlRGuN9x5jDH3fE2Nse0iJdV1Zt+3L9oPg4BP7vnOkN7VMf3SzTO3ZAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="app" title="app" src="/static/9788d36a956a7b50045c7594e8956e68/fcda8/app.png" srcset="/static/9788d36a956a7b50045c7594e8956e68/12f09/app.png 148w, /static/9788d36a956a7b50045c7594e8956e68/e4a3f/app.png 295w, /static/9788d36a956a7b50045c7594e8956e68/fcda8/app.png 590w, /static/9788d36a956a7b50045c7594e8956e68/073e9/app.png 719w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <p>デフォルトの設定だと、コンテナは一つしか動いていないため、copilot の manifest ファイルからコンテナを増やす設定を加えてみます。</p> <p>copilot が作成した<code class="language-text">copilot/&lt;サービス名>/manifest.yml</code>の<code class="language-text">count</code>から起動するコンテナの数を変更します。</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token comment"># Number of tasks that should be running in your service.</span> count: <span class="token number">10</span></code></pre></div> <p>サービスのデプロイコマンドで変更をクラウドに反映します。</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ copilot svc deploy</code></pre></div> <p>再度、ブラウザにアクセスしてみると、ページを表示させる度に表示される内容が毎回変わってしまうことが確認できます。これは先ほど述べた revalidate のタイミングが異なるため、ルーティングされるコンテナごとに異なるコンテンツ(今回の場合現在時刻)が表示されてしまうためです。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <span class="gatsby-resp-image-background-image" style="padding-bottom: 25.675675675675674%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA6klEQVQY05WObUuDYBiF/SNFEBQFRS9ftlXi1BqD/feYITk/bE9oTqeoj87BGvMKnw8RFEQHLu7DgcN9tOUqI8ly6vWaZrNBygpZVZRS8rHb4b766DeXnF0fc3F1i66bmOajwrJG9Hr3nJyec3B4pNDKUlLVNWVZMl8I5kLwHi1ZiDdWWcbz1GE4tBkMHjAMW/nxeEK/f8fTaIxhWCqzrO6JjSaEII5jRRiGJEmifBRFNE1DmqZMHQfXdfF9H8/z1HWcF7zZjCAIkFKqQd04jT9UFAX/kbbf72nb9gdd3inPc7bb7Vf2G997nzx8UDF8PQhkAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="compare" title="compare" src="/static/7050e554b8e88ff2fe53f39baf5e18f7/fcda8/compare.png" srcset="/static/7050e554b8e88ff2fe53f39baf5e18f7/12f09/compare.png 148w, /static/7050e554b8e88ff2fe53f39baf5e18f7/e4a3f/compare.png 295w, /static/7050e554b8e88ff2fe53f39baf5e18f7/fcda8/compare.png 590w, /static/7050e554b8e88ff2fe53f39baf5e18f7/f3c12/compare.png 764w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></p> <h3>まとめ</h3> <p>コンテナに限らずですが、ローカルにキャッシュデータを保持する特性上 revalidate のタイミングが異なる点は意識する必要があります。コンテンツの表示に高い一貫性を求められない場合は大きな問題にはならないかもしれません。</p> <p>これらの挙動が許容できない場合は、revalidate の間隔を短くする、もしくは厳密な一貫性が求められるページのみ SSR や、クライアントから API を Call するなどなどして対応する必要があります(その場合、リクエストの度にバックエンドに負荷をかけることになるため ISR 本来の旨味が少なくなります)。Vercel でデプロイする場合 CDN のキャッシュと合わせてその辺りをうまくハンドリングしているようです。</p> <p>フロントエンドも SPA、JAMstack、SSR、そして ISR とアーキテクチャの選択肢が増えてきましたが、それぞれの特性をよく理解して技術選定をするのが良さそうです。</p><![CDATA[Gatsby製のサイトをAmplify Consoleでホスティングすると、特定のページのURLをブラウザに打ち込んだ際にTOPページにリダイレクトされてしまう]]>https://whnyab.com/amplify_console_gatsby/https://whnyab.com/amplify_console_gatsby/Thu, 10 Dec 2020 04:59:03 GMT<p>Gatsby 製のサイト(このサイト)を Amplify Console でホスティングする際にハマったのでメモ。</p> <p>何が問題だったかというと、特定のページ(<code class="language-text">https://whnyab.com/amplify-ssr-cicd</code>)にブラウザから直接アクセスしても全て TOP ページ(<code class="language-text">https://whnyab.com/</code>)にリダイレクトされてしまう状態だった。全てのアクセスが TOP ページにアクセスされてしまうため、特定の記事を SNS 等でシェアすることができなくなっていた。しかもローカル環境では再現しなかったので気がつくまでに結構時間が立っていた。</p> <h3>解決策</h3> <p>初めは Gatsby のルーティングの設定を疑っていたが、どうやら Amplify Console のリダイレクトの設定が問題のようだった。</p> <p>Amplify Console ではマネジメントコンソールから Web ページのリダイレクトの設定を細かく設定できる。SPA のサイトをホスティングする際は、多くの場合でデフォルトの設定で問題ないのだが、Gatsby のサイトをホスティングする場合はリダイレクト設定を以下のように修正する必要がある。</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token comment">// (before)</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"/&lt;*>"</span><span class="token punctuation">,</span> <span class="token property">"target"</span><span class="token operator">:</span> <span class="token string">"/index.html"</span><span class="token punctuation">,</span> <span class="token property">"status"</span><span class="token operator">:</span> <span class="token string">"404-200"</span><span class="token punctuation">,</span> <span class="token property">"condition"</span><span class="token operator">:</span> <span class="token null keyword">null</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"&lt;/^[^.]+$|\\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|ttf|map|json)$)([^.]+$)/>"</span><span class="token punctuation">,</span> <span class="token property">"target"</span><span class="token operator">:</span> <span class="token string">"/index.html"</span><span class="token punctuation">,</span> <span class="token property">"status"</span><span class="token operator">:</span> <span class="token string">"200"</span><span class="token punctuation">,</span> <span class="token property">"condition"</span><span class="token operator">:</span> <span class="token null keyword">null</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span></code></pre></div> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token comment">// (after)</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"/&lt;*>"</span><span class="token punctuation">,</span> <span class="token property">"target"</span><span class="token operator">:</span> <span class="token string">"/index.html"</span><span class="token punctuation">,</span> <span class="token property">"status"</span><span class="token operator">:</span> <span class="token string">"404-200"</span><span class="token punctuation">,</span> <span class="token property">"condition"</span><span class="token operator">:</span> <span class="token null keyword">null</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span></code></pre></div> <p>設定箇所はこちら。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <a class="gatsby-resp-image-link" href="/static/28500b39d621a83fc000c0a68f6c6cd3/cdef6/amplify_console_redirect_settings.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 33.108108108108105%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA5ElEQVQoz52P227EIAxE+f9f7FO0m6pZyAUIYC7JrOw2abbtU5GOMKPx2KicC/js+w7KGbf7HX3/DmMMtDaIMYKIhHThfKeETATz1mHqeqgYE7Ztk1A2DA8NM45YQxCI8tnMA/MXUpciN4f6x4hlMFDjNEvTETgvFt6vqLXKoFIKFmsFrlln1nWVQa01eXNwqRUqhIDjxJTgvJdgrpkQIrQxmOb51PnmRfgnh+/TG6Ai0XdgTOc2V6z3sM69as6J/tOrgjZoiwVqQ6QkRt7yyl+a4H5ryncd9o8BoALe1lp7hvyHJ8XcHHbs1p3QAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="amplify console redirect settings" title="amplify console redirect settings" src="/static/28500b39d621a83fc000c0a68f6c6cd3/fcda8/amplify_console_redirect_settings.png" srcset="/static/28500b39d621a83fc000c0a68f6c6cd3/12f09/amplify_console_redirect_settings.png 148w, /static/28500b39d621a83fc000c0a68f6c6cd3/e4a3f/amplify_console_redirect_settings.png 295w, /static/28500b39d621a83fc000c0a68f6c6cd3/fcda8/amplify_console_redirect_settings.png 590w, /static/28500b39d621a83fc000c0a68f6c6cd3/efc66/amplify_console_redirect_settings.png 885w, /static/28500b39d621a83fc000c0a68f6c6cd3/cdef6/amplify_console_redirect_settings.png 1163w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <a class="gatsby-resp-image-link" href="/static/669be11bb4fac596a077c596a83477a4/fbf08/edit_by_editor.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.83783783783784%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQoz52RzU7DMBCE8/4PwbvAGQEnDhS1pS1tHSf+TeLESfohOy0nDsBKo51deUez3uLp+YXd/sBZCDbbD9abLYfPI7VS1Eqjjclcaf3du9XaWpQxC6zBOkchpOR0PrM/CXZCIWuVB0pZUV0Hq7rOPVnVmctUJwhJdS6pRZm50YZCSsk0TfR9nzEMAzFGxjHm/COmkehb7GpHtTkg3rZU7zvak6Qwxi5CMRJCyDzlJDzPM6Hvs8jlcsF7T5/6l5nYDwTjGHzL2AamNtD5hiIJpMdpUCmNc5626yilxFhLHEfGaWKa56vzpTbWcRQlYRi4RejD4jB9dFo7hGXl/0YYZ4rlepqu67LLtG5y+Bs0TYt1Hu8s7foRd1xR+Mbjmyaf/K/QWiNkhRQn9MMd5vWeL6qjZnAUkoaMAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="edit by editor" title="edit by editor" src="/static/669be11bb4fac596a077c596a83477a4/fcda8/edit_by_editor.png" srcset="/static/669be11bb4fac596a077c596a83477a4/12f09/edit_by_editor.png 148w, /static/669be11bb4fac596a077c596a83477a4/e4a3f/edit_by_editor.png 295w, /static/669be11bb4fac596a077c596a83477a4/fcda8/edit_by_editor.png 590w, /static/669be11bb4fac596a077c596a83477a4/efc66/edit_by_editor.png 885w, /static/669be11bb4fac596a077c596a83477a4/fbf08/edit_by_editor.png 962w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><a href="https://github.com/aws-amplify/amplify-console/issues/991#issuecomment-725543954">Amplify Console の Issue</a>で同じ事象でハマっている人がいて助かった。</p> <p>Gatsby を Amplify Console にホスティングしようとすると必ずこの問題に直面するはずなんだけど、参考になる情報が少なくて苦労した。</p> <p>みんなバリバリ Amplify Console を使いこなしていてそもそも問題にならないのか、Gatsby ユーザが誰も Amplify Console 使ってないのかどっちだろう..</p><![CDATA[AWS Amplify + AWS AppSyncでUnitテスト書く時の How to]]>https://whnyab.com/amplify_unit_test/https://whnyab.com/amplify_unit_test/Fri, 04 Dec 2020 22:12:03 GMT<h3>概要</h3> <p>Amplify Framework + AWS AppSync でフロントエンドの Unit テスト書く時のハウツーをご紹介します。本記事ではアプリケーションの実装に React を用いていますが、基本的な考え方は Vue などの他のフレームワークでも同じです。</p> <h3>想定読者</h3> <ul> <li>Amplify 触ったことある</li> <li>AppSync 触ったことある</li> <li>jest を使った Unit テストを書いたことがある</li> <li>React のソースが読める(と、尚良し)</li> </ul> <p>Amplify、AppSync あまり触ったことない!という方は <a href="https://qiita.com/nagym/items/e5b9f43b11ef2309c546">Amplify CLI GraphQL Transform とディレクティブで AppSync+DynamoDB をいじってみよう!(@model @auth, @key)</a>の記事を参考にいただけると良いと思います!</p> <h3>Amplify で Unit テストを書く方法</h3> <h4>API を Mock する</h4> <p>一般的にフロントエンドの Unit テストを書く場合は、外部の API 通信を Mock することが多いと思います。例えば以下のような axios を用いた API 通信を Mock する場合は、</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token comment">// return { user: { name: 'taro', age: 22 }}</span> <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/user/1'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resp</span><span class="token punctuation">)</span> <span class="token operator">=></span> resp<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">&lt;</span>User user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js">jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span>axios<span class="token punctuation">,</span> <span class="token string">'get'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">user</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'taro'</span><span class="token punctuation">,</span> <span class="token literal-property property">age</span><span class="token operator">:</span> <span class="token number">22</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>といった感じで、API 呼び出しを行う関数を Mock し、想定されるレスポンスに対し DOM が正しく描画されるか等のテストを行います。</p> <p>Amplify で Unit テストを書く場合も基本的な考え方は同じです。Amplify Framework は AWS バックエンドとシームレスに連携できる API をたくさん提供しており、それらの API を Mock することで Unit テストを実装していきます。</p> <p>例えば、S3 にファイルをアップロード処理を Mock する場合は</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> Storage <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'aws-amplify'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> file <span class="token operator">=</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>files<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// return { key: "&lt;file_path>" }</span> Storage<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">'example.png'</span><span class="token punctuation">,</span> file<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">contentType</span><span class="token operator">:</span> <span class="token string">'image/png'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">result</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> Storage <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'aws-amplify'</span><span class="token punctuation">;</span> jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span>Storage<span class="token punctuation">,</span> <span class="token string">'put'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">key</span><span class="token operator">:</span> <span class="token string">'example.png'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>この様に、想定されるレスポンスの通りに Mock するだけです。 他にも、よく使用する Mock の例いくつかあげます。</p> <h4>例 1. Cognito の認証済みユーザ情報を取得する</h4> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> Auth <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'aws-amplify'</span><span class="token punctuation">;</span> Auth<span class="token punctuation">.</span><span class="token function">currentAuthenticatedUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">user</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// ユーザ情報を用いた処理</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Mock</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> Auth <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'aws-amplify'</span><span class="token punctuation">;</span> jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span>Auth<span class="token punctuation">,</span> <span class="token string">'currentAuthenticatedUser'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">username</span><span class="token operator">:</span> <span class="token string">'myuser'</span><span class="token punctuation">,</span> <span class="token literal-property property">attributes</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">email</span><span class="token operator">:</span> <span class="token string">'test@test.com'</span><span class="token punctuation">,</span> <span class="token literal-property property">email_verified</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token literal-property property">phone_number</span><span class="token operator">:</span> <span class="token string">'08012345678'</span><span class="token punctuation">,</span> <span class="token comment">// ...other attributes</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// ...other parameters</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h4>例 2. GraphQL を用いて AppSync からデータを fetch する</h4> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token constant">API</span><span class="token punctuation">,</span> graphqlOperation <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'aws-amplify'</span><span class="token punctuation">;</span> <span class="token constant">API</span><span class="token punctuation">.</span><span class="token function">graphql</span><span class="token punctuation">(</span><span class="token function">graphqlOperation</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">query ListTodos() { listTodos() { items { id name } nextToken } }</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">result</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// GraphQLのfetch結果の処理</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token parameter">err</span> <span class="token operator">=></span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre></div> <p>Mock</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js">jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span><span class="token constant">API</span><span class="token punctuation">,</span> <span class="token string">'graphql'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">listTodos</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">items</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'todo1'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'todo_name_1'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'todo2'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'todo_name_2'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h3>Amplify から AppSync の Unit テストを書く時に詰まるところ</h3> <p>さて、ここから本題である Amplify AppSync で Unit テストについて説明していきます。上の例で説明した様に基本的には関数(API.graphql)を Mock する方法で問題ありません。しかし、実際にテストを書いていこうとするといくつか問題が出てきます。</p> <p>今回は、ボタンを押すと TODO が登録され、リアルタイムに新しい TODO が表示されるアプリケーションを例に見ていきましょう。</p> <img width="690" alt="スクリーンショット 2019-12-18 19.50.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100291/33ca77d0-b8d2-a813-f2e7-35cb19e431c7.png"> <h4>問題 1. API.graphql を Mock するだけでは一つのテストで複数クエリが発行される場合に対処できない</h4> <p>例えば、コンポーネント作成時にデータの fetch と Subscribe を同時に行うケースなどです。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> useEffect<span class="token punctuation">,</span> useReducer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token constant">API</span><span class="token punctuation">,</span> graphqlOperation <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'aws-amplify'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> listTodos <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./graphql/queries'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> onCreateTodo <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./graphql/subscriptions'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> createTodo <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./graphql/mutations'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> uuidV4 <span class="token keyword">from</span> <span class="token string">'uuid/v4'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> initialState <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">todos</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">reducer</span><span class="token punctuation">(</span><span class="token parameter">state <span class="token operator">=</span> initialState<span class="token punctuation">,</span> action</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>action<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'SET_TODOS'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">todos</span><span class="token operator">:</span> action<span class="token punctuation">.</span>todos <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ADD_TODO'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>state<span class="token punctuation">,</span> <span class="token literal-property property">todos</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>state<span class="token punctuation">.</span>todos<span class="token punctuation">,</span> action<span class="token punctuation">.</span>todo<span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">GraphQLApp</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>state<span class="token punctuation">,</span> dispatch<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useReducer</span><span class="token punctuation">(</span>reducer<span class="token punctuation">,</span> initialState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (1) データをfetch</span> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (2) Subscriptionを開始</span> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> subscription <span class="token operator">=</span> <span class="token constant">API</span><span class="token punctuation">.</span><span class="token function">graphql</span><span class="token punctuation">(</span><span class="token function">graphqlOperation</span><span class="token punctuation">(</span>onCreateTodo<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token function-variable function">next</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">payload</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> todo <span class="token operator">=</span> payload<span class="token punctuation">.</span>value<span class="token punctuation">.</span>data<span class="token punctuation">.</span>onCreateTodo<span class="token punctuation">;</span> <span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'ADD_TODO'</span><span class="token punctuation">,</span> todo <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> subscription<span class="token punctuation">.</span><span class="token function">unsubscribe</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">getData</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token constant">API</span><span class="token punctuation">.</span><span class="token function">graphql</span><span class="token punctuation">(</span><span class="token function">graphqlOperation</span><span class="token punctuation">(</span>listTodos<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'SET_TODOS'</span><span class="token punctuation">,</span> <span class="token literal-property property">todos</span><span class="token operator">:</span> result<span class="token punctuation">.</span>data<span class="token punctuation">.</span>listTodos<span class="token punctuation">.</span>items <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'error fetching todos...'</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">clickButton</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> name <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'_TODO'</span><span class="token punctuation">;</span> <span class="token constant">API</span><span class="token punctuation">.</span><span class="token function">graphql</span><span class="token punctuation">(</span> <span class="token function">graphqlOperation</span><span class="token punctuation">(</span>createTodo<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token function">uuidV4</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> name<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span><span class="token operator">></span> <span class="token operator">&lt;</span>button className<span class="token operator">=</span><span class="token string">"create_button"</span> onClick<span class="token operator">=</span><span class="token punctuation">{</span>clickButton<span class="token punctuation">}</span><span class="token operator">></span> CreateTodo <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span> <span class="token operator">&lt;</span>ul<span class="token operator">></span> <span class="token punctuation">{</span>state<span class="token punctuation">.</span>todos<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">v<span class="token punctuation">,</span> k</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>li key<span class="token operator">=</span><span class="token punctuation">{</span>k<span class="token punctuation">}</span> className<span class="token operator">=</span><span class="token string">"todo"</span><span class="token operator">></span> No<span class="token punctuation">.</span><span class="token punctuation">{</span>k <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">}</span>:<span class="token punctuation">{</span>v<span class="token punctuation">.</span>name<span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>ul<span class="token operator">></span> <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> GraphQLApp<span class="token punctuation">;</span></code></pre></div> <p>このアプリケーションではコンポーネント作成時に 2 種類のクエリを発行しています。「(1) データの fetch」では Query を発行し、「(2) Subscription を開始」 では Subscription を発行しています。API.graphql の Mock には一つのパターンのレスポンスしか記述できないので、別の方法を検討する必要があります。一つのアイディアとして、API.graphql を Mock する代わりに、Model を作成し、この Model を Mock 化します。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token constant">API</span><span class="token punctuation">,</span> graphqlOperation <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'aws-amplify'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> listTodos <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./graphql/queries'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> createTodo <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./graphql/mutations'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> onCreateTodo <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./graphql/subscriptions'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> uuidV4 <span class="token keyword">from</span> <span class="token string">'uuid/v4'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> TodoModel <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token function-variable function">listTodos</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token constant">API</span><span class="token punctuation">.</span><span class="token function">graphql</span><span class="token punctuation">(</span><span class="token function">graphqlOperation</span><span class="token punctuation">(</span>listTodos<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">createTodo</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">name</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token constant">API</span><span class="token punctuation">.</span><span class="token function">graphql</span><span class="token punctuation">(</span> <span class="token function">graphqlOperation</span><span class="token punctuation">(</span>createTodo<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token function">uuidV4</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> name<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">subscribeOnCreateTodos</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token constant">API</span><span class="token punctuation">.</span><span class="token function">graphql</span><span class="token punctuation">(</span><span class="token function">graphqlOperation</span><span class="token punctuation">(</span>onCreateTodo<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> TodoModel<span class="token punctuation">;</span></code></pre></div> <p>GrapQLApp.js からこの Mock を呼び出します。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> useEffect<span class="token punctuation">,</span> useReducer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> TodoModel <span class="token keyword">from</span> <span class="token string">'./TodoModel'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> initialState <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">todos</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">reducer</span><span class="token punctuation">(</span><span class="token parameter">state <span class="token operator">=</span> initialState<span class="token punctuation">,</span> action</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>action<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'SET_TALKS'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">todos</span><span class="token operator">:</span> action<span class="token punctuation">.</span>todos <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ADD_TODO'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>state<span class="token punctuation">,</span> <span class="token literal-property property">todos</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>state<span class="token punctuation">.</span>todos<span class="token punctuation">,</span> action<span class="token punctuation">.</span>todo<span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">GraphQLApp</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>state<span class="token punctuation">,</span> dispatch<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useReducer</span><span class="token punctuation">(</span>reducer<span class="token punctuation">,</span> initialState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> subscription <span class="token operator">=</span> TodoModel<span class="token punctuation">.</span><span class="token function">subscribeOnCreateTodos</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token function-variable function">next</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">payload</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> todo <span class="token operator">=</span> payload<span class="token punctuation">.</span>value<span class="token punctuation">.</span>data<span class="token punctuation">.</span>onCreateTodo<span class="token punctuation">;</span> <span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'ADD_TODO'</span><span class="token punctuation">,</span> todo <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> subscription<span class="token punctuation">.</span><span class="token function">unsubscribe</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">getData</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> TodoModel<span class="token punctuation">.</span><span class="token function">listTodos</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'SET_TALKS'</span><span class="token punctuation">,</span> <span class="token literal-property property">todos</span><span class="token operator">:</span> result<span class="token punctuation">.</span>data<span class="token punctuation">.</span>listTodos<span class="token punctuation">.</span>items <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'error fetching todos...'</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">clickButton</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> TodoModel<span class="token punctuation">.</span><span class="token function">createTodo</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'_TODO'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span><span class="token operator">></span> <span class="token operator">&lt;</span>button className<span class="token operator">=</span><span class="token string">"create_button"</span> onClick<span class="token operator">=</span><span class="token punctuation">{</span>clickButton<span class="token punctuation">}</span><span class="token operator">></span> CreateTodo <span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">></span> <span class="token operator">&lt;</span>ul<span class="token operator">></span> <span class="token punctuation">{</span>state<span class="token punctuation">.</span>todos<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">v<span class="token punctuation">,</span> k</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>li key<span class="token operator">=</span><span class="token punctuation">{</span>k<span class="token punctuation">}</span> className<span class="token operator">=</span><span class="token string">"todo"</span><span class="token operator">></span> No<span class="token punctuation">.</span><span class="token punctuation">{</span>k <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">}</span>:<span class="token punctuation">{</span>v<span class="token punctuation">.</span>name<span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>ul<span class="token operator">></span> <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> GraphQLApp<span class="token punctuation">;</span></code></pre></div> <p>Mock は以下のように記述できます。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token comment">// QueryのMock</span> jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span>TodoModel<span class="token punctuation">,</span> <span class="token string">'listTodos'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">listTodos</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">items</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'todo1'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'todo_name_1'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'todo2'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'todo_name_2'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// SubscriptionのMock (Obervableについては後ほど解説)</span> <span class="token keyword">let</span> emitOnCreateTodos<span class="token punctuation">;</span> jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span>TodoModel<span class="token punctuation">,</span> <span class="token string">'subscribeOnCreateTodos'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">Observable</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">observer</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function-variable function">emitOnCreateTodos</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">v</span><span class="token punctuation">)</span> <span class="token operator">=></span> observer<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span>v<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>このように API.graphql を Mock するのではなく、Model を Mock することで、一度に複数の API.graphql を呼び出す場合のテストにも対処することが可能です。</p> <h4>問題 2. Mutation トリガーの Subscription 処理をどのようにテストするか</h4> <p>GraphQLApp.js の例では、新しく TODO を作成(Mutation)したことをトリガーに Subscription が Mutation の更新を検知し、store の更新を行っています。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">const</span> subscription <span class="token operator">=</span> TodoModel<span class="token punctuation">.</span><span class="token function">subscribeOnCreateTodos</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token comment">// Mutationをトリガーにここ↓が呼び出される</span> <span class="token function-variable function">next</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">payload</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> todo <span class="token operator">=</span> payload<span class="token punctuation">.</span>value<span class="token punctuation">.</span>data<span class="token punctuation">.</span>onCreateTodo<span class="token punctuation">;</span> <span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'ADD_TODO'</span><span class="token punctuation">,</span> todo <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>「Mutation をトリガーに正しく新規 TODO が表示されること」をテストするにはどのようにすれば良いでしょうか?API は Mock されているため、実際に AppSync の Subscription、Mutation を呼び出すわけにはいきません。 「問題 1」でも少し触れましたが、Subscription の Mock は以下のように行っています。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">let</span> emitOnCreateTodos<span class="token punctuation">;</span> jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span>TodoModel<span class="token punctuation">,</span> <span class="token string">'subscribeOnCreateTodos'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">Observable</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">observer</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function-variable function">emitOnCreateTodos</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">v</span><span class="token punctuation">)</span> <span class="token operator">=></span> observer<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span>v<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token function-variable function">subscribeOnCreateTodos</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token constant">API</span><span class="token punctuation">.</span><span class="token function">graphql</span><span class="token punctuation">(</span><span class="token function">graphqlOperation</span><span class="token punctuation">(</span>onCreateTodo<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>API.graphql(graphqlOperation(onCreateTodo))は Subscription クエリを発行する場合、Observable クラスを返却しています。結論から言うと、この Mock で返却している Observable を発火させれば、Mutation をトリガーに store を更新することができる処理をテストできます。</p> <p>テストコードは以下のようになります。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'test subscription'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">act</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>GraphQLApp <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">attachTo</span><span class="token operator">:</span> container <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">waitForExpect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> wrapper<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (1) Mutation発火前の画面に表示されているTODOの数をカウント</span> <span class="token keyword">const</span> initialRenderdTodoLength <span class="token operator">=</span> wrapper<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">'.todo'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token comment">// (2) Observableを発火(擬似的にSubscriptionがAppSyncからメッセージを受け取ったことを再現)</span> <span class="token function">emitOnCreateTodos</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">onCreateTodo</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'todo3'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'todo_name_3'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> wrapper<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (3) 画面に表示されているTODOのカウントが1つ増えていることを確認</span> <span class="token function">expect</span><span class="token punctuation">(</span>wrapper<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">'.todo'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span>initialRenderdTodoLength <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>これで、next: (payload)に emitOnCreateTodos に渡したデータを payload で受け取ることができます。 Query/Mutation/Subscription の全体のテストコードは以下になります。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> MyTest <span class="token keyword">from</span> <span class="token string">'./MyTest'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> waitForExpect <span class="token keyword">from</span> <span class="token string">'wait-for-expect'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> act <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react-dom/test-utils'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> GraphQLApp <span class="token keyword">from</span> <span class="token string">'./GraphQLApp'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> Observable <span class="token keyword">from</span> <span class="token string">'zen-observable'</span><span class="token punctuation">;</span> jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span>TodoModel<span class="token punctuation">,</span> <span class="token string">'listTodos'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">listTodos</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">items</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'todo1'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'todo_name_1'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'todo2'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'todo_name_2'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span>TodoModel<span class="token punctuation">,</span> <span class="token string">'createTodo'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">createTodo</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'todo3'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'todo_name_3'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> emitOnCreateTodos<span class="token punctuation">;</span> jest<span class="token punctuation">.</span><span class="token function">spyOn</span><span class="token punctuation">(</span>TodoModel<span class="token punctuation">,</span> <span class="token string">'subscribeOnCreateTodos'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">mockReturnValue</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">Observable</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">observer</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function-variable function">emitOnCreateTodos</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">v</span><span class="token punctuation">)</span> <span class="token operator">=></span> observer<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span>v<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> wrapper<span class="token punctuation">;</span> <span class="token keyword">let</span> container<span class="token punctuation">;</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'test query'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">act</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>GraphQLApp <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">attachTo</span><span class="token operator">:</span> container <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">waitForExpect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> wrapper<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Queryで取得したTODOデータが2件表示されていること</span> <span class="token function">expect</span><span class="token punctuation">(</span>wrapper<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">'.todo'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'test mutation'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">act</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>GraphQLApp <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">attachTo</span><span class="token operator">:</span> container <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">waitForExpect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> wrapper<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create Todoボタンをクリック</span> wrapper<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">'.create_button'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">first</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">simulate</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Mutationが発行されたこと(対象の関数が呼び出されたこと)</span> <span class="token function">expect</span><span class="token punctuation">(</span>TodoModel<span class="token punctuation">.</span>createTodo<span class="token punctuation">.</span>mock<span class="token punctuation">.</span>calls<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'test subscription'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">act</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> wrapper <span class="token operator">=</span> <span class="token function">mount</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>GraphQLApp <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">attachTo</span><span class="token operator">:</span> container <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">waitForExpect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> wrapper<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (1) Mutation発火前の画面に表示されているTODOの数をカウント</span> <span class="token keyword">const</span> initialRenderdTodoLength <span class="token operator">=</span> wrapper<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">'.todo'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token comment">// (2) Observableを発火(擬似的にSubscriptionがAppSyncからメッセージを受け取ったことを再現)</span> <span class="token function">emitOnCreateTodos</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">value</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">onCreateTodo</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'todo3'</span><span class="token punctuation">,</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'todo_name_3'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> wrapper<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (3) 画面に表示されているTODOのカウントが1つ増えていることを確認</span> <span class="token function">expect</span><span class="token punctuation">(</span>wrapper<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token string">'.todo'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span>initialRenderdTodoLength <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>テストを実行してみます</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">yarn</span> <span class="token builtin class-name">test</span></code></pre></div> <p>無事全てのテストがパスしました!</p> <img width="518" alt="スクリーンショット 2019-12-08 21.04.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100291/28350c93-13ce-ee33-bb86-8600f96dd61f.png"><![CDATA[[Amplify] @httpディレクティブでCognito Authorizer付きAPI Gatewayを Amplify Libraryから呼び出そうとすると"Template transformation yielded an empty response."エラーになる]]>https://whnyab.com/appsync_apigw/https://whnyab.com/appsync_apigw/Sat, 14 Nov 2020 22:12:03 GMT<p>掲題の通り、Amplify の @http ディレクティブを使って AppSync から Cognito Authorizer を指定した API Gateway を Amplify Library から呼び出そうとすると “Template transformation yielded an empty response.” になりました。</p> <p>AppSync ログを Cloudwatch Logs に出力し、原因を調べたところ、どうやら resolver の中で Unauthorized エラーになっているようでした。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; " > <a class="gatsby-resp-image-link" href="/static/2535a5d424eaafb0ad93996cddeb9370/3f3b9/unauthorized.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 52.02702702702703%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABaUlEQVQoz32S3XKbMBBG/f7P1sSdTmIHG5D4MRghQCCJXJ6OsM0QN+nFpxWCOXNWy845xzROiCxD1oJC5XSD5tN/Lpn9jHd+TfjeWrvWx34whq7r2YUH3Wui9IS4CI7lgag8ktYJWSNpuit60PRjT2c6hnHAWYd19gs4VO/9DdibnmMcEcuYXOUUbU7Zlre9KlB9gzaadmgX6Nbu2XYXlkA2ZkTKnLpqGHpzM3FuaXm+t/5ION+abbN7vJjnGaVazmnCQbyzFy+8pr/4KI5cuxrVq8Uw2Ib63O4KtPfDYKlaTXSOSbKUV/HCb7nnvXgjrk7kKiNrsuVea139Bxh6dxZvHe21ITknSClIixMH+YesSilVsUCC5TrpzVD+Abp5Zixqyv2BS3SmihN0KmiSBBNL5s7gvMdtBvGjobsbXquaiyyYdIcbxjW2N9hpwv7w/307FGMMcSrILyWqLRntdM+4gFbIN4Bn4F+A//Q4fG8xmAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="unauthorized" title="unauthorized" src="/static/2535a5d424eaafb0ad93996cddeb9370/fcda8/unauthorized.png" srcset="/static/2535a5d424eaafb0ad93996cddeb9370/12f09/unauthorized.png 148w, /static/2535a5d424eaafb0ad93996cddeb9370/e4a3f/unauthorized.png 295w, /static/2535a5d424eaafb0ad93996cddeb9370/fcda8/unauthorized.png 590w, /static/2535a5d424eaafb0ad93996cddeb9370/3f3b9/unauthorized.png 870w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>AppSync の Authorizer に Cognito UserPool を指定する場合、Authorization ヘッダーの認証情報は API Gateway にも引き継がれるので問題ないかと思っていましたが、同じ Cognito UserPool を Authorizer に指定しているにも関わらず Unauthorized エラーになります。</p> <p>原因としては、AppSync は<code class="language-text">access token</code>を用いて認証しているのに対し、API Gateway は<code class="language-text">Id Token</code>を使用しているためでした。</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 572px; " > <a class="gatsby-resp-image-link" href="/static/0662af2dd797cbf5818c0f0456379395/a805e/architecture.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 29.72972972972973%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABL0lEQVQY012RzUrDQBSF80KCIF35LH0GxYW4cVHEvT6DuBfduNBWCkLQWvpDbUmkLbWEWppW6+R3ZvJJoqa0dzXDOffeM98Y5JWwVsn6fUMl0XrD/uswtNYopbKGIIzw/AAhfBQQDT4QVyay+gqxxg8j3PmCKIqz5uD5heD6Bt1o5kMNy7Ko1Wp4ywXvdod+t8mg0+BbTJld3NPb3sMtnqPHE5Zzh2G3xddkBEnI+OCIcWEXUTrJAqWpDdu2qVQqyDhaPU6r7ORclqnv7DMunpFMP1e6+k04Ojymu1VgUTrNMRmu6+I4ThY33aL/WERS0qu3aN9Wad89Mp+5OS/fD+gPh9jmE3b5Acs0sd5sPM/D2AT7Dzdlmw6NUAQyJo7j3JdqaXMoJRIIlUQIkf3FD4wSwKeHrfC0AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="architecture" title="architecture" src="/static/0662af2dd797cbf5818c0f0456379395/a805e/architecture.png" srcset="/static/0662af2dd797cbf5818c0f0456379395/12f09/architecture.png 148w, /static/0662af2dd797cbf5818c0f0456379395/e4a3f/architecture.png 295w, /static/0662af2dd797cbf5818c0f0456379395/a805e/architecture.png 572w" sizes="(max-width: 572px) 100vw, 572px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Amplify Library で、AppSync のカスタムヘッダーに Id Token を指定すると解決しました。</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js">Amplify<span class="token punctuation">.</span><span class="token function">configure</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token constant">API</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">aws_appsync_authenticationType</span><span class="token operator">:</span> <span class="token string">'AMAZON_COGNITO_USER_POOLS'</span><span class="token punctuation">,</span> <span class="token literal-property property">aws_appsync_graphqlEndpoint</span><span class="token operator">:</span> <span class="token string">"https://xxxxxxxxxxxxxxxx.appsync-api.us-east-1.amazonaws.com/graphql"</span><span class="token punctuation">,</span> <span class="token literal-property property">aws_appsync_region</span><span class="token operator">:</span> <span class="token string">"us-east-1"</span><span class="token punctuation">,</span> <span class="token comment">// Authorization HeaderにIdTokenを指定</span> <span class="token function-variable function">graphql_headers</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> session <span class="token operator">=</span> <span class="token keyword">await</span> Auth<span class="token punctuation">.</span><span class="token function">currentSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token literal-property property">Authorization</span><span class="token operator">:</span> session<span class="token punctuation">.</span><span class="token function">getIdToken</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getJwtToken</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>snip<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span></code></pre></div> <p>複数のマイクロサービスを一つの AppSync で IF を統一するなど、クライアントにバックエンドを意識させない仕組みとして@http ディレクティブは非常に有用だと思うので今後も積極的に活用していきたいと思います。</p>