<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[VELTRA Engineering - Medium]]></title>
        <description><![CDATA[Posts from the VELTRA Engineering team. www.veltra.com - Medium]]></description>
        <link>https://medium.com/veltra-engineering?source=rss----96a0b0173423---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>VELTRA Engineering - Medium</title>
            <link>https://medium.com/veltra-engineering?source=rss----96a0b0173423---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 12 Nov 2026 21:02:28 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/veltra-engineering" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Try Golang! JSONデコード時のキーの一致判定]]></title>
            <link>https://medium.com/veltra-engineering/how-to-match-keys-when-decoding-json-4aae72983315?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/4aae72983315</guid>
            <category><![CDATA[日本語]]></category>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[json]]></category>
            <category><![CDATA[go]]></category>
            <dc:creator><![CDATA[Takuo]]></dc:creator>
            <pubDate>Thu, 21 May 2020 01:01:01 GMT</pubDate>
            <atom:updated>2020-05-21T01:01:01.016Z</atom:updated>
            <content:encoded><![CDATA[<h4>How to match keys when decoding JSON</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*2KIIl0wcT1IVuHrl1vwpkg.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@blakeliskphoto?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Blake Lisk</a> on <a href="https://unsplash.com/">Unsplash</a></figcaption></figure><p>先日、こわーい先輩からこんな指摘を受けました。</p><blockquote><em>APIの仕様書ではJSONのキーが “id” になっているけど、 “Id” で呼び出して動いたんだけど、どうなってんの？ちゃんとしてよ！</em></blockquote><p>あれ？JSONタグミスったかな？と思いながら調べてみると、ちょっと面白い発見があったので、今日はそのお話です。（ちなみにJSONタグはミスっていませんでした）</p><p>なお、本事象は、執筆時点の最新である go1.14.3 で確認しています。</p><h4>GoでJSONを扱う時のおさらい</h4><p>GoでJSONをデコードする場合は、マップを利用することもできますが、次のような感じで構造体を定義しておくことが多いのかなと思います。</p><pre>type foo strcut {<br>  ID    string `json:&quot;id&quot;`<br>  Value string `json:&quot;value&quot;`<br>}</pre><p>`json:&quot;id&quot;` といった記述は、JSON形式に変換する場合のキー名を指定するJSONタグです。大文字小文字、スネークケースなど、Goのフィールド名に依存させたくない場合に使用します。</p><p>このように構造体を定義しておけば、次のようなJSONをデコードすることができます。</p><pre>{&quot;id&quot;:&quot;xxx&quot;,&quot;value&quot;:&quot;yyy&quot;}</pre><p>実際の挙動は<a href="https://play.golang.org/p/d_mPjBCQvcM">Playground</a>で確認できます。</p><h4>本当に“id”ではなくて”Id”で読み込めてしまうのか</h4><p>では、本題に戻りましょう。<a href="https://play.golang.org/p/uOoEKudJEfg">Playground</a>上で、JSONのキーを次のように変更しても、確かに問題なくデコードできていることが分かります。</p><pre>{&quot;Id&quot;:&quot;xxx&quot;,&quot;Value&quot;:&quot;yyy&quot;}</pre><p>実は Id だけでなく、 ID も iD も、全て構造体の ID フィールドにマッピングされます。どうやら大文字小文字は気にしていないようです。</p><p>では試しに、大文字小文字をごちゃ混ぜにした、次のようなJSONを読み込ませてみましょう。</p><pre>{&quot;id&quot;:&quot;a&quot;,&quot;ID&quot;:&quot;b&quot;,&quot;Id&quot;:&quot;c&quot;,&quot;iD&quot;:&quot;d&quot;,&quot;vAlUe&quot;:&quot;yyy&quot;}</pre><p>結果は下記のようになります。どうやら同じフィールドにマッピングされるキーが複数存在した場合は、後勝ちになるようです。</p><pre>{ID:d Value:yyy}</pre><h4>標準パッケージを読み解いてみる</h4><p>Goの標準パッケージ encoding/json を読み進めていくと、該当のロジックは下記にありました。</p><p><a href="https://github.com/golang/go/blob/go1.14.3/src/encoding/json/decode.go#L713-L726">golang/go</a></p><p>「JSONのキー名のマップから取得ができなかった場合に、大文字小文字を無視してマッピングしますよ」とコメントにも記載されています。さらに、<a href="https://golang.org/pkg/encoding/json/#Unmarshal">Godoc</a>にも下記のように記載されています。</p><blockquote>…, preferring an exact match but also accepting a case-insensitive match.</blockquote><p>「完全一致を優先し」てはいないような気もしますが、ひとまず今回発生した事象についてはこれで説明ができました。</p><h4>今の挙動は今後変更されるかもしれない</h4><p>実はGitHubに、今回の事象についての<a href="https://github.com/golang/go/issues/14750">Issue</a>があります。やはり皆さん、今の挙動に納得していない様子。デコーダーに UseStrictNames メソッドを追加して大文字小文字を無視しないオプションを用意する案や、JSONタグでフィールド毎に個別に制御する案が上がっていますが、まだどのように対応されるかよく分からない感じです。</p><p>ただ、「完全一致のフィールドが見つかった後は、不完全な一致（大文字小文字を無視した一致）のデコードをスキップする」という修正が<a href="https://go-review.googlesource.com/c/go/+/224079">Change List</a>にあがっているため、単純な後勝ちになる今の挙動は、もしかしたら変更されるかもしれません。</p><p>大文字小文字を無視するとキーが重複してしまうようなJSONを使用することはなかなかないと思うので、今の挙動が問題となることは少ないように思います。ただ、今の仕様を頭の片隅に置いておいてあげると、こわーい先輩に指摘されても、冷静に回答できるかもしれませんね。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4aae72983315" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/how-to-match-keys-when-decoding-json-4aae72983315">Try Golang! JSONデコード時のキーの一致判定</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GoのMustacheエンジンにマージされたプルリクエストの話]]></title>
            <link>https://medium.com/veltra-engineering/go-mustache-engine-pr-3e974a3bb730?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/3e974a3bb730</guid>
            <category><![CDATA[go]]></category>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[pull-request]]></category>
            <category><![CDATA[mustache]]></category>
            <category><![CDATA[日本語]]></category>
            <dc:creator><![CDATA[Takashi Iwamoto]]></dc:creator>
            <pubDate>Mon, 18 May 2020 01:01:01 GMT</pubDate>
            <atom:updated>2020-05-18T01:01:01.443Z</atom:updated>
            <content:encoded><![CDATA[<p>放置されたissueは、伸びきったひげのようだ</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ao9TrmBpOdeCXArWkA8n1Q.jpeg" /></figure><p>マスク生活のおかげで、ひげを伸ばしたい放題のイワモトです。先日、GoのMustacheテンプレートエンジンにプルリクエストを送ったところ、マージされました。その話を書きます。小ネタです。</p><h3>Mustacheとは？</h3><p>Mustacheはプログラミング言語や用途に依存しない汎用的なテンプレート仕様です。40以上のプログラミング言語で処理系が実装されています。</p><p>構文に {{var}} のように波括弧が頻出し、{ が横向きの口ひげに見えることから、口ひげを表すMustacheと名づけられました。</p><h3>どんなプルリクエスト？</h3><p>ぼくが送ったプルリクエストはこちらです。写真はメンテナの方で、ぼくではありません。</p><p><a href="https://github.com/cbroglie/mustache/pull/39">Support missing variables at any depth by iwamot · Pull Request #39 · cbroglie/mustache</a></p><p>こんなテンプレートに対して：</p><pre>You have {{count}} items:<br>{{#sections}}<br>--{{name}}--<br>{{#items}}<br>{{.}}<br>{{/items}}<br>{{/sections}}</pre><p>下記のコードを実行した場合：</p><pre><strong>mustache.AllowMissingVariables = false</strong><br>s, err := mustache.Render(tmpl, map[string]interface{}{<br>    &quot;count&quot;: 2,<br>    &quot;sections&quot;: map[string]interface{}{<br>        //&quot;name&quot;: &quot;Stuff&quot;,<br>        &quot;items&quot;: []string{&quot;foo&quot;, &quot;bar&quot;},<br>    },<br>})<br>fmt.Println(s, err)</pre><p>sections.name が存在しないのだからエラーを返してほしい、という<a href="https://github.com/cbroglie/mustache/issues/12">issue</a>が立ったのが2016年10月のこと。</p><p>メンテナから「I agree this is a valid issue.」「PRs are welcome 😄」と返信があったものの、ずっと放置されていました。</p><p>2020年4月、GoのMustacheについて調べていたぼくは、たまたまこのissueを見つけました。むむ、これは対応できそうな気がするぞ。</p><p>さっそく手もとで試したところ、エラーハンドリングの追加と、テストコードの移動だけでエラーを返せることがわかりました。なかなかエレガントな<a href="https://github.com/cbroglie/mustache/pull/39/files">diff</a>ではないでしょうか。</p><p>すぐにプルリクエストを送ったものの、メンテナが忙しそうなので、反応は期待しませんでした。そして送ったのを忘れた頃、マージ完了の通知が来た次第です。送ってみるものですね 🧔</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3e974a3bb730" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/go-mustache-engine-pr-3e974a3bb730">GoのMustacheエンジンにマージされたプルリクエストの話</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[東大松尾研の「GCIデータサイエンティスト育成講座」(無料)を修了しました]]></title>
            <link>https://medium.com/veltra-engineering/cig-data-scientist-curriculum-fdc65d2a5116?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/fdc65d2a5116</guid>
            <category><![CDATA[kaggle-competition]]></category>
            <category><![CDATA[日本語]]></category>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[gci]]></category>
            <category><![CDATA[機械学習]]></category>
            <dc:creator><![CDATA[Miwa Sakamoto]]></dc:creator>
            <pubDate>Thu, 30 Apr 2020 01:01:01 GMT</pubDate>
            <atom:updated>2020-04-30T01:01:01.229Z</atom:updated>
            <content:encoded><![CDATA[<h4>Pythonの基礎からデータ処理と機械学習</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*glmiEGh6OLR8V39w_4oSwg.jpeg" /></figure><h4><strong>はじめに</strong></h4><p>Pythonとデータ処理や機械学習に興味があり、東大の松尾研が主催しているGCIデータサイエンティスト育成講座を受講しました。コロナの影響で授与式兼交流会が延期になったため修了証を受け取っていませんが、受講した感想を書いてみます。</p><h4><strong>講座の受講の仕方</strong></h4><p><a href="https://gci.t.u-tokyo.ac.jp/">東京大学グローバル消費インテリジェンス寄附講座</a></p><p>WEBの募集フォームから応募します。応募の際、簡単なPythonの Numpyを使った10問ほどのテストを受けます。時間は結構ありそれほどプログラム的には難しくない問題でした。その後合格した方にメールが送られてきて、受講可能となります。</p><p><strong>対象の受講者</strong></p><p>現役東大生および一般社会人</p><p><strong>受講方法</strong></p><p>東大生向けオフライン授業と一般向けのオンライン講座を同期して行われました。一般社会人はオフライン授業の動画を後日オンラインでみることができました。</p><h4><strong>コースカリキュラム</strong></h4><pre>1. 本講座の概要とPythonの基礎</pre><pre>2. 科学計算、データ加工、グラフ描画ライブラリの使い方の基礎</pre><pre>3. Pythonによる科学計算（NumpyとScipy）</pre><pre>4. Pandasを使ったデータ加工処理</pre><pre>5. Matplotlibを使ったデータ可視化</pre><pre>6. 統計・確率</pre><pre>7. 機械学習の基礎（教師あり学習）</pre><pre>8. 機械学習の基礎（教師なし学習）</pre><pre>9. モデルの検証方法とチューニング方法</pre><pre>10. データサイエンティスト中級者への道</pre><p>週１回の講義で配信されるチャプターを独自で読んで学習し、その講義に対して毎回宿題が出る形でした。また宿題の他に「コンペ」と呼ばれる機械学習の精度を競う課題が期間中に計３回、最後に最終課題が出されました。コンペではKaggleを模した形式になっており、実際のデータを分析して他の生徒とスコアを競うものでした。</p><h4><strong>Kaggleとは</strong></h4><p>世界中の機械学習・データサイエンスのコミュニティです。企業や政府のプロのデータサイエンティストや機械学習エンジニアを繋げるプラットフォームです。Kaggleの特徴の一つであるCompetition（コンペ）により、企業や政府がコンペ形式（競争形式）で課題を提示し、賞金と引き換えに最も制度の高い分析モデルを買い取るという、最近でいう一種のクラウドファンディングに近いような仕組みとなっています。</p><p><a href="https://www.kaggle.com/">Kaggle: The World&#39;s AI Proving Ground</a></p><p>また今回の講座で使っている教材はこちらのページで無料公開されています。よって誰でも勉強することができます。 （演習課題とコンペ以外）</p><p><a href="https://weblab.t.u-tokyo.ac.jp/gci_contents/">グローバル消費インテリジェンス寄附講座演習コンテンツ 公開ページ</a></p><h4>学習環境</h4><p>学習環境は<strong>iLect</strong>とSlackが提供されました。その中で<strong>Jupiter Notebook</strong>と同じプログラムの実行環境が構成されていて、提供された教科書を読んだ後、プログラムを実行しながら、講義を理解していく形です。</p><ul><li><a href="https://www.ilect.net/">iLect by NABLAS | AI人材育成, Deep Learning, データサイエンス | 日本</a></li><li><a href="https://jupyter.org/">Project Jupyter</a></li></ul><p>また、Googleが無料で公開しているJupiter Notebook環境である<strong>Colaboratory</strong>で同様に動かすことができるようです。</p><p><a href="https://colab.research.google.com/">Google Colab</a></p><p>講座内容、宿題、コンペの疑問、質問、問題などをオンラインでSlackを通じて行いました。 基本的には自習する形なのですが、Slackで受講生たちがTAに自由に質問したり生徒間で意見交換できたりしました。</p><h4>実際受講した感想</h4><p>最初授業が始まる前にSlackで受講生たちの自己紹介があったのですが、大学院の研究者だとか企業の研究員、病院の医者などそうそうたるメンバーだったので、授業についていけるのだろうか？ととても不安に思っていました。また、フルタイムで働いた後、家事・育児に追われる中、講義の復習、宿題、コンペをやる時間が本当に無く、寝る間も惜しんで勉強していた本当につらい３ヶ月でした。しかし、iLectの使いやすさとSlackで他の方々からのヘルプによりどうにかドロップアウトせず最後まで続け、無事修了することができました。</p><h4>今後</h4><p>今回のこの講座からPythonと機械学習について基礎を学びことができました。今コロナが流行るアメリカでは、ホワイトハウスがKaggleで賞金ありの分析コンペを公開しています。</p><p><a href="https://www.kaggle.com/allen-institute-for-ai/CORD-19-research-challenge">COVID-19 Open Research Dataset Challenge (CORD-19)</a></p><p>今後はさらに機械学習を学んで、このようなKaggleに挑戦できたら良いと思います。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fdc65d2a5116" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/cig-data-scientist-curriculum-fdc65d2a5116">東大松尾研の「GCIデータサイエンティスト育成講座」(無料)を修了しました</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fat Controllerをコードで予防する方法はないか？]]></title>
            <link>https://medium.com/veltra-engineering/intent-response-pattern-c1be6aa9f2e5?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/c1be6aa9f2e5</guid>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[web-framework]]></category>
            <category><![CDATA[mvc]]></category>
            <category><![CDATA[日本語]]></category>
            <category><![CDATA[design-patterns]]></category>
            <dc:creator><![CDATA[Takashi Iwamoto]]></dc:creator>
            <pubDate>Mon, 06 Apr 2020 01:01:01 GMT</pubDate>
            <atom:updated>2021-03-13T08:01:02.040Z</atom:updated>
            <content:encoded><![CDATA[<h3>Fat Controllerをコードで防ぐ方法はないか？</h3><h4>Intent-Responseパターンの提案</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QlsDy15O50xKG4zOQFFirA.jpeg" /></figure><p>昨秋に受けた人間ドックの結果、メタボ予防の指導対象となったイワモトです。半年間の指導が先日終わりました。</p><p>人間のファットも問題ですが、Webアプリにおけるコントローラのファットも問題です。保守性が下がり、エンバグの危険性が高まるためです。書籍『<a href="https://www.amazon.co.jp/dp/B004C04QE0">Rails AntiPatterns</a>』でも、Railsのアンチパターンのひとつとして「Fat Controller」が挙げられています。</p><p><strong>Fat Controllerの予防には「ビジネスロジックやプレゼンテーションロジックをコントローラに書かない」ルールを守るのが効果的です。</strong>前者はモデル、後者はビューの責務です。このルールを守れば、コントローラをスリムに保てます。</p><p>ただ、ルールを守るといっても、なかなか大変です。プロジェクトに携わるすべてのプログラマにルールを守らせるには、相応のコストがかかるでしょう。ルールの周知、ルール違反の検知・修正といったコストです。</p><p><strong>何か、頑張らなくてもルールが守られる実装パターンは考えられないものでしょうか？</strong> もし、そのようなパターンで実装されたフレームワークなりミドルウェアなりが使えたら、コストをかけずにFat Controllerが予防できます。</p><h3>Intent-Responseパターン</h3><p>そこで思いついたのが「Intent-Responseパターン」です。以下、詳しく説明します。</p><h4>Intent</h4><ul><li>ビジネスロジックを担当</li><li>コントローラのアクションから即座に呼び出される</li><li>HTTPリクエストを受け、HTTPステータスコードとビジネスロジックの実行結果（outcome）を返す</li></ul><h4>Response</h4><ul><li>プレゼンテーションロジックを担当</li><li>Intentが返したHTTPステータスコードをもとに、コントローラから呼び出される</li><li>HTTPリクエストとoutcomeを受け、HTTPヘッダとレスポンスボディを返す</li></ul><h4>コントローラ</h4><ul><li>IntentとResponseの呼び出しを担当</li><li>HTTPリクエストを受け、呼び出すべきIntentを決めて呼び出す</li><li>Intentから返されたHTTPステータスコードをもとに、呼び出すべきResponseを決めて呼び出す</li></ul><h3>Intent-Responseパターンの実装例</h3><p>言葉だけでは分かりづらいので、実装例として、Flaskの拡張機能を作ってみました。flask-skinnyです。</p><p><a href="https://github.com/iwamot/flask-skinny">iwamot/flask-skinny</a></p><p>インポートすると、Flaskアプリを下記のように実装できます。</p><pre>from flask import Flask<br><strong>from flask_skinny import skinny</strong><br>from random import randint<br>import json<br><br><br><strong>def intent(request):<br>    if randint(0, 1) == 0:<br>        status_code = 200<br>        outcome = &quot;OK&quot;<br>    else:<br>        status_code = 403<br>        outcome = &quot;Forbidden&quot;<br>    return status_code, outcome<br><br><br>def response(request, outcome):<br>    headers = {&quot;content-type&quot;: &quot;application/json&quot;}<br>    body = json.dumps({&quot;message&quot;: outcome}) + &quot;\n&quot;<br>    return headers, body</strong><br><br><br>app = Flask(__name__)<br><br><br>@app.route(&quot;/&quot;, methods=[&quot;GET&quot;])<strong><br>@skinny.responses({200: response, 403: response})<br>@skinny.intent(intent)<br>def index():<br>    pass</strong></pre><p><strong>ご覧の通り、コントローラのアクションには「pass」しか書かれていません。究極のSkinny Controllerといえます。</strong>pass以外のコードが書かれていたらルール違反であり、その検知や修正は簡単です。</p><p>アクションには @skinny.intent デコレータが指定されています。デコレータは自動的に intent 関数を呼び出します。</p><p>intent 関数は、50%の確率で 200, &quot;OK&quot; もしくは 403, &quot;Forbidden&quot; を返します。</p><p>次のデコレータは @skinny.responses です。自動的に response 関数を呼び出します。今回は説明を簡単にするため、200 でも 403 でも response 関数を呼び出すようにしました。</p><p>response 関数は、Intentの実行結果（outcome）をJSON文字列に変換し、HTTPヘッダとともに返します。それらの戻り値がアクションの戻り値となります。</p><p>上記アプリを起動し、アクセスすると、下記のような結果が得られます。</p><pre>$ curl -v http://localhost:3000/<br>*   Trying 127.0.0.1...<br>* TCP_NODELAY set<br>* Connected to localhost (127.0.0.1) port 3000 (#0)<br>&gt; <strong>GET /</strong> HTTP/1.1<br>&gt; Host: localhost:3000<br>&gt; User-Agent: curl/7.58.0<br>&gt; Accept: */*<br>&gt;<br>* HTTP 1.0, assume close after body<br>&lt; HTTP/1.0 <strong>200 OK</strong><br>&lt; <strong>content-type: application/json</strong><br>&lt; Content-Length: 18<br>&lt; Server: Werkzeug/1.0.0 Python/3.8.2<br>&lt; Date: Mon, 30 Mar 2020 08:15:12 GMT<br>&lt;<br><strong>{&quot;message&quot;: &quot;OK&quot;}</strong><br>* Closing connection 0</pre><h3>想定される質問</h3><h4><strong>Intent-Responseパターンのメリットは何ですか？</strong></h4><p>以下のメリットがあります。</p><ul><li>Skinny Controllerが実現できる</li><li>プログラマがIntentとResponseの実装に集中できる</li><li>フレームワークへの依存度が下がる</li></ul><h4>なぜIntentをModel、ResponseをViewと呼ばないのですか？</h4><p>そう呼んでもよいのですが、既存のMVCフレームワークとの違いを明確にしたく、別の名前を選びました。</p><h4>Fat Controllerは予防できても、Fat IntentとFat Responseを生むのではないですか？</h4><p>それぞれの責務が守られていれば問題ないと思いますが、コード量が増えて読みづらければ分割すべきでしょう。</p><h4>Intent-ResponseパターンはRailsのようなフルスタックフレームワークでも使えますか？</h4><p>使えると思います。が、そうすべきかどうか、実際のところは実装してみないと分かりません。</p><h4>IntentでHTTPステータスコードを返すのは適切なのでしょうか？</h4><p>Intentの実装者にとって分かりやすいだろうと判断して採用しました。結果コードのようにResponseの選択に使える値であれば、何でもよいとは思います。</p><h4>なぜResponseにrequestを渡すのですか？</h4><p>プレゼンテーションロジックにも、HTTPリクエストの内容が必要となる処理がありえるからです。たとえば表示言語は、ビジネスロジックでは不要でも、プレゼンテーションロジックでは必要になるでしょう。</p><h4>Response内でHTTPステータスコードを変えたいのですが？</h4><p>はい、その必要があるなら、Responseのインターフェイスを変えても構いません。</p><pre><strong>def response(status_code, request, outcome):<br></strong>    headers = {&quot;content-type&quot;: &quot;application/json&quot;}<br>    body = json.dumps({&quot;message&quot;: outcome}) + &quot;\n&quot;<strong><br>    return status_code, headers, body</strong></pre><h4>flask-skinnyについて、Intentが受け取るrequestは何ですか？</h4><p>flask.requestにしました。Flaskへの依存をなくすのが最優先なら、WSGIのenvironを渡すべきです。が、flask-skinnyはFlaskの拡張機能なので、その必要はないと判断しました。</p><h3>おわりに</h3><p>この記事では、Fat Controllerの予防策として、Intent-Responseパターンを提案しました。また、実装例のflask-skinnyもご紹介しました。</p><p>ただし、このパターンで書いたWebアプリを実運用しているわけではないので、考慮不足があるかもしれません。お気づきの点がありましたら、コメントいただければ幸いです。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c1be6aa9f2e5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/intent-response-pattern-c1be6aa9f2e5">Fat Controllerをコードで予防する方法はないか？</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCPを使ってるならCloud Buildがいいんじゃない？]]></title>
            <link>https://medium.com/veltra-engineering/why-not-use-cloud-build-for-gcp-projects-b95b1f708fd0?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/b95b1f708fd0</guid>
            <category><![CDATA[cloud-build]]></category>
            <category><![CDATA[ci-cd-pipeline]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[日本語]]></category>
            <dc:creator><![CDATA[Takuo]]></dc:creator>
            <pubDate>Tue, 24 Mar 2020 01:01:01 GMT</pubDate>
            <atom:updated>2020-03-24T01:01:01.101Z</atom:updated>
            <content:encoded><![CDATA[<h4>Why not use Cloud Build for GCP projects?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*xnfz6-AFSWY8e-IiW3IflQ.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@duck58cth?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Alphacolor</a> on <a href="https://unsplash.com/">Unsplash</a></figcaption></figure><p>Continuous Integration/Continuous Delivery (CI/CD)について、筆者はこれまで<a href="https://circleci.com/ja/">CircleCI</a>をよく使っていたのですが、最近GCPの<a href="https://cloud.google.com/cloud-build?hl=ja">Cloud Build</a>を触る機会があり、思っていた以上に便利だったので、その紹介をしたいと思います。今、GCP + CircleCIを使っている人が、Cloud Buildを検討するきっかけになれば幸いです。</p><p>結論を一言で言うと、「GCPを使っているなら、Cloud Buildを使った方が便利だよ」ということ。当たり前のことかもしれませんが、便利だと感じた３つの点について、CircleCIと比較しながら紹介していきます。</p><h4>便利なこと その１：１ステップ１コンテナ</h4><p>CI/CDのフローを実装する時、一般的には「ビルド」「テスト」「デプロイ」といった複数のステップが必要になることが多いと思います。この時、ビルドやテストはGoが必要で、デプロイにはGoogle Cloud SDKが必要で、といった風に、複数のツールが必要になることになります。</p><p>CircleCIの場合、ジョブ単位で仮想マシンまたはDockerイメージを指定するため、これを実現するためには、ひとつの仮想マシン上に全てのツールをインストールしてあげるか、特定のDockerコンテナ（例えば<a href="https://hub.docker.com/r/google/cloud-sdk/">Google Cloud SDKのDockerコンテナ</a>）に残りのツールをインストールしてあげるといった実装が必要になります。他にも、全てのツールがインストールされたDockerイメージを用意してそれを使用するとか、ジョブを分けて書くといった方法でも実現が可能ですが、いずれも管理が面倒だったり、制約があってうまく実現できなかったりすることがあります。</p><p>一方で、Cloud Buildの場合、ステップ単位でイメージを指定するため、ビルドとテストはGoのコンテナ上で実行し、デプロイはGoogle Cloud SDKのコンテナ上で実行するといった書き方が簡単にでき、ツールのインストールといった面倒な準備が不要となります。例えばこんな感じ。すごいシンプルですね。</p><pre>steps:<br>  - name: &#39;golangci/golangci-lint&#39;<br>    id: &#39;lint&#39;<br>    args: [&#39;golangci-lint&#39;, &#39;run&#39;]<br>  - name: &#39;golang&#39;<br>    id: &#39;test&#39;<br>    args: [&#39;go&#39;, &#39;test&#39;, &#39;./...&#39;, &#39;-v&#39;]<br>  - name: &#39;golang&#39;<br>    id: &#39;build&#39;<br>    args: [&#39;go&#39;, &#39;build&#39;]<br>  - name: &#39;gcr.io/cloud-builders/gcloud&#39;<br>    id: &#39;deploy&#39;<br>    args: [&#39;app&#39;, &#39;deploy&#39;]</pre><h4>便利なこと その２：設定ファイルの分割</h4><p>CircleCIの場合、フローは .circleci/config.yml に記述する必要があります。一方で、Cloud Buildの場合は、トリガーを複数作成することができ、それぞれのトリガーに対して実行するファイル名を指定できるので、デフォルトの cloudbuild.yaml というファイル名でなくても構いません。ディレクトリも含め、 build/cloudbuild-prod.yaml 、 build/cloudbuild-dev.yaml といった風にカスタマイズできます。複数のプロジェクトに同じリポジトリのトリガーを作成することもできます。</p><p>実はCloud buildでは、CircleCIでは当たり前の条件分岐（「 master ブランチの場合だけ実行する」といった分岐）を書くことができません。その代わりといってはなんですが、 master ブランチの場合に実行するファイルと、それ以外のブランチの場合に実行するファイルを分けて記述することで、同等の挙動を実現することができます。</p><p>「条件分岐を書けない代わりに、設定ファイルを分割できる」という仕様は一長一短で、人によって好みが分かれるとは思いますが、設定ファイルが長くなってくると途端に読みにくさが倍増するので、分割できること自体はありがたいなぁと思います。</p><h4>便利なこと その３：GCP IAMとの親和性</h4><p>最後に持ってきましたが、一番便利なのはこれです。Cloud Buildは、実行時にCloud Buildのサービスアカウントをデフォルトで利用できるのです。</p><p>CircleCIの場合、CircleCI用のサービスアカウントを作成して、サービスアカウントの鍵をダウンロードして、環境変数に鍵を設定して、それを実行中に読み込んで、 gcloud コマンドで認証して、と複数の手順が必要でしたが、Cloud Buildなら面倒な初期設定がごっそり不要になります。</p><p>GCPを使用しているのであれば、GAEへのデプロイや、GCPのデータベースに接続するテスト、KMSを用いた秘匿情報の暗号/複合等々、何かとGCPのサービスに依存することが多くありますが、Cloud Buildなら自動的に作成されるCloud Buildのサービスアカウントに必要な権限を付与してあげるだけ。これはとても楽ちんですね。</p><p>本記事では、CircleCIと比べたCloud Buildの便利な点を３つ紹介しました。ただ、今回は触れませんでしたが、Cloud Buildがいまいちな点ももちろんあります（キャッシュ機能がない等）。また、GitHubとの連携については、<a href="https://github.co.jp/features/actions">GitHub Actions</a>なども出てきているので、それぞれの特徴を知って、自分に合ったサービスを見つけてください。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b95b1f708fd0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/why-not-use-cloud-build-for-gcp-projects-b95b1f708fd0">GCPを使ってるならCloud Buildがいいんじゃない？</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Pythonで日経平均株価のグラフを作成してみた（後編）]]></title>
            <link>https://medium.com/veltra-engineering/python-nikkei-quandl-api-2-2ae2002e361c?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/2ae2002e361c</guid>
            <category><![CDATA[pandas]]></category>
            <category><![CDATA[matplotlib]]></category>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[日本語]]></category>
            <category><![CDATA[python3]]></category>
            <dc:creator><![CDATA[shimo]]></dc:creator>
            <pubDate>Wed, 18 Mar 2020 01:01:01 GMT</pubDate>
            <atom:updated>2020-03-18T01:01:01.342Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h6HSYV2MtTBukXAC_U96Xg.jpeg" /></figure><p>前編では、APIで日経平均株価のデータの取得と前処理、移動平均線の表示まで行います。<br>後編では、前編のデータを使い、ポイントアンドフィギュア(以下: P&amp;F)の表示を行います。</p><p>PythonはJupyter Notebookで実行しています。</p><h4>はじめに:</h4><p>Pythonを使ってAPIでデータ取得、前処理、グラフ表示をするのが目的です。要件をみたすデータとして、日経平均株価を使って移動平均線とP&amp;Fを表示しています。</p><p>こちらのデータを使って投資を薦めているわけではありません。</p><h4>データ整形:</h4><p>ここからは、ソース内にコメントを記載していきます。</p><p>まずは関数を作成します。</p><p>作成する関数</p><ul><li>日経平均値から閾値の計算: getFrameValue</li><li>P&amp;Fの枠数のリスト作成: getFrameList</li><li>P&amp;Fのマーク用に1 or -1 を戻す</li></ul><pre># 値が枠の閾値になるように計算<br># 計算方法: 値 / 枠の幅 -&gt; 整数に変換 -&gt; 枠の幅を乗算<br># ex) 値: 23820, 枠の幅 200, 閾値, int(23820 / 200) -&gt; 119 * 200 -&gt; 23800<br>def getFrameValue(val, box):<br>  return int(val / box) * box</pre><pre># チャートに記載する枠数を求めリストにセット<br># ex) 値: [2000, 2250, 20610, 22210, 21430, 23000], 枠の幅 200, リスト: [1, -3, 8, -4, 8]<br>def getFrameList(data_list, base_value, box, change_frame):</pre><pre>  p_list = [0] # returnの初期値<br><br>  # 対象データ分ループ<br>  for val in data_list:</pre><pre>    val = getFrameValue(val, box)<br>    last_diff = int((val — base_value)/box) # 前日との差分</pre><pre>    past_diff = p_list[-1] # 前日の枠数</pre><pre>    # 前日がプラス<br>    if past_diff &gt;= 0:<br>      # 前日より高い<br>      if last_diff &gt; 0:<br>        p_list[-1] = past_diff + last_diff # 高値更新のため上書き<br>        base_value = val<br>      # 前日より低い かつ 枠変換以下<br>      elif last_diff &lt; 0 and last_diff &lt;= change_frame*-1:<br>        p_list.append(last_diff) # 次の列になるため、追加<br>        base_value = val<br>    # 前日がマイナス<br>    elif past_diff &lt; 0:<br>      # 前日より安い<br>      if last_diff &lt; 0:<br>        p_list[-1] = past_diff + last_diff # 低値更新のため上書き<br>        base_value = val<br>      # 前日より高い かつ 枠変換以上<br>      elif last_diff &gt; 0 and last_diff &gt; change_frame:<br>        p_list.append(last_diff) # 次の列になるため、追加<br>        base_value = val</pre><pre>  # 1件目のデータ終値 からスタートしているので、プラスマイナス1<br>  # ここがないと、データが1マス分ずれていたので、調整<br>  if p_list[0] &lt; 0:<br>    p_list[0] -= 1<br>  elif p_list[0] &gt; 0:<br>    p_list[0] += 1<br>  elif p_list[1] &lt; 0: # 1つ目が0だと2つ目のデータで判断<br>    p_list[0] += 1<br>  elif p_list[1] &gt; 0: # 1つ目が0だと2つ目のデータで判断<br>    p_list[0] -= 1</pre><pre>  return p_list</pre><pre># 印の表示用に絶対値で割る, return は 1 or -1 <br># 0なら1<br>def sign(val):<br>  result = 1<br>  try:<br>    result = val / abs(val)<br>  # 0なら1を戻す<br>　except ZeroDivisionError:<br>    result = 1<br>  except:<br>    pass</pre><pre>  return result</pre><p>つづいて、枠の幅と変換点を指定して、P&amp;F作成用の配列を取得します。</p><pre># 1枠の幅と変換点、基準値を設定<br>box = 200<br>change_frame = 3<br>base_value = getFrameValue(df_target.values[0], box)</pre><pre># 表示用のリストを取得<br>p_list_100 = getFrameList(df_target, base_value, box, change_frame)</pre><pre># P&amp;Fの印と色を指定<br>symbol  = {-1:&#39;o&#39;,1:&#39;x&#39;} # チャートの印<br>s_color = {-1:&#39;red&#39;,1:&#39;blue&#39;} # チャートの色</pre><h4>P&amp;Fの表示:</h4><p>作成したデータからP&amp;Fを表示していきます。</p><pre># P&amp;Fの表示サイズを指定し、表示枠を作成<br>fig = plt.figure(figsize=(10, 10))<br>ax = fig.add_axes([.15, .15, .7, .7])</pre><pre># P&amp;Fの1点目をスタートの価格に設定<br>chgStart = base_value</pre><pre># 表示データ分ループ<br>for ichg, chg in enumerate(p_list_100):<br>  # X, Y軸の表示箇所を指定<br>  x = [ichg+1] * abs(chg)<br>  y = [chgStart + i * box * sign(chg) for i in range(abs(chg))]<br>  # 次のX軸の始点を指定<br>  chgStart += box * sign(chg) * (abs(chg)-2)</pre><pre>  # P&amp;Fの各点を表示<br>  ax.scatter(x, y,<br>             marker=symbol[sign(chg)],<br>             c=s_color[sign(chg)],<br>             s=175)</pre><pre># グリッドの中心にP&amp;Fの各印を表示するように調整<br># X軸のグリッドを実際の列分表示<br>plt.xticks(np.arange(0, len(p_list_100)+1,1))<br># Y軸のグレードを 最小、最大値 +-1 枠分、枠の幅ごとに表示<br>plt.yticks(np.arange(getFrameValue(df_target.min()-box*2, box), getFrameValue(df_target.max()+box*2,box),box))<br># grid の表示<br>plt.grid(True)</pre><pre># グラフ表示<br>plt.show()</pre><p>実行すると意図した取りP&amp;Fが表示できます。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/604/1*__orDIgJ-dn4Oin-eX8Ntw.jpeg" /></figure><p>日経平均株価のデータ取得からP&amp;Fの表示まで1日かからずに作成することが出来ました。棒グラフや円グラフなどは色々情報があったのですが、P&amp;Fは全然なく、表示するためのデータの作成に一番苦労をしました。</p><p>今回は線グラフとP&amp;Fのグラフのみ表示しましたが、月毎に集計して棒グラフにしたり、散布図などを表示するのもいいかもしれませんね。</p><h4>参考にしたURL:</h4><p>Jupyter Notebook: <a href="https://ai-inter1.com/jupyter-notebook/">https://ai-inter1.com/jupyter-notebook/</a></p><p>pandas: <a href="https://pandas.pydata.org/pandas-docs/stable/reference/index.html">https://pandas.pydata.org/pandas-docs/stable/reference/index.html</a></p><p>matplotlib: <a href="https://matplotlib.org/users/index.html">https://matplotlib.org/users/index.html</a></p><p>移動平均線: <a href="https://fx-quicknavi.com/chart/moving-average/">https://fx-quicknavi.com/chart/moving-average/</a></p><p>P&amp;F: <a href="https://fx-quicknavi.com/chart/point-and-figure/">https://fx-quicknavi.com/chart/point-and-figure/</a></p><p>Quandl API: <a href="https://www.quandl.com/">https://www.quandl.com/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2ae2002e361c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/python-nikkei-quandl-api-2-2ae2002e361c">Pythonで日経平均株価のグラフを作成してみた（後編）</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Googleオプティマイズで多数のページをリダイレクトテストする]]></title>
            <link>https://medium.com/veltra-engineering/google-optimize-redirect-89312235ebc2?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/89312235ebc2</guid>
            <category><![CDATA[redirect]]></category>
            <category><![CDATA[日本語]]></category>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[a-b-testing]]></category>
            <category><![CDATA[google-optimize]]></category>
            <dc:creator><![CDATA[Takashi Iwamoto]]></dc:creator>
            <pubDate>Mon, 16 Mar 2020 01:01:01 GMT</pubDate>
            <atom:updated>2020-06-17T08:30:58.791Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong>「リダイレクトテスト」機能では不可能。どうする？</strong></h4><pre>2020年5月に<a href="https://support.google.com/optimize/answer/9874647">「高度なリダイレクト」機能</a>が追加され、多数のページのA/Bテストがリダイレクトテスト機能でできるようになりました。本記事のタイトルおよび本文は、2020年3月時点の情報であり、古くなっています。ご注意ください。</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i7UOPyjN91mDp-7m44D4Ew.jpeg" /></figure><p><a href="https://www.veltra.com/jp/">ベルトラ</a>のイワモトです。「人生は選択の連続である」と誰かが言ったそうですが、確かに、A定食にすべきかB定食にすべきかなど、難しい選択を迫られる場面はよくあります。</p><p>さて、<a href="https://marketingplatform.google.com/intl/ja/about/optimize/">Googleオプティマイズ</a>は、ウェブサイトのA/Bテストが簡単にできる便利なツールです。「<a href="https://support.google.com/optimize/answer/6361119?hl=ja">リダイレクトテスト</a>」機能を使うと、新デザインページへのリダイレクトによるA/Bテストが実現できます。</p><p>しかし、2020年3月現在のリダイレクトテスト機能では、<strong>リダイレクト先が静的URLでしか指定できず、1ページしかテストできません</strong>。たとえば「商品0001の新ページ」はテストできますが「商品0001~商品9999の新ページ」はテストできないのです（※）。</p><pre>※リダイレクトを <strong>/products/0001 → /products2/0001</strong> のようには指定できるのですが、<strong>/products/{product_id} → /products2/{product_id}</strong> のようには指定できないためです。</pre><p>それでも、Googleオプティマイズで多数のページをリダイレクトテストする方法があります。以下、詳しくご紹介します。</p><h3>1. エクスペリエンスの作成</h3><p>まず、エクスペリエンスを作成します。Googleオプティマイズでは、各種テストやパーソナライゼーションを総称して「エクスペリエンス」と呼んでいます。</p><p>作成する際は、下記の2点に気をつけます。</p><ol><li><strong>「エディタページ」には新デザインページのURLを指定します。</strong>ビジュアルエディタの操作対象として使われるだけなので、どの商品でも構いません。現行デザインのURLにすると、ビジュアルエディタ上でリダイレクトが効いてしまい、操作しづらくなります。</li><li><strong>「テストのタイプ」は「A/Bテスト」を選択します。</strong>「リダイレクトテスト」を選択しないのがポイントです。</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/601/1*UEMK6pWTXsLZD9k0rVuqqA.png" /></figure><h3>2. パターンの追加</h3><p>次に、パターンを追加します。現行デザインは「オリジナル」パターンとしてデフォルトで登録されているので、新デザイン用のパターンだけを追加すればOKです。実際には、パターン名を決めるだけです。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/609/1*C_2BsoDfHtuTtuNcPJuTCQ.png" /></figure><h3>3. パターンの編集</h3><p>続いて、手順2で追加したパターンを編集します。ここでビジュアルエディタが使われます。</p><h4>3–1. 要素の選択</h4><p>ビジュアルエディタが開いたら、head要素を選択します。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qF5DzFdQQGmukgmApL7-4Q.png" /></figure><h4>3–2. 要素の編集</h4><p>「要素を編集」ボタンを押し、「JavaScriptを実行」を選択します。</p><p>コード入力欄が開いたら、リダイレクト用のコードを入力します。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LccB6ElT4__6mq-qryjQ6A.png" /></figure><p>適切なコードの内容はサイトやページによって異なります。ここでは下記のようにします。</p><pre>if (location.href.includes(&#39;/a/&#39;)) {<br>  // Hide body<br>  var css = &#39;body {display:none}&#39;;<br>  var style = document.createElement(&#39;style&#39;);<br>  style.appendChild(document.createTextNode(css));<br>  element.appendChild(style);</pre><pre>  // Redirect<br>  location.href = location.href.replace(&#39;/a/&#39;, &#39;/a2/&#39;);<br>}</pre><ul><li>URLに「/a/」が含まれる場合だけリダイレクトする</li><li>現行デザインがちらっと表示されるのを避けるため、bodyを非表示にする</li><li>リダイレクト先は、現行デザインページの「/a/」を「/a2/」に変えたもの</li></ul><p>プルダウンは「開始タグの後」を選択するのがよいと思います。「終了タグの後」より若干早く処理されるためです。</p><h4>3–3. 編集内容の保存</h4><p>「適用」ボタン、「保存」ボタン、「完了」ボタンを順に押し、編集内容を保存します。これでビジュアルエディタが終了します。</p><h3>4. ターゲティングURLの編集</h3><p>次に、ターゲティングURLを編集します。現段階では、手順1で選んだ新デザインページが仮設定されています。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/877/1*MscUUDU6s5IS4lAVSQg-Aw.png" /></figure><p>これを、正しいターゲットである「リダイレクトテストを実施したい現行ページすべて」に変更しなければなりません。</p><p>変更するには、鉛筆アイコンを押して編集画面を開き、下記のように編集します。</p><ol><li><strong>「URL」「次の正規表現に一致」を選ぶ</strong></li><li><strong>現行ページにマッチする正規表現を入力する</strong></li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/734/1*meMePGdIFG9HVmCGkAUbNg.png" /></figure><p>編集したら「ルールの確認」欄で、現行ページのURLが正しくマッチするかどうか確認します。問題なければ保存しましょう。</p><p>保存すると「 エディタページが URL ターゲティング ルールと一致しません。」と警告されます。が、気にしなくて大丈夫です。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/780/1*j08ckAHXpSsll7geJ5k7kw.png" /></figure><h3>5. その他の設定</h3><p>あとは必要に応じて、新デザインの表示確率、ターゲティングユーザー、<a href="https://support.google.com/optimize/answer/7018998">テスト目標</a>などの項目を設定します。このあたりはGoogleオプティマイズの通常の使い方なので省略します。</p><h3>6. テストの開始</h3><p>設定が完了したら「開始」ボタンを押します。いよいよテスト開始です。</p><p>現行ページのURLにアクセスすると、手順5で指定した確率で、新デザインページにリダイレクトされます。されなかった場合は、Cookieを削除し、再度アクセスしてみてください。</p><p>Cookieを削除するのが面倒なら、ブラウザのシークレットウィンドウでアクセスするのが簡単です。ウィンドウを閉じるとCookieが自動的に削除されます。</p><h3>おわりに</h3><p>今回は、Googleオプティマイズの「A/Bテスト」機能を使って多数のページをリダイレクトテストする方法をご紹介しました。将来的に「リダイレクトテスト」機能で可能になることを期待しますが、それまでは今回の方法で実施するのがよいと思います。</p><p>なお、この方法は下記ページの内容と原理的には同じです。下記ページでは、ビジュアルエディタに指定するJavaScriptコードが「フラグ変数を設定するだけ」になっています。フラグが立っていたらページ側のスクリプトでリダイレクトする寸法です。この辺は好みの問題でしょう。</p><p><a href="https://stackoverflow.com/questions/58756432/google-optimize-create-a-b-test-with-dynamic-url">Google Optimize - create A/B test with dynamic URL</a></p><p>Happy A/B testing!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=89312235ebc2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/google-optimize-redirect-89312235ebc2">Googleオプティマイズで多数のページをリダイレクトテストする</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Pythonで日経平均株価のグラフを作成してみた（前編）]]></title>
            <link>https://medium.com/veltra-engineering/python-nikkei-quandl-api-430ce6214dfc?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/430ce6214dfc</guid>
            <category><![CDATA[日本語]]></category>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[pandas]]></category>
            <category><![CDATA[python3]]></category>
            <category><![CDATA[matplotlib]]></category>
            <dc:creator><![CDATA[shimo]]></dc:creator>
            <pubDate>Thu, 12 Mar 2020 01:01:02 GMT</pubDate>
            <atom:updated>2020-03-12T01:01:02.249Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h6HSYV2MtTBukXAC_U96Xg.jpeg" /></figure><p>前編では、APIで日経平均株価のデータの取得と前処理、移動平均線の表示まで行います。<br>後編では、前編のデータを使い、ポイントアンドフィギュア(以下: P&amp;F)の表示を行います。</p><p>PythonはJupyter Notebookで実行しています。</p><h4>はじめに:</h4><p>Pythonを使ってAPIでデータ取得、前処理、グラフ表示をするのが目的です。要件をみたすデータとして、日経平均株価を使って移動平均線とP&amp;Fを表示しています。</p><p>こちらのデータを使って投資を薦めているわけではありません。</p><h4>日経平均株価のデータ取得:</h4><p>日経平均株価はQuandl APIから取得しています。</p><p>制限はありますが、フリーで使用できます。データ取得にはユーザー登録後、APIキーを発行します。</p><p>今回は”Nikkei/USD”を使用します。</p><p>ドキュメントにしたがってエンドポイントを作成します。</p><p>データはjsonで取得するので、下記の用になります。</p><pre>https://www.quandl.com/api/v3/datasets/CHRIS/CME_NK2/data.json?api_key={API_TOKEN}</pre><p>では実際に日経平均株価を取得してみます</p><p>まず、必要なライブラリをimportします<br>データの前処理、グラフ表示に必要なライブラリもimportしています。</p><pre># API<br>import requests<br>import json<br>import datetime as dt<br># 必要なデータを絞るための日付系<br>from dateutil.relativedelta import relativedelta<br>from pytz import timezone</pre><pre># データ前処理、グラフ表示<br>import numpy as np<br>import pandas as pd<br>%matplotlib inline<br>import matplotlib.pyplot as plt</pre><p>APIをコールしてデータ取得</p><pre># API key<br>API_TOKEN = ‘your API key’</pre><pre># エンドポイントを定義<br>url = ‘https://www.quandl.com/api/v3/datasets/CHRIS/CME_NK2/data.json?api_key={API_TOKEN}&#39;</pre><pre># json でデータ取得<br>catched_response = requests.get(url)<br>json_data = catched_response.json()</pre><p>※<a href="https://www.quandl.com/tools/python">Quandl Python Package</a>も配布されていますが、今回はrequestsモジュールを使います。</p><p>取得したデータを確認。欲しいデータは”dataset_data”内にある”column_names”と”data”になります。</p><p>DataFrame型にデータをセットします。</p><pre># jsonから必要なデータを取得<br>columns = json_data[‘dataset_data’][‘column_names’]<br>values = json_data[‘dataset_data’][‘data’]</pre><pre># DataFrameにセット後日付カラムをindexに設定<br>df = pd.DataFrame(values, columns=columns)<br>df.loc[:,&#39;Date&#39;] = pd.to_datetime(df.loc[:, &#39;Date&#39;])<br>df.set_index(‘Date’, inplace=True)</pre><p>DataFrameにセットしたデータを確認します。</p><pre>df.info()</pre><p>データタイプと個数が確認できます。</p><pre><em>&lt;class &#39;pandas.core.frame.DataFrame&#39;&gt;<br>DatetimeIndex: 7406 entries, 2020-02-12 to 1990-09-26<br>Data columns (total 8 columns):<br>Open                          7197 non-null float64<br>High                          7379 non-null float64<br>Low                           7349 non-null float64<br>Last                          7404 non-null float64<br>Change                        0 non-null object<br>Settle                        7406 non-null float64<br>Volume                        7406 non-null float64<br>Previous Day Open Interest    7406 non-null float64<br>dtypes: float64(7), object(1)<br>memory usage: 520.7+ KB</em></pre><p>総数 7406に対して、Open, High, Low, Lastの数がバラバラですので、欠損値が存在しているようです。</p><p>今回は終値だけ欲しいのでデータの確認、欠損値の処理は”Last”のみ行います</p><p>欠損値があるか確認します。</p><pre>df[df.loc[:, ‘Last’].isnull()]</pre><p>確かに2件データがとれます。</p><p>欠損値を処理します。</p><p>欠損値は以下のルールで処理します。</p><p>高値: 欠損値前後のNaNではない高値に線形で伸びるとする。</p><p>終値: 欠損値前後のNaNではない終値に線形で伸びるとする。ただし高値以上にならない</p><p>欲しいデータは終値だけなのですが、高値とも比較したいので、欠損値の処理は高値も行うことにします。</p><p>その為、高値、終値の順番に欠損値の処理を行います。</p><pre>df.loc[:,’High’].interpolate(inplace=True)<br>df.loc[:,’Last’] = np.minimum(df.loc[:,’Last’].interpolate(), df.loc[:,’High’])</pre><p>np.minimumで終値と高値を比較し低い方を取得しています。</p><p>interpolateで前後のNaN以外の値を取得し、その間の値を取得しています。高値は該当するオブジェクトをそのまま変更したいので、”inplace=True”を指定しています。</p><p>欠損値が埋まったことを確認します。</p><pre>df.info()</pre><p>総数 とLastが同じ値になっているので、欠損値が埋まったことが確認できます。</p><pre>&lt;class &#39;pandas.core.frame.DataFrame&#39;&gt;<br>DatetimeIndex: 7406 entries, 2020-02-12 to 1990-09-26<br>Data columns (total 8 columns):<br>Open                          7197 non-null float64<br>High                          7406 non-null float64<br>Low                           7349 non-null float64<br>Last                          7406 non-null float64<br>Change                        0 non-null object<br>Settle                        7406 non-null float64<br>Volume                        7406 non-null float64<br>Previous Day Open Interest    7406 non-null float64<br>dtypes: float64(7), object(1)<br>memory usage: 840.7+ KB</pre><p>データ取得から欠損値の処理までのデータの前処理が終わりました。</p><h4>日経平均株価の移動平均線を表示:</h4><p>移動平均線のグラフを表示しています。</p><p>全期間だとデータ量が多く見にくいので、1年分のみ表示します。</p><pre># 1年前の日付を取得<br>today = dt.datetime.now(timezone(&#39;Asia/Tokyo&#39;))<br>t_year = today - relativedelta(years=1)<br>t_date = t_year.strftime(&#39;%Y-%m&#39;)</pre><pre># 1年分の終値を取得し、グラフ表示<br>df_target = df.loc[:t_date,’Last’].sort_index()<br>df_target.plot()<br># 移動平均線も表示<br>df_target.rolling(window = 5).mean().plot()<br>df_target.rolling(window = 25).mean().plot()</pre><p>終値の移動平均線を表示したいので、終値だけを取得し、index(日付)でソートします。</p><p>日経平均株価と、5日移動平均線、25日移動平均線を表示します。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/529/1*NhpUIg4kvCPZK3FRz9TjPQ.jpeg" /></figure><p>移動平均線の表示ができました。</p><p>前編では主に、PandasのDataframeを使ったデータ処理がメインでした。後編のP&amp;Fの表示では、matplotlibをメインに使っています。</p><h4>参考にしたURL:</h4><p>Jupyter Notebook: <a href="https://ai-inter1.com/jupyter-notebook/">https://ai-inter1.com/jupyter-notebook/</a></p><p>pandas: <a href="https://pandas.pydata.org/pandas-docs/stable/reference/index.html">https://pandas.pydata.org/pandas-docs/stable/reference/index.html</a></p><p>matplotlib: <a href="https://matplotlib.org/users/index.html">https://matplotlib.org/users/index.html</a></p><p>移動平均線: <a href="https://fx-quicknavi.com/chart/moving-average/">https://fx-quicknavi.com/chart/moving-average/</a></p><p>P&amp;F: <a href="https://fx-quicknavi.com/chart/point-and-figure/">https://fx-quicknavi.com/chart/point-and-figure/</a></p><p>Quandl API: <a href="https://www.quandl.com/">https://www.quandl.com/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=430ce6214dfc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/python-nikkei-quandl-api-430ce6214dfc">Pythonで日経平均株価のグラフを作成してみた（前編）</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS SAMでSSL証明書の有効期限をチェックする]]></title>
            <link>https://medium.com/veltra-engineering/wip-aws-sam%E3%81%A7ssl%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AE%E6%9C%89%E5%8A%B9%E6%9C%9F%E9%99%90%E3%82%92%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B-6ee4e9d092c2?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/6ee4e9d092c2</guid>
            <category><![CDATA[日本語]]></category>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[ssl-certificate]]></category>
            <category><![CDATA[aws-sam]]></category>
            <category><![CDATA[aws-lambda]]></category>
            <dc:creator><![CDATA[Takashi Iwamoto]]></dc:creator>
            <pubDate>Fri, 07 Feb 2020 01:01:02 GMT</pubDate>
            <atom:updated>2020-02-07T01:10:03.479Z</atom:updated>
            <content:encoded><![CDATA[<h4>SAMテンプレートの記述例とその解説</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4TQNjnbcjV0VtwnCM6jvCw.jpeg" /></figure><p>虎の穴ラボさんの「<a href="https://toranoana-lab.hatenablog.com/entry/2020/01/31/184136">Python + AWS LambdaでSSL証明書の有効期限をチェックする</a>」を拝見し、この構成なら<a href="https://aws.amazon.com/jp/serverless/sam/">SAM</a>でデプロイするのが便利そうだと思いました。SAMテンプレートの記述例はこんな感じです。</p><h3>SAMテンプレートの記述例</h3><pre>AWSTemplateFormatVersion: &#39;2010-09-09&#39;<br>Transform: AWS::Serverless-2016-10-31</pre><pre>Parameters:<br>  Urls:<br>    Type: String<br>    Description: &quot;Domains to remind (comma separated)&quot;<br>    Default: &quot;example.com&quot;<br>  BufferDays:<br>    Type: Number<br>    Description: &quot;Days to start reminding&quot;<br>    Default: 30<br>  IncommingWebhooks:<br>    Type: String<br>    Description: &quot;URL of Slack Incoming Webhook&quot;<br>    Default: &quot;https://hooks.slack.com/services/*&quot;</pre><pre>Resources:<br>  RemindFunction:<br>    Type: AWS::Serverless::Function<br>    Properties:<br>      CodeUri: remind/<br>      Handler: app.lambda_handler<br>      Runtime: python3.7<br>      Timeout: 10<br>      Environment:<br>        Variables:<br>          Urls: !Ref Urls<br>          BufferDays: !Ref BufferDays<br>          IncommingWebhooks: !Ref IncommingWebhooks<br>      Events:<br>        RemindSchedule:<br>          Type: Schedule<br>          Properties:<br>            Schedule: &quot;cron(0 1 * * ? *)&quot;<br>  RemindFunctionLogGroup:<br>    Type: AWS::Logs::LogGroup<br>    DependsOn: RemindFunction<br>    Properties:<br>      LogGroupName: !Sub /aws/lambda/${RemindFunction}<br>      RetentionInDays: 90</pre><pre>Outputs:<br>  RemindFunction:<br>    Description: &quot;Remind Function ARN&quot;<br>    Value: !GetAtt RemindFunction.Arn<br>  RemindFunctionRole:<br>    Description: &quot;Implicit IAM Role created for Remind function&quot;<br>    Value: !GetAtt RemindFunctionRole.Arn<br>  RemindFunctionLogGroup:<br>    Description: &quot;Log Group for Remind Function&quot;<br>    Value: !GetAtt RemindFunctionLogGroup.Arn<br>  RemindFunctionRemindSchedule:<br>    Description: &quot;Schedule for Remind Function&quot;<br>    Value: !GetAtt RemindFunctionRemindSchedule.Arn</pre><h3>記述のポイント</h3><p>以下、記述のポイントを解説します。ポイントは3つです。</p><h4>1. sam deploy時のパラメータで環境変数を渡す</h4><p>元記事では、3つの環境変数をLambda関数に渡しています。</p><ul><li>チェック対象のドメイン (Urls)</li><li>通知対象とする日数の閾値 (BufferDays)</li><li>Slack通知用URL (IncommingWebhooks)</li></ul><p>その要件をSAMで実現するため記述したのが下記の部分です。 sam deploy 時にデフォルトパラメータを上書きすることで、Lambda関数に環境変数を渡せます。</p><pre><strong>Parameters:<br>  Urls:<br>    Type: String<br>    Description: &quot;Domains to remind (comma separated)&quot;<br>    Default: &quot;example.com&quot;<br>  BufferDays:<br>    Type: Number<br>    Description: &quot;Days to start reminding&quot;<br>    Default: 30<br>  IncommingWebhooks:<br>    Type: String<br>    Description: &quot;URL of Slack Incoming Webhook&quot;<br>    Default: &quot;https://hooks.slack.com/services/*&quot;</strong></pre><pre>Resources:<br>  RemindFunction:<br>    Type: AWS::Serverless::Function<br>    Properties:<br>      -- （中略） --<br><strong>      Environment:<br>        Variables:<br>          Urls: !Ref Urls<br>          BufferDays: !Ref BufferDays<br>          IncommingWebhooks: !Ref IncommingWebhooks</strong></pre><h4>2. Cron式でスケジュールを定義する</h4><p>また、元記事では、CloudWatchのイベントでLambda関数の実行スケジュールを定義しています。JSTの10:00なら「0 1 * * ? * 」のように<a href="https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/scheduled-events.html#cron-expressions">Cron式</a>で指定するわけです。</p><p>SAMテンプレートでも、このCron式が使えます。下記が該当部分です。</p><pre>Resources:<br>  RemindFunction:<br>    Type: AWS::Serverless::Function<br>    Properties:<br>      -- （中略） --<br><strong>      Events:<br>        RemindSchedule:<br>          Type: Schedule<br>          Properties:<br>            Schedule: &quot;cron(0 1 * * ? *)&quot;</strong></pre><p>「Schedule」が2つ出てきて混乱しそうですが、それぞれ下記の意味となります。</p><ul><li>Lambda関数の<a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-events">Eventsプロパティ</a>に<a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-schedule.html">Schedule</a>オブジェクトを指定</li><li>Scheduleオブジェクトの<a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-schedule.html#sam-function-schedule-schedule">Scheduleプロパティ</a>でCron式を指定</li></ul><h4>3. CloudWatchログの保持期間を指定する（任意）</h4><p>なお、必須ではありませんが、CloudWatchの<a href="https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html">LogGroup</a>リソースを明示的に記述すると、ログイベントの保持期間が指定できます。今回は下記のように90日間としました。</p><pre>Resources:<br>  -- （中略） --<br><strong>  RemindFunctionLogGroup:<br>    Type: AWS::Logs::LogGroup<br>    DependsOn: RemindFunction<br>    Properties:<br>      LogGroupName: !Sub /aws/lambda/${RemindFunction}<br>      RetentionInDays: 90</strong></pre><p>こうすると、不要なログイベントが残り続けて課金対象となるのを防げます。もちろん、あえて残しておきたい場合は記述しなくて構いません。</p><h3>デプロイ</h3><p>さて、SAMテンプレートができたら、あとはビルドしてデプロイするのみです。下記のコマンドを実行すれば、Lambda関数の作成からCloudWatchの設定までが、あれよあれよという間に進んでいきます。</p><pre>$ sam build<br>$ sam deploy --guided</pre><p>元記事のLambdaコードも含め、下記のGitHubリポジトリにまとめています。よろしければご参照ください。</p><p><a href="https://github.com/vt-iwamoto/ssl-expiry-reminder">vt-iwamoto/ssl-expiry-reminder</a></p><p>元記事のLambdaコードのおかげで、SSL証明書の期限切れ監視ツールとして実用に耐えるものになっていると思います。ベルトラでも活用できそうなサイトがあれば、実環境に投入してみるつもりです。きっかけをくださった虎の穴ラボさん、ありがとうございます。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6ee4e9d092c2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/wip-aws-sam%E3%81%A7ssl%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AE%E6%9C%89%E5%8A%B9%E6%9C%9F%E9%99%90%E3%82%92%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B-6ee4e9d092c2">AWS SAMでSSL証明書の有効期限をチェックする</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS SAMで「Stage」ステージが作られるバグを回避する]]></title>
            <link>https://medium.com/veltra-engineering/avoid-aws-sam-stage-stage-45f7331b7b5d?source=rss----96a0b0173423---4</link>
            <guid isPermaLink="false">https://medium.com/p/45f7331b7b5d</guid>
            <category><![CDATA[aws-sam]]></category>
            <category><![CDATA[veltra]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[日本語]]></category>
            <dc:creator><![CDATA[Takashi Iwamoto]]></dc:creator>
            <pubDate>Thu, 16 Jan 2020 01:01:01 GMT</pubDate>
            <atom:updated>2020-01-16T01:01:01.265Z</atom:updated>
            <content:encoded><![CDATA[<h4>AWS::Serverless::ApiのOpenApiVersionを指定すればOK</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dPj6Vu5V6ZCPwVhwEW4B0g.jpeg" /></figure><p>先日、<a href="https://aws.amazon.com/jp/serverless/sam/">AWS SAM</a>の<a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html">チュートリアル</a>を進めていて、アプリケーションをデプロイした際、API Gatewayに2つのステージ（「Prod」と「Stage」）が作られ、不思議に思いました。必要なのは「Prod」だけです。調べると、これはSAMのバグで、すでに回避策が提供されていることが分かりました。本記事では、その回避策をご紹介します。</p><h3>回避策：AWS::Serverless::ApiのOpenApiVersionを指定する</h3><p>その回避策とは「SAMテンプレートの<a href="https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-resource-api.html">AWS::Serverless::Api</a>リソースに対する<a href="https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-openapiversion">OpenApiVersion</a>の指定」です。GitHubの<a href="https://github.com/awslabs/serverless-application-model/issues/191">issue</a>で<a href="https://github.com/awslabs/serverless-application-model/issues/191#issuecomment-515589603">正式な回避策として紹介されています</a>。</p><p>OpenApiVersionに指定できる値は下記の通りです。</p><blockquote>This can either be 2.0 for the Swagger specification, or one of the OpenApi 3.0 versions, like 3.0.1. For more information about OpenAPI, see the <a href="https://swagger.io/specification/">OpenAPI Specification</a>.</blockquote><h4>Globalsセクションで指定する場合</h4><p>実際の指定先として手っ取り早いのは<a href="https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst">Globalsセクション</a>です。下記のように指定するだけで、不要な「Stage」ステージが作られなくなります。</p><pre>AWSTemplateFormatVersion: &#39;2010-09-09&#39;<br>Transform: AWS::Serverless-2016-10-31</pre><pre>Globals:<br>  Function:<br>    Timeout: 3<br><strong>  Api:<br>    OpenApiVersion: 3.0.2</strong></pre><pre>Resources:<br>  HelloWorldFunction:<br>    Type: AWS::Serverless::Function<br>    Properties:<br>      CodeUri: hello_world/<br>      Handler: app.lambda_handler<br>      Runtime: python3.7<br>      Events:<br>        HelloWorld:<br>          Type: Api<br>          Properties:<br>            Path: /hello<br>            Method: get</pre><h4>Resourcesセクションで指定する場合</h4><p>もしくは下記のように、<a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html">AWS::Serverless::Function</a>リソースの<a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-eventsource.html">EventSource</a>側で指定しても、不要な「Stage」ステージが作られなくなります。</p><pre>AWSTemplateFormatVersion: &#39;2010-09-09&#39;<br>Transform: AWS::Serverless-2016-10-31</pre><pre>Globals:<br>  Function:<br>    Timeout: 3</pre><pre>Resources:<br>  HelloWorldFunction:<br>    Type: AWS::Serverless::Function<br>    Properties:<br>      CodeUri: hello_world/<br>      Handler: app.lambda_handler<br>      Runtime: python3.7<br>      Events:<br>        HelloWorld:<br>          Type: Api<br>          Properties:<br>            Path: /hello<br>            Method: get<br><strong>            RestApiId:<br>              Ref: HelloWorldApiGatewayApi<br>  HelloWorldApiGatewayApi:<br>    Properties:<br>      StageName: Prod<br>      OpenApiVersion: 3.0.2<br>    Type: AWS::Serverless::Api</strong></pre><h3>回避策が使えるAWS SAM CLIのバージョン</h3><p>この回避策は<a href="https://github.com/awslabs/aws-sam-cli">AWS SAM CLI</a> 0.22.0以降であれば使えるはずです。aws-sam-translator 1.13.0で<a href="https://github.com/awslabs/serverless-application-model/commit/7859f19ada835377a628f1d495a193c8588f01eb">回避策が追加され</a>、AWS SAM CLI 0.22.0で依存しているaws-sam-translatorの<a href="https://github.com/awslabs/aws-sam-cli/pull/1412/files">バージョンが上がりました</a>。</p><h3>まとめ</h3><p>AWS SAMで余分な「Stage」ステージが作られるバグについて、回避策をご紹介しました。余分なステージを作ってもメリットはないので、忘れずに回避しましょう。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=45f7331b7b5d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/veltra-engineering/avoid-aws-sam-stage-stage-45f7331b7b5d">AWS SAMで「Stage」ステージが作られるバグを回避する</a> was originally published in <a href="https://medium.com/veltra-engineering">VELTRA Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>