技術向上

プログラミングの学び、気になるテクノロジーやビジネストレンドを発信

英語のリスニングを上達させるコツ【聞き流しに効果はあるか?】

英語のリスニングは難しい

日本人にとって英語のリスニングは難しい。

これは英語の語順とフランス語の語順、その下は英語の語順と日本語の語順を比べたもの。見ての通り、日本語は英語とは全く異なる語順になっている。

聞くときに日本語に翻訳しようとしてしまうと、英語に慣れないうちは余計に難しくなり、頭が痛くなる。


また、日本語話者では英単語の意味が想像しづらい、文法もまるで一致しないことがリスニングの上達を妨げていると思う。

リスニング、特にネイティブが話す英語を理解するには高速に意味を理解する必要がある。そんな時に単語の意味や言い回しが少しでも想像できると、全体理解につながりやすい。

英語はロジカルな言語であり、最初に結論を述べ、続いてその理由を説明する。また別の言い回しで同じことを話すことも多く、少しでも意味がわかると時間が経ってから理解が深まることも少なくない。

リスニングを上達させる方法

聞き流しは上達に効果的か?


私も同じこと考えてもう2年間、毎日5時間は英語のニュースを聞いている。確かに英語に慣れることはできた。しかし聞き流すだけでは理解するまでに至らなかった。

thとsの違いなど発音の違いがわからないままに聞き流しを続けていると、間違った情報を脳が記憶してしまうと耳にしたこともある。下に書く方法を試したところ、私はリスニング力が大きく向上した。

文法を学ぶ

文法は非常に効果的だった。文法に関しては大学受験までに本当に多くのことを学んでいたと思うが、すっかり忘れていた。現在形、過去形、完了形など時制は特に重要である。話が過去のことなのか、現在も続いているのか、これが理解できないと意味がつながらないし、会話が成立しない。

if + 過去形, wouldなどもよく耳にする文法。事実なのか、意見なのかを区別するにもこうした文法は理解しておく必要がある。

文法を学ぶにあたっては、世界で最も売れているというケンブリッジ大学のEnglish Grammar in Useがおすすめである。日本の学校教育では教わったこともないが重要な意味やニュアンスの違いがあることがわかる。英語で書かれているが、平易な英語で書かれており、言い換えや説明の勉強にもなるので臆することはない。

何種類かあるが、青色一冊を学べば一般的には事足りる

cambridge-university-press.jp



聞き流しを1年継続した後、この本で文法を学び直してからリスニングの理解度が3割から6割くらいに向上したと思う。

これはアメリカのニュースを聞いた時の話であり、TOEICのリスニング試験であれば8割から9割はこの時点で理解できるようになっていた。



発音を学ぶ

発音を学ぶことでどうしても分からなかった部分が急にわかるようになり、ネイティブの発音を聞き取る力が大きく向上した。

日本人だけでなく、大抵のインド人やイタリア人、フランス人が話す英語は母語のアクセントが強く残り、ネイティブの発音には程遠い。そんなことから「発音はリスニングの上達に関係ない」と決めつけていた。が、発音を学ぶと聞き取れるようになった。

なぜかをはっきりとは理解できていないが、人間は知っている音を聞き取れるようになっているらしい。確かにニュースで話されるような綺麗な英語は聞き取りやすいが、日常会話や強い訛りのある英語は難しいときもある。

まず基本ということで、アメリカ英語のきれいな発音を学びたいのであれば、YouTubeのRachel's Englishがおすすめ。口や舌をどのように動かすのか、実際の人の動画で説明してくれる。レベルを調整して繰り返してくれるレッスン形式で、余計な装飾や誇張がなく集中できるのも良い。

www.youtube.com



このチャンネルの動画を見ながら声に出し、また自分で別の教材をアクセントに気をつけて繰り返し音読することでリスニングを上達させることができた。

英語の言い回しを学ぶ

音読する教材としておすすめなのが、Perfect Phrasesという本。英語の日常、ビジネス上の会話をどのように組み立てればいいかを学ぶことができる。

同じことを表現するにも目上の人、フランクな関係で言い方を変える必要が英語にもある。多くの表現方法がコンパクトで軽い1冊に纏まっているので、持っていて損はなく、音読で表現に慣れれば会話もスムーズに始められるだろう。

Amazon | Perfect Phrases for ESL: Conversation Skills, Second Edition (English Edition) [Kindle edition] by Engelhardt, Diane | Language Instruction | Kindleストア



単語の発音をマスターしたいならこのツール

YouGlishはネイティブの単語の発音を覚えたいならとても便利なサービスである。

アメリカ英語、イギリス英語、オーストラリア英語に加え、カナダ英語、アイルランド英語、スコットランド英語、ニュージーランド英語から指定して、キーワードにまつわるYouTube動画を検索できる。

その単語が発音される数秒前から再生され、ハイライトされる字幕付きである。

youglish.com



イギリス英語、アメリカ英語、シングリッシュ...

英語にも色々ある。イギリス英語とアメリカ英語でスペルが違うし、アクセントが異なる。日本の英語教育はアメリカ英語を基本としているから、イギリス英語の言い回しやアクセントには慣れていないと聞き取れないことが多いだろう。

私はイギリス英語のアクセントに慣れるためにイギリスのSkyニュースを毎日聴いている。アメリカのNBCも毎日聴いているため、今では話者がイギリス英語だろうがアメリカ英語だろうが気にならない。

www.youtube.com


イギリスSkyニュースは政治の話が多い。報道の仕方も日本に近いものを感じる..

www.youtube.com


アメリNBCはビジネス、マネーの話が多い。



また、株に興味があればBloomberg Financeもいい。

www.youtube.com

サンフランシスコ シリコンバレーを中心とした技術トレンドならBloomberg Technologyをおすすめしたい。

www.youtube.com



シングリッシュシンガポール英語。文法がアメリカ英語よりもずっと省略されるらしい。シンガポールや東アジア系の英語に慣れたいのであれば、シンガポールのニュースCNAはおすすめである。ただ、非ネイティブの英語はネイティブの早口英語を聞き取るよりもずっと楽なので、余裕があればで良いかもしれない。

www.youtube.com


シンガポールCNAでは、アジアで今日何が起きたのかをレビューできる。

オーストラリア英語のアクセントには柔らかい印象を受ける。イギリス英語が聞き取れれば、オーストラリア英語もさほど難しくは感じない。こちらも余裕があれば、オーストラリアのABCはおすすめできる。

www.youtube.com


オーストラリアABCは自然や農業の話題が多い。

英語の日常会話を身につけたいのであれば、さらに工夫が必要

ニュースと日常会話は使われる言葉や言い方も異なり、音量や明瞭さも大きく異なる。

ニュースはプログラムに沿って、字幕も出るし、映像も内容に合わせて切り替わる。目で情報を補うことができてしまう。また、発声がしっかりしているので、とても聞き取りやすい。

日常会話はスラングが出てくるし、文法も略されることがある。それに加えて困るのが語尾をはっきり言わなかったりすることである。

ただ、英語の日常会話を聞き慣れるにもYouTubeはおすすめしたい。分野によって単語も大きく異なるので、まずは自分の趣味や興味のある単語でYouTube上を検索し、対談している動画を選ぶといいと思う。

tech-up.hatenablog.com

英語がデキると情報の質が高くなり、選択肢が広がる

きっかけ

現在勤めている会社が社員に英語力を求めているのがきっかけで勉強を始めた。TOEICの点数が求められている。

が、今はそれに関係なく英語の勉強を毎日、2年続けている。 英語の情報に触れていると、英語がわからないことは大きな損失だと感じたから。

 

英語と情報

ネットに流れる英語の情報は日本語の30倍近く。マニアックな情報であるほど英語がわかる恩恵は大きい。

例えばエンジニアの情報。コーディングをしていてマニュアルでも分からない壁にぶつかった時、英語で書かれているStackoverflowを検索すると解決することが少なくない。


いずれリアルタイム翻訳機が普及して英語は必要なくなる」と言い訳して、大学入学以来、英語をサボってきた


今では後悔している。いずれ、なんて考えているうちに遅れていく。

量はもちろん、先端の情報は大体が日本国外から英語で生まれる。それを追いかけて日本語に訳されるのが現状。日本語の情報を待っていると周回遅れになる。

お金と同じように、進化にも「複利の効果」が当てはまる。

質の高い情報を早く得ることができれば、行動が変わる。

例え1日がごくわずかな積み重ねであっても、複利によって数年後には大きな開きになる。日本語だけ or 英語も のどちらが質の高い情報を得る可能性が高いかは明らか。

 

英語とキャリア

外資系で働ける、出世できる、ここまでが以前の共通認識。今は日本にいながら海外の仕事を受けられるようになっている。

円安が進んでいるので、アメリカドルを稼げば日本での生活は楽になる。

物価や税金が低い場所に移住するのも良い。日本の少子高齢化は深刻で、経済・政治の地盤も弱いので、若年層が大きな負担を強いられる日は近い。また大地震が起きる可能性が高いということも忘れてはならない。いざという時に海外で暮らすというオプションは無視できないものではないか。

 

英語翻訳のおすすめツール

ネット上の文章を理解するなら、翻訳機能は便利。大抵の意味はわかってしまう。

Google翻訳だと文意がつながらない翻訳が多いので、DeepLが良い。無料でツールをインストールすれば、ショートカットでメールや資料上の言語もすぐに翻訳できる。


www.deepl.com

 

それでもリスニングを鍛えた方が良い

YouTubeや音声メディアの浸透によって、動画や音声の有益なコンテンツが増えたYouTubeで世界中のニュースもLiveで観ることができるし、アメリカの大学MITのコンピューターサイエンスだって受講可能。

現SEC長官(アメリカ証券業界を仕切る人)ゲイリー・ゲンスラー氏のブロックチェーン講義も無料。SECが仮想通貨をどう捉えているか、なぜ規制しようとするかが感覚としてわかるようになる。(規制は仮想通貨の値動き、存続に大きな影響を及ぼす)

それから、ブロックチェーンで起業を志す人のためのスタンフォード大学教授やコインベースCEOの授業だってある。

動画、音声の方が、文章よりも話者、世間がどこに注目、興奮しているのかが分かりやすい。例えばブロックチェーンも英語で最先端に触れることができるが、この界隈には話題先行の理由付けも見受けられ、話し方でそれが伝わってくることがある。動画や音声の方がそういうものを見分けやすいと思う。楽しい時もあれば、冷静にもなれる時がある。

それでも英語のコンテンツは言語学の観点からも論理的な説明になりやすいので、英語がわかればスッと腑に落ちる、興味を惹かれるものが多い。 先端の情報に触れて理解を深めたいのであれば、リスニングを鍛えるのは本当におすすめ。

 

リスニングができるようになってきて、世界がまた広がった

元々、大学受験レベルはそこそこわかる方だったのでリーディングはなんとかできていたものの、リスニングはよくある短文のゆっくりとした日常会話の教材向け音声なら理解できたレベルだった。講義やニュースなんてもちろん分からない。始まって数分でもう頭が疲れていた。

けれど今ではニュースや講義であれば集中することで意味を大方把握できるようになった。

自分が興味のある経済、金融とブロックチェーンの情報を英語の動画や音声メディアで収集している。日本語メディアだけを見ていた2年前から考え方や知識が大きく変化している。

英語の有料で良質な情報にも目を向けてピックアップしていきたいし、得た情報を発信、何かしら形にしていくことをそろそろ目標にしていきたい。

tech-up.hatenablog.com

GolangでCasbin~アクセス制御ライブラリ~【Go】

What is Casbin


What Casbin does

  • subject(主語)、object(目的語)、action(行動)をベースとして柔軟に「許可」「不許可」を定義することができる。
  • RBAC(役割ベース)モデル(マルチテナント)など多様な権限設定が可能。
  • adminユーザーを定義することができる。
  • ルールの一致検証に使える演算子(関数)を提供。
    • 例えばkeyMatchは「/ foo / barがパターン/ foo *にマッチする」と判定できる。


What Casbin does NOT do

  • ログイン認証。
    • あくまで文字列に対するルールを管理する(read == trueのような)。
  • ユーザー自体の管理や役割(役割自体の定義)の管理。


How it works

  • .conf形式で、構成ファイル(ポリシー記述の仕様書)を作成する。
  • 構成ファイルで定義した仕様に則り、.csv形式で役割、権限を定義する。
  • サポートされている言語のCasbinライブラリを使って、構成ファイルとポリシーファイルを読み込み、trueかfalseを判定する。
  • 各々のコードに、条件分岐によって、trueなら処理を許可する、falseならエラーを返すなどの処理を書く。


以下に、RBACモデルにおける例を書きます。

sample_configuration.conf

[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, dom, obj, act

[role_definition]
g = _, _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act


構成ファイルは、PERM(Policy/Effect/Request/Matchers)に則って記述します([request_definition][policy_definition][policy_effect][matchers]の4つのセクションが必要)。

RBAC(役割ベースのアクセス制御)モデルを利用する場合、セクション[role_definition]も追加します。
 

  • [request_definition]
    • Casbinのライブラリで、構成ファイルとポリシーファイルを読み込んでtrue or falseを判定するEnforce()メソッドの引数を定義します。
    • つまり、「sub(subject)はobj(object)に対してact(action)できますか?」を問い合わせる書式です。


  • [policy_definition]
    • ポリシーファイルに記述する「誰が、何に対して、何ができるか(RBACの場合、テナント(domain)も記述)」というポリシーのフォーマットを定義します。
    • 「誰が、〜」のように書きましたが、それは目的あって、全ての要素は単なる識別するための文字列で、それ自体に意味は持ちません。
      • あくまで、ポリシーファイルの定義とリクエストを付き合わせることで「一致しているかどうか」を確認するときに意味を持ちます。
      • なので、actはreadではなくてGetと書いてもOKです。Enforce()を使う際、[request_definition]のactに当たる部分にhttp.Methodを指定してrequestができるかどうかを判定させることができます。
    • 執筆現在、Casbinは複数のポリシー定義パターンを許容していません。
      • 例えば「dom(domain)を用いる場合」と「dom(domain)を用いない場合」の2つを同時に定義することは、今の所できません。


  • [policy_effect]
    • Effect()などを使うリクエストの内容が、複数のポリシー定義にマッチする場合に、そのリクエストを許可するか拒否するかを定義します。
    • someは「あるかどうか」を意味します。
    • whereで条件を判定します。
    • p.eftは「ポリシーのマッチ結果」を意味し、それが== allowであれば、許可されている、== denyであれば拒否されている、となります。
    • ただし、何でも書けるわけではありません。Casbinの作成者が「それほどの柔軟さは求められない」と判断したためです。次のパターンがハードコードされており、指定することが可能です(詳細)。
      • some(where (p.eft == allow))
      • !some(where (p.eft == deny))
      • some(where (p.eft == allow)) && !some(where (p.eft == deny))
      • priority(p.eft) || deny


  • [matchers]
    • [request_definition][policy_definition]に記載されるのは、「書き方」であって、順番と識別のための情報(名前付きの箱)です。
    • [matchers]で両者を紐づけ、条件判定に使います。
    • +-*/のような算術演算子&&||!の論理演算子を使うことができます。
    • タイトルは複数形でありながら、複数のmatcherを定義することはできません。


  • [role_definition]
    • type(上記例ではg)と要求する個数(少なくとも2つ)を、カンマ区切りで_を使って指定します。
    • 「誰に、どのテナントの、何のポリシーを与えるか」や「誰に、どのテナントの全ての権限を与えるか」などを定義します。
    • 上記のようなことは[matchers]によって判断します。
      • 例えば右記について。m = g(r.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
      • 上記はg(r.sub, r.dom)の部分で、ポリシーファイルのgで指定されたものについて(例:g, bob, domain2)、1番目の要素(bob)がリクエストのsubと、2番目の要素(domain2)がリクエストのdomと文字列として等しいかをチェックします。

sample_policy.csv

p, admin, domain1, data1, read
p, admin, domain1, data1, write
p, admin, domain2, data2, read
p, admin, domain2, data2, write

g, alice, admin, domain1
g, bob, admin, domain2


pで表記されているポリシー定義と、gで表記されている役割定義は、それぞれ先述した下記の構成ファイルの定義に沿って書かれています。


[policy_definition]
p = sub, dom, obj, act


1行目を解釈すると、
「adminは、domain1で、data1を、readできる」という意味になります。

(matchersが無ければ、あくまでただの識別子ですが、わかりやすさのために書いています)
 

[role_definition]
g = _, _, _


役割定義の1行目を解釈すると、
「aliceは、adminであり、domain1に属する」となります。

(こちらも本来は、順序によって識別するただのマークです。)

 

サーバーサイドのコード

Go(with firebase authentication)を例に解説します。

※ 別ファイル、別パッケージとした方が良い部分がありますが、簡単のため、まとめています。


func main() {
    // 構成ファイルとポリシーファイルを読み込み、CasbinのEnforcerを生成
    authEnforcer, err := casbin.NewEnforcerSafe("./auth_model.conf", "./policy.csv")
    if err != nil {
        log.Fatal(err)
    }

    //依存性 dependency(d)を注入
       //  NewMiddleware(svc Service, CasbinEnforcer: *casbin.Enforcer)の実行など

    // chi.Routerを使ってrouting
    r.With(d.FirebaseAuth.Handle).Route("/hoge", func(r chi.Router) {
        r.Get("/", d.HogeHandler.Get)
    })
}

func (m *Middleware) Handle(next http.Handler) http.Handler {
// 認証を行いたいパスへのアクセスを処理する何かしらのHandlerを想定
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    idToken := m.getAuthorizationHeader(r) // Headerから認証情報であるid token(firebase authentication)を取得するメソッドを別途定義
    if idToken == "" {
            // エラー処理
            return
        }
    okFromCasbin, err := m.Svc.Authentication(ctx, r, m.CasbinEnforcer, idToken)
        if err != nil {
            // unauthorizedエラーを返す
            return
        }
        if okFromCasbin {
            // Casbinからtrueが返されたので、routingを成功させる
            next.ServeHTTP(w, r)
        } else {
            // unauthorizedエラーを返す
            return
        }
    })
}

func NewMiddleware(svc Service, CasbinEnforcer: *casbin.Enforcer) *Middleware {
    return &Middleware{
        Svc: svc,
        CasbinEnforcer: *casbin.Enforcer
    }
}

type Claims struct {
    Role string
}

func (s *service) Authentication(
    ctx context.Context,
    r *http.Request,
    CasbinEnforcer *casbin.Enforcer,
    idToken string,
) (bool, error) {
        claims := Claims{}

        t, err := s.firebaseauthClient.VerifyIDTokenAndCheckRevoked(ctx, idToken)
        if err != nil {
                // エラーログ
                return userID, claims, err
         }

        if role, ok := t.Claims["Role"].(string); ok {
            claims.Role = role
        }
        // contextにClaimを詰める処理
        // casbinにリクエスト
        return CasbinEnforcer.EnforceSafe(claims.Role, r.URL.Path, r.Method)
}


Casbinには
- 設定内容を読み込む - リクエストを送り、「許可」「不許可」を問い合わせる

メソッドがあるので、それを利用すればOKです。

「許可」が返されたら、serve HTTPをし、「不許可」ならUnauthorized(認証されていない)エラーを返します。


参考

Overview · Casbin

Casbin · An authorization library that supports access control models like ACL, RBAC, ABAC for Golang, Java, C/C++, Node.js, Javascript, PHP, Python, .NET (C#), Delphi, Rust, Ruby, Lua, Dart/Flutter and Elixir

Basic Role-Based HTTP Authorization in Go with Casbin - zupzup

Approach Question: Support license with roles · Issue #433 · casbin/casbin · GitHub

Add Permissions Requirement on route? · Issue #33 · casbin/casbin · GitHub

How to implement RBAC custom function which has to read information from database? · Issue #326 · casbin/casbin · GitHub

UnixTimeStampのカラムから日付ごとに集計する【MySQL】

UnixTimeStampで表現されているカラムを用いて、日付ごとに集計を行うクエリ。

SELECT DATE_FORMAT(FROM_UNIXTIME(c.confirmed_at),'%Y/%m/%d') AS confirm_date, COUNT(ci.id) AS quantity FROM carts AS o JOIN cart_items AS ci ON ci.cart_id = c.id WHERE ci.status = 0 GROUP BY confirm_date


Table

  • carts
    • date_timeをunix時間で保持
  • cart_items

の場合、次のような結果が返されます。

+-------------+---------+
| confirm_date | quantity |
+-------------+---------+
| 2019/11/16       |            3  |
| 2019/12/25      |            1   |
+-------------+---------+


Group Byに指定されたカラムの昇順で並びますが、 降順にしたい場合は、インナークエリ(パフォーマンスが悪くなります)かコードで処理します。


Cloud Pub SubをトリガーにしたCloud Functionsをデプロイする【Go】【GCP】

Cloud Pub/Sub

Cloud Pub/Subは、GCPの環境を使って、手軽にPublisherとSubscriberを構成できる、Googleによるクラウドツールです。

概要については、こちらに記載していますのでご覧ください。

Cloud Functions

Cloud Functionsは、GCPの環境を使って、手軽に関数単位でデプロイできる、Googleによるクラウドツールです。

概要については、こちらに記載していますのでご覧ください。


構成

あるTokenにPublishされたものをSubscribeすることをトリガーに、 Cloud Functionsとしてデプロイした関数を起動することができます。

Pubilsh
↓
あるTopic
↓ Subscribe
Cloud Functions上にデプロイした関数の発動


必要な設定は、Cloud Functionsに関数をデプロイするときに、トリガー(何をきっかけに関数を発動するか)にPub/SubのPublishイベントを指定し、あらかじめ設定しておいたPub/SubのTopicを指定することです。

ソースコードの記述

今回は関数をGolangで記述する例を紹介します。

// グローバルに定義した型や定数、変数は、関数がデプロイされている状態であれば、確保され続ける

type pubSubMessage struct { // この中から必要なものだけ指定すればOK
    Attrs       map[string]string `json:"attributes"`
    Data        []byte            `json:"data"` // base64-encoded data  送られるメッセージ
    ID          string            `json:"messageId"`
    PublishTime string            `json:"publishTime"`
}

type publishedData struct {
    Orders     []*orderData `json:"orders"`
}

type orderData struct {
    OrderID                 string `json:"order_id"`
    OrderStatus             string `json:"order_status"`
}

type got struct {
    Orders     []*gotOrder `json:"orders"`
}


type gotOrder struct {
    ID int64 `json:"id"`
}


var ordersURL *url.URL

func init() {  // 関数内で繰り返し使用する環境変数をinitで最初に読み込む。関数デプロイ時に実行される
    var err error
    ordersURL, err = url.Parse(os.Getenv("INTEGRATION_URL"))
    if err != nil {
        panic("error occurred parsing INTEGRATION_URL")  もし、指定した環境変数が存在しなければ、デプロイを失敗させる
    }
    ordersURL.Path = path.Join(ordersURL.Path, "orders")
}

func UpdateOrder(ctx context.Context, m pubSubMessage) error { // context.ContextとPub/Subメッセージを引数に取るのが決まった形式。 errorを返すことで、コンソール画面で一覧できる。
    var pData publishedData

    if err := json.Unmarshal(m.Data, &pData); err != nil {
        return fmt.Errorf("json unmarshal error, from m.Data to publishedData: %v", err)
    }

    orders := pData.Orders
    client := http.Client{Timeout: 15 * time.Second}

    for _, order := range orders {
        if err := func() error { // for文の中で、defer でBodyをCloseする際、オーバーヘッドを減らすために即時実行関数を使用
            req, err := createRequest(ctx, http.MethodGet,
                ordersURL.String(), nil)
            if err != nil {
                return err
            }

            q := req.URL.Query()
            q.Set("ec_order_id", order.OrderID)
            req.URL.RawQuery = q.Encode()

            resp, err := client.Do(req)
            if err != nil {
                return fmt.Errorf("http GET request error: %v", err)
            }
            defer resp.Body.Close()

            isSuccess := http.StatusOK <= resp.StatusCode && resp.StatusCode <= http.StatusPartialContent  // 206までなら成功と扱う
            if !isSuccess {
                return fmt.Errorf("failed to request GET: status code = %d", resp.StatusCode)
            }

            var got gotModel
            err = json.NewDecoder(resp.Body).Decode(&got)  / Streamのまま扱うことで、メモリ効率を下げない(↔︎ioutil.ReadAllからのjson.Unmarshal)
            if err != nil {
                return fmt.Errorf("json decode, from response body to got:  %w", err)
            }

            reqData, err := json.Marshal(order)
            if err != nil {
                return fmt.Errorf("json marshal error, order: %v", err)
            }
            for _, gotData := range got.Orders {
                ordersURL.Path = path.Join(ordersURL.Path, strconv.FormatInt(gotData.ID, 10))
                req, err = createRequest(ctx, http.MethodPut,
                    ordersURL.String(),
                    bytes.NewReader(reqData))
                if err != nil {
                    return err
                }

                resp, err = client.Do(req)
                if err != nil {
                    return fmt.Errorf("http PUT request error: %v", err)
                }
                defer resp.Body.Close()

                isSuccess = http.StatusOK <= resp.StatusCode && resp.StatusCode <= http.StatusPartialContent
                if isSuccess {
                    if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {  // bodyの結果を利用しないので、切り捨てる
                        log.Printf("[WARN] discard response body error: %v", err)
                    }
                } else {
                    b, err := ioutil.ReadAll(resp.Body)  // なぜエラーになったのかを知りたいため、bodyを読む
                    if err != nil {
                        log.Printf("failed to read response body, PUT: %v", err)
                    }
                    return fmt.Errorf("failed to request PUT: %s", string(b))
                }
                ordersURL.Path = path.Dir(ordersURL.Path)  // 最後の"/"の後に指定されたパスを切り捨てる、pathのメソッド(ファイルに対するディレクトリの意味)。
            }
            return nil
        }(); err != nil {  // for文の中のdefer resp.Body.Close()のためにifで囲ったので、ここでエラーをキャッチする必要がある
            return err
        }
    }
    return nil
}


func createRequest(
    ctx context.Context,
    method, uri,
    body io.Reader,
) (*http.Request, error) {
    req, err := http.NewRequest(method, uri, body)
    if err != nil {
        return nil, fmt.Errorf("create request error: %v", err)
    }
    req = req.WithContext(ctx)  // forのなかで、reqは繰り返し生成されるので、header、contextを都度設定
    req.Header.Add("user-agent", "UpdateOrder/1.0")
    req.Header.Add("content-type", "application/json; charset=utf-8")
    return req, nil
}


デプロイ

下記のようにコマンドをターミナルで実行し、デプロイします。

gcloud functions deploy <関数名> --runtime go111  --trigger-resource <topicID project/~などは不要> --trigger-event google.pubsub.topic.publish --region <リージョン。例:asia-northeast1> --set-env-vars <好きな変数名>=<好きな値> --timeout <任意。タイムアウトに指定する秒数。Maxは540>


このコマンドは、デプロイする関数が書かれているファイルと同じディレクトリで実行します。
関数名は先ほどのソースコードで言えば、UpdateOrderとなります。GCPのプロジェクト上で一意である必要がありますが、--entry-pointオプションを使って、ソースコード上の同じ関数を別名でデプロイすることも可能です。

タイムアウトはデフォルトで1分です。処理時間がかかりそうな関数の場合は、--timeoutオプションを指定して、伸ばしましょう。

--set-env-varsオプションを使えば、ソースコード上で使える環境変数を設定できます。


Pub/Sub、Cloud Functions共に、そこまで使用しなければ、無料で使えます。
Webサービスのアカウント登録後のメール送信など、ある程度時間のかかる処理をPublish Subscriber構成にしてUXを毀損させないようにするなど、手軽に試してみるといいと思います。


Cloud Pub/Sub概要【GCP】 - 技術向上

Cloud Functions概要【GCP】 - 技術向上

Cloud SQL Proxyで接続する【GCP】

Cloud SQL Proxyとは

Cloud SQL へ接続するにはいくつか方法があります(公式)。

その中でもCloud SQL Proxyを使った接続は、簡単にセキュアな接続ができる便利な方法です。
パブリックIP接続を構成したり、SSL/TLSを構成する手間が省けます。

プロキシサーバーをローカル環境で実行することによって機能します。
Cloud SQLに接続するアプリケーションは、セキュアな通信路を使用してプロキシサーバーにアクセスし、Cloud SQLに接続します。


インストール

前段で、必要があれば「Cloud SQL API を有効にする」を実行します。

プロキシをインストールします。

$ curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.386


cloud sql proxyの実行権限を設定します(Linux)。

$ chmod +x cloud_sql_proxy


接続

$ ./cloud_sql_proxy -instances=<INSTANCE_CONNECTION_NAME>=tcp:3306 -credential_file=<PATH_TO_KEY_FILE>
// 例↓
$ ~/cloud_sql_proxy-dir=/cloudsql -instances='team-a:asia-northeast1:team-a=tcp:3306' -credential_file=credentials_info.json


cloud_sql_proxyのバイナリファイルや、credential_fileの格納場所は、皆さんの環境次第だと思いますので、調整が必要です。

これによって、Cloud SQLに接続ができている状態になります。

My SQL接続

これはMy SQLを使用している場合の話になりますが、接続には次のようなコマンドを実行します。

$ mysql -u <ユーザー名> -p --host <ホストIP>
例↓
$ mysql -u user-a -p --host 127.0.0.1

実行後、パスワードの入力を求められるので、適切なものを入力します。

このユーザー名とパスワードは、Cloud SQLインスタンスに対して作成したユーザー名とパスワードのセットを意味します。


POSTなどの際、JSONの形式をチェックしつつも、bodyの指定は任意とする方法【Go】

POSTでbodyの入力を任意としたいけど、JSONの形式がちゃんとあっているかエラーハンドリングしたい場合。

func decodeJSON(r *http.Request, dst interface{}) error {
    dec := json.NewDecoder(r.Body)
    return dec.Decode(dst)
}

create(w http.ResponseWriter, r *http.Request) {
  if err := decodeJSON(r, &param); err != nil && err != io.EOF {
      // エラーハンドリング
        return
    }
  // 何かしらの処理
}


ポイントは、decodeJSONが返す、JSON形式のエラーチェックの他に、io.EOFのエラーでないことをチェックしている点です。

io.EOFは、内容の終端でないかをチェックするものですが、このエラーの場合、bodyが指定されていないことになります。
そのエラーではないことを確認することで、bodyの指定を任意とすることができるのです。